feat: Implement Category, Item, UOM, Vendor, and Warehouse services with CRUD operations
- Added CategoryService for managing categories with listing, tree structure, and creation functionalities. - Introduced ItemService for item management, including listing, detail retrieval, creation, updating, and status management. - Created UomService for handling unit of measure operations, including listing and creation. - Developed VendorService for vendor management, supporting listing, detail retrieval, creation, updating, and status management. - Implemented WarehouseService for warehouse and bin management, including listing warehouses, creating warehouses, and managing bins within warehouses. - Added interfaces for each service to define the contract for service implementations. - Generated Entity Framework Core model snapshot for database migrations.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ERPCore.Dtos.Categories;
|
||||
|
||||
/// <summary>Flat category resource (docs/11-BACKEND-PHASE1.md §2.3).</summary>
|
||||
public sealed record CategoryDto(long CategoryId, string Name, long? ParentId);
|
||||
|
||||
/// <summary>Nested category node for <c>GET /categories?tree=true</c>.</summary>
|
||||
public sealed record CategoryTreeDto(long CategoryId, string Name, long? ParentId, IReadOnlyList<CategoryTreeDto> Children);
|
||||
|
||||
public sealed class CreateCategoryRequest
|
||||
{
|
||||
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
|
||||
public long? ParentId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace ERPCore.Dtos.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Shared paging/sorting query binding (docs/11-BACKEND-PHASE1.md §1.5). Page size
|
||||
/// is clamped to <see cref="MaxPageSize"/> to enforce pagination bounds
|
||||
/// (02-SECURITY B.6). Bind from the query string on list endpoints.
|
||||
/// </summary>
|
||||
public class PageQuery
|
||||
{
|
||||
public const int MaxPageSize = 200;
|
||||
public const int DefaultPageSize = 20;
|
||||
|
||||
private int _page = 1;
|
||||
private int _pageSize = DefaultPageSize;
|
||||
|
||||
/// <summary>1-based page number (default 1).</summary>
|
||||
public int Page
|
||||
{
|
||||
get => _page;
|
||||
set => _page = value < 1 ? 1 : value;
|
||||
}
|
||||
|
||||
/// <summary>Page size (default 20, clamped to 1..200).</summary>
|
||||
public int PageSize
|
||||
{
|
||||
get => _pageSize;
|
||||
set => _pageSize = value < 1 ? DefaultPageSize : Math.Min(value, MaxPageSize);
|
||||
}
|
||||
|
||||
/// <summary>Free-text search term (<c>q</c>).</summary>
|
||||
public string? Q { get; set; }
|
||||
|
||||
/// <summary>Sort spec, e.g. <c>name</c> or <c>-createdAt</c>.</summary>
|
||||
public string? Sort { get; set; }
|
||||
|
||||
public int Skip => (Page - 1) * PageSize;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace ERPCore.Dtos.Common;
|
||||
|
||||
/// <summary>
|
||||
/// List envelope matching docs/11-BACKEND-PHASE1.md §1.4:
|
||||
/// <c>{ "items": [...], "pagination": { page, pageSize, totalItems, totalPages } }</c>.
|
||||
/// </summary>
|
||||
public sealed record PagedResponse<T>(IReadOnlyList<T> Items, PaginationDto Pagination)
|
||||
{
|
||||
public static PagedResponse<T> Create(IReadOnlyList<T> items, int page, int pageSize, int totalItems)
|
||||
{
|
||||
var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(totalItems / (double)pageSize);
|
||||
return new PagedResponse<T>(items, new PaginationDto(page, pageSize, totalItems, totalPages));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pagination metadata block (docs/11 §1.4).</summary>
|
||||
public sealed record PaginationDto(int Page, int PageSize, int TotalItems, int TotalPages);
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ERPCore.Domain.Enums;
|
||||
|
||||
namespace ERPCore.Dtos.Items;
|
||||
|
||||
// Response DTOs (docs/11-BACKEND-PHASE1.md §2.1) --------------------------------
|
||||
|
||||
/// <summary>Row shape for <c>GET /items</c>.</summary>
|
||||
public sealed record ItemListItemDto(
|
||||
long ItemId, string Sku, string Name, long CategoryId, long BaseUomId,
|
||||
long? DefaultVendorId, ItemType ItemType, TrackingMode TrackingMode,
|
||||
string? TaxClass, EntityStatus Status);
|
||||
|
||||
/// <summary>A single per-warehouse reorder policy row.</summary>
|
||||
public sealed record ItemReorderDto(long WarehouseId, decimal ReorderPoint, decimal ReorderQty);
|
||||
|
||||
/// <summary>Full item resource for <c>GET /items/{id}</c> and create/update responses.</summary>
|
||||
public sealed record ItemDetailDto(
|
||||
long ItemId, string Sku, string Name, string? Description, long CategoryId,
|
||||
long BaseUomId, long? DefaultVendorId, ItemType ItemType, TrackingMode TrackingMode,
|
||||
string? TaxClass, EntityStatus Status, IReadOnlyList<ItemReorderDto> Reorder,
|
||||
DateTime CreatedAt, DateTime? UpdatedAt);
|
||||
|
||||
/// <summary>UOM conversion row (docs/11 §2.2).</summary>
|
||||
public sealed record UomConversionDto(long ConversionId, long FromUom, long ToUom, decimal Factor);
|
||||
|
||||
/// <summary>Response body for <c>PUT /items/{id}/uom-conversions</c>.</summary>
|
||||
public sealed record ItemUomConversionsDto(long ItemId, long BaseUomId, IReadOnlyList<UomConversionDto> Conversions);
|
||||
|
||||
/// <summary>Response body for <c>PUT /items/{id}/reorder</c>.</summary>
|
||||
public sealed record ItemReorderSettingsDto(IReadOnlyList<ItemReorderDto> Settings);
|
||||
|
||||
// Request DTOs — narrow: server-controlled fields (status, ids, timestamps)
|
||||
// are intentionally excluded to prevent over-posting (02-SECURITY B.6 / C.1). ----
|
||||
|
||||
public sealed class CreateItemRequest
|
||||
{
|
||||
[Required, StringLength(50)] public string Sku { get; set; } = string.Empty;
|
||||
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
|
||||
[StringLength(1000)] public string? Description { get; set; }
|
||||
[Required] public long CategoryId { get; set; }
|
||||
[Required] public long BaseUomId { get; set; }
|
||||
public long? DefaultVendorId { get; set; }
|
||||
[Required, EnumDataType(typeof(ItemType))] public ItemType ItemType { get; set; }
|
||||
[EnumDataType(typeof(TrackingMode))] public TrackingMode TrackingMode { get; set; } = TrackingMode.None;
|
||||
[StringLength(20)] public string? TaxClass { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UpdateItemRequest
|
||||
{
|
||||
[Required, StringLength(50)] public string Sku { get; set; } = string.Empty;
|
||||
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
|
||||
[StringLength(1000)] public string? Description { get; set; }
|
||||
[Required] public long CategoryId { get; set; }
|
||||
[Required] public long BaseUomId { get; set; }
|
||||
public long? DefaultVendorId { get; set; }
|
||||
[Required, EnumDataType(typeof(ItemType))] public ItemType ItemType { get; set; }
|
||||
[EnumDataType(typeof(TrackingMode))] public TrackingMode TrackingMode { get; set; } = TrackingMode.None;
|
||||
[StringLength(20)] public string? TaxClass { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UpdateItemStatusRequest
|
||||
{
|
||||
[Required, EnumDataType(typeof(EntityStatus))] public EntityStatus Status { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ReorderSettingInput
|
||||
{
|
||||
[Required] public long WarehouseId { get; set; }
|
||||
[Range(0, double.MaxValue)] public decimal ReorderPoint { get; set; }
|
||||
[Range(0, double.MaxValue)] public decimal ReorderQty { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UpdateReorderRequest
|
||||
{
|
||||
[Required, MinLength(1)] public List<ReorderSettingInput> Settings { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class UomConversionInput
|
||||
{
|
||||
[Required] public long FromUom { get; set; }
|
||||
[Required] public long ToUom { get; set; }
|
||||
[Range(0.000001, double.MaxValue)] public decimal Factor { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UpdateUomConversionsRequest
|
||||
{
|
||||
[Required, MinLength(1)] public List<UomConversionInput> Conversions { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ERPCore.Dtos.Uoms;
|
||||
|
||||
/// <summary>UOM resource (docs/11-BACKEND-PHASE1.md §2.2).</summary>
|
||||
public sealed record UomDto(long UomId, string Name);
|
||||
|
||||
public sealed class CreateUomRequest
|
||||
{
|
||||
[Required, StringLength(50)] public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ERPCore.Domain.Enums;
|
||||
|
||||
namespace ERPCore.Dtos.Vendors;
|
||||
|
||||
/// <summary>Vendor resource (docs/11-BACKEND-PHASE1.md §2.4).</summary>
|
||||
public sealed record VendorDto(
|
||||
long VendorId, string Code, string Name, string? Terms, string? TaxReg,
|
||||
string Currency, EntityStatus Status, DateTime CreatedAt, DateTime? UpdatedAt);
|
||||
|
||||
public sealed class CreateVendorRequest
|
||||
{
|
||||
[Required, StringLength(50)] public string Code { get; set; } = string.Empty;
|
||||
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
|
||||
[StringLength(50)] public string? Terms { get; set; }
|
||||
[StringLength(50)] public string? TaxReg { get; set; }
|
||||
[Required, StringLength(3, MinimumLength = 3)] public string Currency { get; set; } = "LKR";
|
||||
}
|
||||
|
||||
public sealed class UpdateVendorRequest
|
||||
{
|
||||
[Required, StringLength(50)] public string Code { get; set; } = string.Empty;
|
||||
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
|
||||
[StringLength(50)] public string? Terms { get; set; }
|
||||
[StringLength(50)] public string? TaxReg { get; set; }
|
||||
[Required, StringLength(3, MinimumLength = 3)] public string Currency { get; set; } = "LKR";
|
||||
}
|
||||
|
||||
public sealed class UpdateVendorStatusRequest
|
||||
{
|
||||
[Required, EnumDataType(typeof(EntityStatus))] public EntityStatus Status { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ERPCore.Dtos.Warehouses;
|
||||
|
||||
/// <summary>Warehouse resource (docs/11-BACKEND-PHASE1.md §2.5).</summary>
|
||||
public sealed record WarehouseDto(long WarehouseId, string Code, string Name);
|
||||
|
||||
/// <summary>Bin/location resource (docs/11 §2.5).</summary>
|
||||
public sealed record BinDto(long BinId, long WarehouseId, string Code, string? BinType);
|
||||
|
||||
public sealed class CreateWarehouseRequest
|
||||
{
|
||||
[Required, StringLength(50)] public string Code { get; set; } = string.Empty;
|
||||
[Required, StringLength(200)] public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class CreateBinRequest
|
||||
{
|
||||
[Required, StringLength(50)] public string Code { get; set; } = string.Empty;
|
||||
[StringLength(50)] public string? BinType { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user