You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Step 1: Add the Stock Entity in the CleanAspire.Domain Project
To begin, we’ll define the Stock entity in the CleanAspire.Domain project, under the Entities directory. This entity will represent a stock record, containing details about the associated product, quantity, and location. We’ll also add a DbSet<Stock> in the application database context to enable querying and saving Stock records to the database.
Here’s how the Stock entity should look:
/// <summary>/// Represents a stock entity./// </summary>publicclassStock:BaseAuditableEntity,IAuditTrial{/// <summary>/// Gets or sets the product ID./// </summary>publicstring?ProductId{get;set;}/// <summary>/// Gets or sets the product associated with the stock./// </summary>publicProduct?Product{get;set;}/// <summary>/// Gets or sets the quantity of the stock./// </summary>publicintQuantity{get;set;}/// <summary>/// Gets or sets the location of the stock./// </summary>publicstringLocation{get;set;}=string.Empty;}
/// <summary>/// Configures the Stock entity./// </summary>publicclassStockConfiguration:IEntityTypeConfiguration<Stock>{/// <summary>/// Configures the properties and relationships of the Stock entity./// </summary>/// <param name="builder">The builder to be used to configure the Stock entity.</param>publicvoidConfigure(EntityTypeBuilder<Stock>builder){/// <summary>/// Configures the ProductId property of the Stock entity./// </summary>builder.Property(x =>x.ProductId).HasMaxLength(50).IsRequired();/// <summary>/// Configures the relationship between the Stock and Product entities./// </summary>builder.HasOne(x =>x.Product).WithMany().HasForeignKey(x =>x.ProductId).OnDelete(DeleteBehavior.Cascade);/// <summary>/// Configures the Location property of the Stock entity./// </summary>builder.Property(x =>x.Location).HasMaxLength(12).IsRequired();/// <summary>/// Ignores the DomainEvents property of the Stock entity./// </summary>builder.Ignore(e =>e.DomainEvents);}}
publicinterfaceIApplicationDbContext{DbSet<Product>Products{get;set;}DbSet<AuditTrail>AuditTrails{get;set;}DbSet<Tenant>Tenants{get;set;}DbSet<Stock>Stocks{get;set;}Task<int>SaveChangesAsync(CancellationTokencancellationToken=default);}/// <summary>/// Represents the application database context./// </summary>publicclassApplicationDbContext:IdentityDbContext<ApplicationUser>,IApplicationDbContext{/// <summary>/// Initializes a new instance of the <see cref="ApplicationDbContext"/> class./// </summary>/// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>publicApplicationDbContext(DbContextOptions<ApplicationDbContext>options):base(options){}/// <summary>/// Gets or sets the Tenants DbSet./// </summary>publicDbSet<Tenant>Tenants{get;set;}/// <summary>/// Gets or sets the AuditTrails DbSet./// </summary>publicDbSet<AuditTrail>AuditTrails{get;set;}/// <summary>/// Gets or sets the Products DbSet./// </summary>publicDbSet<Product>Products{get;set;}/// <summary>/// Gets or sets the Stocks DbSet./// </summary>publicDbSet<Stock>Stocks{get;set;}/// <summary>/// Configures the schema needed for the identity framework./// </summary>/// <param name="builder">The builder being used to construct the model for this context.</param>protectedoverridevoidOnModelCreating(ModelBuilderbuilder){base.OnModelCreating(builder);builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());}/// <summary>/// Configures the conventions to be used for this context./// </summary>/// <param name="configurationBuilder">The builder being used to configure conventions for this context.</param>protectedoverridevoidConfigureConventions(ModelConfigurationBuilderconfigurationBuilder){base.ConfigureConventions(configurationBuilder);configurationBuilder.Properties<string>().HaveMaxLength(450);}}
Explanation of Properties
ProductId:
Represents the unique identifier of the associated product. It is nullable (string?) to allow flexibility during object creation.
Product:
A navigation property linking the Stock entity to the Product entity, enabling easy access to related product details.
Quantity:
Stores the current quantity of the stock.
Location:
Represents the physical location (e.g., warehouse or store) where the stock is stored. It defaults to an empty string.
Step 2: Add Stock Features in the CleanAspire.Application Project
In this step, we will add features for managing Stock in the CleanAspire.Application project. These features include queries and commands for handling stock-related operations, such as inventory queries and stock receiving/dispatching. Create the following structure under the Features directory:
Stocks\Queries
Stocks\Commands
Query: StocksWithPaginationQuery
publicrecordStocksWithPaginationQuery(stringKeywords,intPageNumber=1,intPageSize=15,stringOrderBy="Id",stringSortDirection="Descending"):IFusionCacheRequest<PaginatedResult<StockDto>>{publicIEnumerable<string>?Tags=>new[]{"stocks"};publicstringCacheKey=>$"stockswithpagination_{Keywords}_{PageNumber}_{PageSize}_{OrderBy}_{SortDirection}";}publicclassStocksWithPaginationQueryHandler:IRequestHandler<StocksWithPaginationQuery,PaginatedResult<StockDto>>{privatereadonlyIApplicationDbContext_context;publicStocksWithPaginationQueryHandler(IApplicationDbContextcontext){_context=context;}publicasyncValueTask<PaginatedResult<StockDto>>Handle(StocksWithPaginationQueryrequest,CancellationTokencancellationToken){vardata=await_context.Stocks.OrderBy(request.OrderBy,request.SortDirection).ProjectToPaginatedDataAsync(condition: x =>x.Location.Contains(request.Keywords)||(x.Product!=null&&x.Product.Name.Contains(request.Keywords)),pageNumber:request.PageNumber,pageSize:request.PageSize,mapperFunc: t =>newStockDto{Id=t.Id,ProductId=t.ProductId,Product=t.ProductId!=null?newProductDto{Category=(ProductCategoryDto)t.Product?.Category,Currency=t.Product?.Currency,Description=t.Product?.Description,Id=t.Product?.Id,Name=t.Product?.Name,Price=t.Product?.Price??0,SKU=t.Product?.SKU,UOM=t.Product?.UOM,}:null,Quantity=t.Quantity,Location=t.Location},cancellationToken:cancellationToken);returndata;}}
Command: StockReceivingCommand
publicrecordStockReceivingCommand:IFusionCacheRefreshRequest<Unit>,IRequiresValidation{publicstringProductId{get;init;}=string.Empty;publicintQuantity{get;init;}publicstringLocation{get;init;}=string.Empty;publicIEnumerable<string>?Tags=>new[]{"stocks"};}publicclassStockReceivingCommandHandler:IRequestHandler<StockReceivingCommand,Unit>{privatereadonlyIApplicationDbContext_context;publicStockReceivingCommandHandler(IApplicationDbContextcontext){_context=context??thrownewArgumentNullException(nameof(context));}publicasyncValueTask<Unit>Handle(StockReceivingCommandrequest,CancellationTokencancellationToken){// Validate that the product existsvarproduct=await_context.Products.FirstOrDefaultAsync(p =>p.Id==request.ProductId,cancellationToken);if(product==null){thrownewKeyNotFoundException($"Product with Product ID '{request.ProductId}' was not found.");}// Check if the stock record already exists for the given ProductId and LocationvarexistingStock=await_context.Stocks.FirstOrDefaultAsync(s =>s.ProductId==request.ProductId&&s.Location==request.Location,cancellationToken);if(existingStock!=null){// If the stock record exists, update the quantityexistingStock.Quantity+=request.Quantity;_context.Stocks.Update(existingStock);}else{// If no stock record exists, create a new onevarnewStockEntry=newStock{ProductId=request.ProductId,Location=request.Location,Quantity=request.Quantity,};_context.Stocks.Add(newStockEntry);}// Save changes to the databaseawait_context.SaveChangesAsync(cancellationToken);returnUnit.Value;}}
Command: StockDispatchingCommand
publicrecordStockDispatchingCommand:IFusionCacheRefreshRequest<Unit>,IRequiresValidation{publicstringProductId{get;init;}=string.Empty;publicintQuantity{get;init;}publicstringLocation{get;init;}=string.Empty;publicIEnumerable<string>?Tags=>new[]{"stocks"};}publicclassStockDispatchingCommandHandler:IRequestHandler<StockDispatchingCommand,Unit>{privatereadonlyIApplicationDbContext_context;publicStockDispatchingCommandHandler(IApplicationDbContextcontext){_context=context??thrownewArgumentNullException(nameof(context));}publicasyncValueTask<Unit>Handle(StockDispatchingCommandrequest,CancellationTokencancellationToken){// Validate that the product existsvarproduct=await_context.Products.FirstOrDefaultAsync(p =>p.Id==request.ProductId,cancellationToken);if(product==null){thrownewKeyNotFoundException($"Product with Product ID '{request.ProductId}' was not found.");}// Check if the stock record exists for the given ProductId and LocationvarexistingStock=await_context.Stocks.FirstOrDefaultAsync(s =>s.ProductId==request.ProductId&&s.Location==request.Location,cancellationToken);if(existingStock==null){thrownewKeyNotFoundException($"No stock record found for Product ID '{request.ProductId}' at Location '{request.Location}'.");}// Validate that the stock quantity is sufficientif(existingStock.Quantity<request.Quantity){thrownewInvalidOperationException($"Insufficient stock quantity. Available: {existingStock.Quantity}, Requested: {request.Quantity}");}// Reduce the stock quantityexistingStock.Quantity-=request.Quantity;// Update the stock record_context.Stocks.Update(existingStock);// Save changes to the databaseawait_context.SaveChangesAsync(cancellationToken);returnUnit.Value;}}
Here's the continuation, explaining Step 3 in detail:
Step 3: Add and Register the Stock Endpoint in the CleanAspire.Api Project
In this step, we will implement the StockEndpointRegistrar in the CleanAspire.Api project. This class defines and registers API endpoints for managing stocks, enabling interaction with the system via HTTP requests. Below is the implementation and an explanation of its intent:
Code Explanation:
publicclassStockEndpointRegistrar:IEndpointRegistrar{privatereadonlyILogger<StockEndpointRegistrar>_logger;publicStockEndpointRegistrar(ILogger<StockEndpointRegistrar>logger){_logger=logger;}publicvoidRegisterRoutes(IEndpointRouteBuilderroutes){vargroup=routes.MapGroup("/stocks").WithTags("stocks").RequireAuthorization();// Dispatch stockgroup.MapPost("/dispatch",([FromServices]IMediatormediator,[FromBody]StockDispatchingCommandcommand)=>mediator.Send(command)).Produces<Unit>(StatusCodes.Status200OK).ProducesValidationProblem(StatusCodes.Status422UnprocessableEntity).ProducesProblem(StatusCodes.Status400BadRequest).ProducesProblem(StatusCodes.Status500InternalServerError).WithSummary("Dispatch stock").WithDescription("Dispatches a specified quantity of stock from a location.");// Receive stockgroup.MapPost("/receive",([FromServices]IMediatormediator,[FromBody]StockReceivingCommandcommand)=>mediator.Send(command)).Produces<Unit>(StatusCodes.Status200OK).ProducesValidationProblem(StatusCodes.Status422UnprocessableEntity).ProducesProblem(StatusCodes.Status400BadRequest).ProducesProblem(StatusCodes.Status500InternalServerError).WithSummary("Receive stock").WithDescription("Receives a specified quantity of stock into a location.");// Get stocks with paginationgroup.MapPost("/pagination",([FromServices]IMediatormediator,[FromBody]StocksWithPaginationQueryquery)=>mediator.Send(query)).Produces<PaginatedResult<StockDto>>(StatusCodes.Status200OK).ProducesProblem(StatusCodes.Status400BadRequest).ProducesProblem(StatusCodes.Status500InternalServerError).WithSummary("Get stocks with pagination").WithDescription("Returns a paginated list of stocks based on search keywords, page size, and sorting options.");}}
Intent of the Code:
MapGroup: Groups all stock-related endpoints under the /stocks path, allowing for consistent and logical URL structure.
Authorization Requirement: Ensures only authorized users can access these endpoints.
Endpoint Definitions:
Dispatch Stock: Handles the removal of stock from a specified location.
Receive Stock: Facilitates the addition of stock to a location.
Pagination Query: Returns a paginated list of stock records based on search criteria and sorting preferences.
Response Types:
Status200OK: Indicates a successful operation.
Status422UnprocessableEntity: Represents validation errors in input data.
Status400BadRequest and Status500InternalServerError: Handle general and server-side errors.
Documentation Features:
WithSummary and WithDescription: Provide concise summaries and detailed descriptions for API documentation, improving clarity and usability.
This implementation enables seamless interaction with the stock management system and adheres to best practices in designing RESTful APIs.
Step 4: Generate the Client Code Using Kiota in Visual Studio Code
In this step, we will generate the client code using Kiota in Visual Studio Code, enabling seamless interaction between front-end (or other external) applications and the CleanAspire API. By providing strongly typed methods and data models, the generated ClientApi reduces potential errors and simplifies development.
Below are the implementation steps and an explanation of the intent:
Implementation Steps
Build the CleanAspire.Api Project
This will generate the CleanAspire.Api.json file, which Kiota uses to create the client code.
Open Visual Studio Code and Load the CleanAspire.Api.json File
Navigate to the Kiota extension and select the CleanAspire.Api.json file as your API specification.
Click “Generate Client Code”
Specify the Client directory under the CleanAspire.ClientApp project as the output path.
Before generating, delete any existing code in the Client directory to avoid conflicts.
Explanation of Its Intent
By generating the ClientApi, you create a straightforward way for your applications to call and consume the CleanAspire API. This client library:
Provides strongly typed methods and data models, making API interaction more intuitive and less error-prone.
Reduces boilerplate code, accelerating development and simplifying maintenance.
Ensures consistency in how the API is accessed and utilized, promoting a robust and scalable integration.
Step 5: Add a Blazor Page for Stock Operations in the CleanAspire.ClientApp Project
In this step, we will add a new Blazor page to the CleanAspire.ClientApp project to facilitate various stock operations, such as searching, receiving, and dispatching. By leveraging the MudDataGrid component alongside the CleanAspire API, this page provides a seamless user experience for managing inventory. Below is the implementation and an explanation of its intent:
By adding this Blazor page, you empower users to perform essential stock operations directly from the front-end. Specifically, they can:
Browse and Search Stock: The MudDataGrid displays stock records with built-in pagination and sorting, while the search bar filters records based on keywords.
Receive Stock: The “Receiving” button opens a dialog to add incoming stock.
Dispatch Stock: The “LocalShipping” icon in each row opens a dialog to dispatch stock.
Keep Data in Sync: Upon successful receive or dispatch, the cache is cleared, and the DataGrid reloads to display updated information.
This approach streamlines inventory management in a user-friendly manner, leveraging the underlying CleanAspire API for data retrieval and updates.
The text was updated successfully, but these errors were encountered:
Step 1: Add the
Stock
Entity in the CleanAspire.Domain ProjectTo begin, we’ll define the
Stock
entity in the CleanAspire.Domain project, under theEntities
directory. This entity will represent a stock record, containing details about the associated product, quantity, and location. We’ll also add aDbSet<Stock>
in the application database context to enable querying and savingStock
records to the database.Here’s how the
Stock
entity should look:Explanation of Properties
ProductId
:Represents the unique identifier of the associated product. It is nullable (
string?
) to allow flexibility during object creation.Product
:A navigation property linking the
Stock
entity to theProduct
entity, enabling easy access to related product details.Quantity
:Stores the current quantity of the stock.
Location
:Represents the physical location (e.g., warehouse or store) where the stock is stored. It defaults to an empty string.
Step 2: Add Stock Features in the CleanAspire.Application Project
In this step, we will add features for managing
Stock
in the CleanAspire.Application project. These features include queries and commands for handling stock-related operations, such as inventory queries and stock receiving/dispatching. Create the following structure under theFeatures
directory:Stocks\Queries
Stocks\Commands
Query: StocksWithPaginationQuery
Command: StockReceivingCommand
Command: StockDispatchingCommand
Here's the continuation, explaining Step 3 in detail:
Step 3: Add and Register the Stock Endpoint in the CleanAspire.Api Project
In this step, we will implement the
StockEndpointRegistrar
in the CleanAspire.Api project. This class defines and registers API endpoints for managing stocks, enabling interaction with the system via HTTP requests. Below is the implementation and an explanation of its intent:Code Explanation:
Intent of the Code:
MapGroup
: Groups all stock-related endpoints under the/stocks
path, allowing for consistent and logical URL structure.Authorization Requirement: Ensures only authorized users can access these endpoints.
Endpoint Definitions:
Response Types:
Status200OK
: Indicates a successful operation.Status422UnprocessableEntity
: Represents validation errors in input data.Status400BadRequest
andStatus500InternalServerError
: Handle general and server-side errors.Documentation Features:
WithSummary
andWithDescription
: Provide concise summaries and detailed descriptions for API documentation, improving clarity and usability.This implementation enables seamless interaction with the stock management system and adheres to best practices in designing RESTful APIs.
Step 4: Generate the Client Code Using Kiota in Visual Studio Code
In this step, we will generate the client code using Kiota in Visual Studio Code, enabling seamless interaction between front-end (or other external) applications and the CleanAspire API. By providing strongly typed methods and data models, the generated ClientApi reduces potential errors and simplifies development.
Below are the implementation steps and an explanation of the intent:
Implementation Steps
Build the CleanAspire.Api Project
CleanAspire.Api.json
file, which Kiota uses to create the client code.Open Visual Studio Code and Load the
CleanAspire.Api.json
FileCleanAspire.Api.json
file as your API specification.Click “Generate Client Code”
Client
directory under theCleanAspire.ClientApp
project as the output path.Client
directory to avoid conflicts.Explanation of Its Intent
By generating the ClientApi, you create a straightforward way for your applications to call and consume the CleanAspire API. This client library:
Step 5: Add a Blazor Page for Stock Operations in the CleanAspire.ClientApp Project
In this step, we will add a new Blazor page to the
CleanAspire.ClientApp
project to facilitate various stock operations, such as searching, receiving, and dispatching. By leveraging theMudDataGrid
component alongside the CleanAspire API, this page provides a seamless user experience for managing inventory. Below is the implementation and an explanation of its intent:Implementation
Below is the dialog component that the
Receive
andDispatch
methods open when creating or updating stock records:Explanation of Its Intent
By adding this Blazor page, you empower users to perform essential stock operations directly from the front-end. Specifically, they can:
MudDataGrid
displays stock records with built-in pagination and sorting, while the search bar filters records based on keywords.This approach streamlines inventory management in a user-friendly manner, leveraging the underlying CleanAspire API for data retrieval and updates.
The text was updated successfully, but these errors were encountered: