From 22f86451e357686c8286f052cd20cc6444f4930b Mon Sep 17 00:00:00 2001 From: ImanThiyanga Date: Tue, 14 Jul 2026 10:20:38 +0530 Subject: [PATCH 1/3] feat: Add new services and interfaces for GRN, Purchase Return, Reason Code, Reorder, Stock, and Transfer functionalities - Implemented IGrnService for managing goods receipts including retrieval, creation, and confirmation. - Created IPurchaseReturnService for handling purchase return operations. - Added IReasonCodeService for managing reason codes with listing and creation capabilities. - Developed IReorderService for fetching reorder alerts and creating suggested requisitions. - Introduced IStockMutator for applying stock changes and posting ledger entries. - Established IStockService for stock inquiries, ledger retrieval, and valuation. - Created ITransferService for managing inter-warehouse transfers including dispatch and receiving operations. - Implemented PurchaseReturnService to handle purchase return logic and stock adjustments. - Developed ReasonCodeService for listing and creating reason codes. - Created ReorderService for fetching reorder alerts and generating requisitions. - Implemented FifoCostingService for FIFO cost-layer management and ledger writing. - Developed StockMutator for applying stock deltas and posting ledger entries. - Created StockService for stock inquiries and ledger management. - Implemented TransferService for managing inter-warehouse transfers with dispatch and receive functionalities. --- .../Controllers/AuditLogsController.cs | 26 + Backend/ERPCore/Controllers/GrnsController.cs | 52 + .../Controllers/JournalEntriesController.cs | 24 + .../Controllers/PurchaseReturnsController.cs | 26 + .../Controllers/ReasonCodesController.cs | 31 + .../Controllers/StockAdjustmentsController.cs | 26 + .../ERPCore/Controllers/StockController.cs | 56 + .../Controllers/StockCountsController.cs | 49 + .../Controllers/StockTransfersController.cs | 49 + Backend/ERPCore/Domain/Entities/AuditLog.cs | 23 + Backend/ERPCore/Domain/Entities/Batch.cs | 16 + Backend/ERPCore/Domain/Entities/Grn.cs | 37 + Backend/ERPCore/Domain/Entities/GrnLine.cs | 37 + .../Domain/Entities/JournalEntryStub.cs | 18 + .../ERPCore/Domain/Entities/PurchaseReturn.cs | 32 + .../Domain/Entities/PurchaseReturnLine.cs | 21 + Backend/ERPCore/Domain/Entities/ReasonCode.cs | 15 + Backend/ERPCore/Domain/Entities/Serial.cs | 16 + .../Domain/Entities/StockAdjustment.cs | 31 + .../Domain/Entities/StockAdjustmentLine.cs | 23 + Backend/ERPCore/Domain/Entities/StockCount.cs | 29 + .../ERPCore/Domain/Entities/StockCountLine.cs | 22 + Backend/ERPCore/Domain/Entities/StockLayer.cs | 33 + .../ERPCore/Domain/Entities/StockLedger.cs | 32 + .../ERPCore/Domain/Entities/StockTransfer.cs | 31 + .../Domain/Entities/StockTransferLine.cs | 35 + .../ERPCore/Domain/Enums/AdjustmentStatus.cs | 13 + Backend/ERPCore/Domain/Enums/AuditAction.cs | 9 + Backend/ERPCore/Domain/Enums/CountStatus.cs | 9 + Backend/ERPCore/Domain/Enums/CountType.cs | 8 + Backend/ERPCore/Domain/Enums/Direction.cs | 8 + Backend/ERPCore/Domain/Enums/GrnStatus.cs | 9 + Backend/ERPCore/Domain/Enums/HoldStatus.cs | 12 + Backend/ERPCore/Domain/Enums/ReasonContext.cs | 9 + Backend/ERPCore/Domain/Enums/ReturnStatus.cs | 8 + .../ERPCore/Domain/Enums/TransferStatus.cs | 10 + Backend/ERPCore/Dtos/Audit/AuditDtos.cs | 12 + Backend/ERPCore/Dtos/Grn/GrnDtos.cs | 63 + .../Dtos/Procurement/PurchaseReturnDtos.cs | 31 + .../ERPCore/Dtos/Reference/ReasonCodeDtos.cs | 14 + Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs | 31 + Backend/ERPCore/Dtos/Stock/CountDtos.cs | 33 + Backend/ERPCore/Dtos/Stock/ReorderDtos.cs | 6 + Backend/ERPCore/Dtos/Stock/StockDtos.cs | 22 + Backend/ERPCore/Dtos/Stock/TransferDtos.cs | 52 + .../Infra/Persistence/Auditing/AuditScribe.cs | 98 + .../Configurations/AuditConfiguration.cs | 41 + .../BatchSerialConfiguration.cs | 43 + .../Configurations/GrnConfiguration.cs | 50 + .../PurchaseReturnConfiguration.cs | 40 + .../Configurations/ReasonCodeConfiguration.cs | 20 + .../StockAdjustmentConfiguration.cs | 42 + .../Configurations/StockConfiguration.cs | 60 + .../Configurations/StockCountConfiguration.cs | 44 + .../StockTransferConfiguration.cs | 47 + .../ERPCore/Infra/Persistence/DataSeeder.cs | 45 + .../ERPCore/Infra/Persistence/ErpDbContext.cs | 74 +- .../20260713054124_AddStockAndGrn.Designer.cs | 1480 +++++++++++ .../20260713054124_AddStockAndGrn.cs | 434 ++++ ...713061141_AddStockTransactions.Designer.cs | 1847 ++++++++++++++ .../20260713061141_AddStockTransactions.cs | 337 +++ ...0713105512_AddCountsAndReturns.Designer.cs | 2134 ++++++++++++++++ .../20260713105512_AddCountsAndReturns.cs | 253 ++ ...60713121825_AddAuditAndJournal.Designer.cs | 2222 +++++++++++++++++ .../20260713121825_AddAuditAndJournal.cs | 87 + .../Migrations/ErpDbContextModelSnapshot.cs | 1235 +++++++++ Backend/ERPCore/Program.cs | 25 + Backend/ERPCore/Services/AdjustmentService.cs | 104 + Backend/ERPCore/Services/AuditService.cs | 58 + Backend/ERPCore/Services/CountService.cs | 181 ++ Backend/ERPCore/Services/GrnService.cs | 357 +++ .../Services/Interfaces/IAdjustmentService.cs | 9 + .../Services/Interfaces/IAuditService.cs | 17 + .../Services/Interfaces/ICountService.cs | 12 + .../Interfaces/IFifoCostingService.cs | 46 + .../Services/Interfaces/IGrnService.cs | 15 + .../Interfaces/IPurchaseReturnService.cs | 9 + .../Services/Interfaces/IReasonCodeService.cs | 12 + .../Services/Interfaces/IReorderService.cs | 12 + .../Services/Interfaces/IStockMutator.cs | 21 + .../Services/Interfaces/IStockService.cs | 15 + .../Services/Interfaces/ITransferService.cs | 16 + .../ERPCore/Services/PurchaseReturnService.cs | 115 + Backend/ERPCore/Services/ReasonCodeService.cs | 50 + Backend/ERPCore/Services/ReorderService.cs | 59 + .../Services/Stock/FifoCostingService.cs | 183 ++ .../ERPCore/Services/Stock/StockMutator.cs | 77 + .../ERPCore/Services/Stock/StockService.cs | 76 + Backend/ERPCore/Services/TransferService.cs | 196 ++ Backend/PROGRESS.md | 68 +- 90 files changed, 13496 insertions(+), 19 deletions(-) create mode 100644 Backend/ERPCore/Controllers/AuditLogsController.cs create mode 100644 Backend/ERPCore/Controllers/GrnsController.cs create mode 100644 Backend/ERPCore/Controllers/JournalEntriesController.cs create mode 100644 Backend/ERPCore/Controllers/PurchaseReturnsController.cs create mode 100644 Backend/ERPCore/Controllers/ReasonCodesController.cs create mode 100644 Backend/ERPCore/Controllers/StockAdjustmentsController.cs create mode 100644 Backend/ERPCore/Controllers/StockController.cs create mode 100644 Backend/ERPCore/Controllers/StockCountsController.cs create mode 100644 Backend/ERPCore/Controllers/StockTransfersController.cs create mode 100644 Backend/ERPCore/Domain/Entities/AuditLog.cs create mode 100644 Backend/ERPCore/Domain/Entities/Batch.cs create mode 100644 Backend/ERPCore/Domain/Entities/Grn.cs create mode 100644 Backend/ERPCore/Domain/Entities/GrnLine.cs create mode 100644 Backend/ERPCore/Domain/Entities/JournalEntryStub.cs create mode 100644 Backend/ERPCore/Domain/Entities/PurchaseReturn.cs create mode 100644 Backend/ERPCore/Domain/Entities/PurchaseReturnLine.cs create mode 100644 Backend/ERPCore/Domain/Entities/ReasonCode.cs create mode 100644 Backend/ERPCore/Domain/Entities/Serial.cs create mode 100644 Backend/ERPCore/Domain/Entities/StockAdjustment.cs create mode 100644 Backend/ERPCore/Domain/Entities/StockAdjustmentLine.cs create mode 100644 Backend/ERPCore/Domain/Entities/StockCount.cs create mode 100644 Backend/ERPCore/Domain/Entities/StockCountLine.cs create mode 100644 Backend/ERPCore/Domain/Entities/StockLayer.cs create mode 100644 Backend/ERPCore/Domain/Entities/StockLedger.cs create mode 100644 Backend/ERPCore/Domain/Entities/StockTransfer.cs create mode 100644 Backend/ERPCore/Domain/Entities/StockTransferLine.cs create mode 100644 Backend/ERPCore/Domain/Enums/AdjustmentStatus.cs create mode 100644 Backend/ERPCore/Domain/Enums/AuditAction.cs create mode 100644 Backend/ERPCore/Domain/Enums/CountStatus.cs create mode 100644 Backend/ERPCore/Domain/Enums/CountType.cs create mode 100644 Backend/ERPCore/Domain/Enums/Direction.cs create mode 100644 Backend/ERPCore/Domain/Enums/GrnStatus.cs create mode 100644 Backend/ERPCore/Domain/Enums/HoldStatus.cs create mode 100644 Backend/ERPCore/Domain/Enums/ReasonContext.cs create mode 100644 Backend/ERPCore/Domain/Enums/ReturnStatus.cs create mode 100644 Backend/ERPCore/Domain/Enums/TransferStatus.cs create mode 100644 Backend/ERPCore/Dtos/Audit/AuditDtos.cs create mode 100644 Backend/ERPCore/Dtos/Grn/GrnDtos.cs create mode 100644 Backend/ERPCore/Dtos/Procurement/PurchaseReturnDtos.cs create mode 100644 Backend/ERPCore/Dtos/Reference/ReasonCodeDtos.cs create mode 100644 Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs create mode 100644 Backend/ERPCore/Dtos/Stock/CountDtos.cs create mode 100644 Backend/ERPCore/Dtos/Stock/ReorderDtos.cs create mode 100644 Backend/ERPCore/Dtos/Stock/StockDtos.cs create mode 100644 Backend/ERPCore/Dtos/Stock/TransferDtos.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Auditing/AuditScribe.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/AuditConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/BatchSerialConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/GrnConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/PurchaseReturnConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/ReasonCodeConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/StockAdjustmentConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/StockConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/StockCountConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/StockTransferConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/DataSeeder.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.Designer.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.Designer.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.Designer.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.Designer.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.cs create mode 100644 Backend/ERPCore/Services/AdjustmentService.cs create mode 100644 Backend/ERPCore/Services/AuditService.cs create mode 100644 Backend/ERPCore/Services/CountService.cs create mode 100644 Backend/ERPCore/Services/GrnService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IAdjustmentService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IAuditService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/ICountService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IGrnService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IPurchaseReturnService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IReasonCodeService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IReorderService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IStockMutator.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IStockService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/ITransferService.cs create mode 100644 Backend/ERPCore/Services/PurchaseReturnService.cs create mode 100644 Backend/ERPCore/Services/ReasonCodeService.cs create mode 100644 Backend/ERPCore/Services/ReorderService.cs create mode 100644 Backend/ERPCore/Services/Stock/FifoCostingService.cs create mode 100644 Backend/ERPCore/Services/Stock/StockMutator.cs create mode 100644 Backend/ERPCore/Services/Stock/StockService.cs create mode 100644 Backend/ERPCore/Services/TransferService.cs diff --git a/Backend/ERPCore/Controllers/AuditLogsController.cs b/Backend/ERPCore/Controllers/AuditLogsController.cs new file mode 100644 index 0000000..a3f9750 --- /dev/null +++ b/Backend/ERPCore/Controllers/AuditLogsController.cs @@ -0,0 +1,26 @@ +using ERPCore.Dtos.Audit; +using ERPCore.Dtos.Common; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// +/// Read-only audit trail (FR-X-02; auditor role). Extends the documented §11 API — +/// the audit trail is required (AR-01 compensating control) and read access is the +/// only way to consume it. +/// +[Route("api/v1/audit-logs")] +public sealed class AuditLogsController : ApiControllerBase +{ + private readonly IAuditService _audit; + + public AuditLogsController(IAuditService audit) => _audit = audit; + + [HttpGet] + [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] + public async Task>> List( + [FromQuery] string? entityType, [FromQuery] long? entityId, [FromQuery] long? userId, + [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, [FromQuery] PageQuery query, CancellationToken ct) + => Ok(await _audit.ListLogsAsync(entityType, entityId, userId, from, to, query, ct)); +} diff --git a/Backend/ERPCore/Controllers/GrnsController.cs b/Backend/ERPCore/Controllers/GrnsController.cs new file mode 100644 index 0000000..0903a5a --- /dev/null +++ b/Backend/ERPCore/Controllers/GrnsController.cs @@ -0,0 +1,52 @@ +using ERPCore.Dtos.Grn; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// Goods-receipt endpoints (docs/11 §4). +[Route("api/v1/grns")] +public sealed class GrnsController : ApiControllerBase +{ + private readonly IGrnService _grns; + + public GrnsController(IGrnService grns) => _grns = grns; + + [HttpGet("{grnId:long}")] + [ProducesResponseType(typeof(GrnDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetById(long grnId, CancellationToken ct) + { + var dto = await _grns.GetAsync(grnId, ct); + return dto is null ? NotFound() : Ok(dto); + } + + /// Create a Draft GRN against a PO or direct. Cost is PO-derived for PO lines. + [HttpPost] + [ProducesResponseType(typeof(GrnDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task> Create([FromBody] CreateGrnRequest request, CancellationToken ct) + { + var dto = await _grns.CreateAsync(request, ct); + return Created($"/api/v1/grns/{dto.GrnId}", dto); + } + + /// Confirm: create FIFO layers + inbound ledger + update PO receipts (single UoW txn). + [HttpPost("{grnId:long}/confirm")] + [ProducesResponseType(typeof(GrnConfirmResultDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task> Confirm( + long grnId, [FromHeader(Name = "Idempotency-Key")] string? idempotencyKey, CancellationToken ct) + => Ok(await _grns.ConfirmAsync(grnId, idempotencyKey, ct)); + + /// Release or reject an inspection-hold line (FR-GRN-05). + [HttpPost("{grnId:long}/lines/{grnLineId:long}/release")] + [ProducesResponseType(typeof(ReleaseLineResultDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task> Release( + long grnId, long grnLineId, [FromBody] ReleaseLineRequest request, CancellationToken ct) + => Ok(await _grns.ReleaseLineAsync(grnId, grnLineId, request.Action, ct)); +} diff --git a/Backend/ERPCore/Controllers/JournalEntriesController.cs b/Backend/ERPCore/Controllers/JournalEntriesController.cs new file mode 100644 index 0000000..a2d500d --- /dev/null +++ b/Backend/ERPCore/Controllers/JournalEntriesController.cs @@ -0,0 +1,24 @@ +using ERPCore.Dtos.Audit; +using ERPCore.Dtos.Common; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// +/// Read-only GL-ready journal stubs (FR-STK-13; consumed by the Accounting phase). +/// Data only — no posting in Phase 1. +/// +[Route("api/v1/journal-entries")] +public sealed class JournalEntriesController : ApiControllerBase +{ + private readonly IAuditService _audit; + + public JournalEntriesController(IAuditService audit) => _audit = audit; + + [HttpGet] + [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] + public async Task>> List( + [FromQuery] string? sourceDocType, [FromQuery] long? sourceDocId, [FromQuery] PageQuery query, CancellationToken ct) + => Ok(await _audit.ListJournalAsync(sourceDocType, sourceDocId, query, ct)); +} diff --git a/Backend/ERPCore/Controllers/PurchaseReturnsController.cs b/Backend/ERPCore/Controllers/PurchaseReturnsController.cs new file mode 100644 index 0000000..9f59a56 --- /dev/null +++ b/Backend/ERPCore/Controllers/PurchaseReturnsController.cs @@ -0,0 +1,26 @@ +using ERPCore.Dtos.Procurement; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// Purchase-return endpoints (docs/11 §3.4). +[Route("api/v1/purchase-returns")] +public sealed class PurchaseReturnsController : ApiControllerBase +{ + private readonly IPurchaseReturnService _returns; + + public PurchaseReturnsController(IPurchaseReturnService returns) => _returns = returns; + + /// Create + auto-post a return (outbound movement). 409 if return exceeds available stock. + [HttpPost] + [ProducesResponseType(typeof(PurchaseReturnDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task> Create([FromBody] CreatePurchaseReturnRequest request, CancellationToken ct) + { + var dto = await _returns.CreateAsync(request, ct); + return Created($"/api/v1/purchase-returns/{dto.ReturnId}", dto); + } +} diff --git a/Backend/ERPCore/Controllers/ReasonCodesController.cs b/Backend/ERPCore/Controllers/ReasonCodesController.cs new file mode 100644 index 0000000..e67aa32 --- /dev/null +++ b/Backend/ERPCore/Controllers/ReasonCodesController.cs @@ -0,0 +1,31 @@ +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Reference; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// Reason-code reference endpoints (docs/11 §6). +[Route("api/v1/reason-codes")] +public sealed class ReasonCodesController : ApiControllerBase +{ + private readonly IReasonCodeService _codes; + + public ReasonCodesController(IReasonCodeService codes) => _codes = codes; + + [HttpGet] + [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] + public async Task>> List( + [FromQuery] ReasonContext? context, [FromQuery] PageQuery query, CancellationToken ct) + => Ok(await _codes.ListAsync(context, query, ct)); + + [HttpPost] + [ProducesResponseType(typeof(ReasonCodeDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task> Create([FromBody] CreateReasonCodeRequest request, CancellationToken ct) + { + var dto = await _codes.CreateAsync(request, ct); + return Created($"/api/v1/reason-codes/{dto.ReasonCodeId}", dto); + } +} diff --git a/Backend/ERPCore/Controllers/StockAdjustmentsController.cs b/Backend/ERPCore/Controllers/StockAdjustmentsController.cs new file mode 100644 index 0000000..7952ba6 --- /dev/null +++ b/Backend/ERPCore/Controllers/StockAdjustmentsController.cs @@ -0,0 +1,26 @@ +using ERPCore.Dtos.Stock; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// Stock-adjustment endpoints (docs/11 §5.5). +[Route("api/v1/stock-adjustments")] +public sealed class StockAdjustmentsController : ApiControllerBase +{ + private readonly IAdjustmentService _adjustments; + + public StockAdjustmentsController(IAdjustmentService adjustments) => _adjustments = adjustments; + + /// Create + auto-post an adjustment (mandatory reason code; decrease FIFO-consumes). + [HttpPost] + [ProducesResponseType(typeof(AdjustmentDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task> Create([FromBody] CreateAdjustmentRequest request, CancellationToken ct) + { + var dto = await _adjustments.CreateAsync(request, ct); + return Created($"/api/v1/stock-adjustments/{dto.AdjustmentId}", dto); + } +} diff --git a/Backend/ERPCore/Controllers/StockController.cs b/Backend/ERPCore/Controllers/StockController.cs new file mode 100644 index 0000000..3124bd3 --- /dev/null +++ b/Backend/ERPCore/Controllers/StockController.cs @@ -0,0 +1,56 @@ +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Procurement; +using ERPCore.Dtos.Stock; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// Stock enquiry, ledger, valuation and reorder-alert endpoints (docs/11 §5.1–5.3, §5.7). +[Route("api/v1/stock")] +public sealed class StockController : ApiControllerBase +{ + private readonly IStockService _stock; + private readonly IReorderService _reorder; + + public StockController(IStockService stock, IReorderService reorder) + { + _stock = stock; + _reorder = reorder; + } + + [HttpGet("on-hand")] + [ProducesResponseType(typeof(StockOnHandDto), StatusCodes.Status200OK)] + public async Task> OnHand([FromQuery] long itemId, [FromQuery] long warehouseId, CancellationToken ct) + => Ok(await _stock.GetOnHandAsync(itemId, warehouseId, ct)); + + [HttpGet("ledger")] + [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] + public async Task>> Ledger( + [FromQuery] long? itemId, [FromQuery] long? warehouseId, + [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, [FromQuery] PageQuery query, CancellationToken ct) + => Ok(await _stock.GetLedgerAsync(itemId, warehouseId, from, to, query, ct)); + + [HttpGet("valuation")] + [ProducesResponseType(typeof(StockValuationDto), StatusCodes.Status200OK)] + public async Task> Valuation([FromQuery] long itemId, [FromQuery] long warehouseId, CancellationToken ct) + => Ok(await _stock.GetValuationAsync(itemId, warehouseId, ct)); + + /// Items at/below their reorder point (FR-STK-10), computed on read. + [HttpGet("reorder-alerts")] + [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] + public async Task>> ReorderAlerts( + [FromQuery] long? warehouseId, [FromQuery] PageQuery query, CancellationToken ct) + => Ok(await _reorder.GetAlertsAsync(warehouseId, query, ct)); + + /// Create a draft requisition for an item's suggested reorder quantity. + [HttpPost("reorder-alerts/{itemId:long}/requisition")] + [ProducesResponseType(typeof(RequisitionDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task> SuggestRequisition( + long itemId, [FromQuery] long warehouseId, CancellationToken ct) + { + var dto = await _reorder.CreateSuggestedRequisitionAsync(itemId, warehouseId, ct); + return Created($"/api/v1/requisitions/{dto.RequisitionId}", dto); + } +} diff --git a/Backend/ERPCore/Controllers/StockCountsController.cs b/Backend/ERPCore/Controllers/StockCountsController.cs new file mode 100644 index 0000000..e1c7bed --- /dev/null +++ b/Backend/ERPCore/Controllers/StockCountsController.cs @@ -0,0 +1,49 @@ +using ERPCore.Dtos.Stock; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// Stock-count endpoints (docs/11 §5.6). +[Route("api/v1/stock-counts")] +public sealed class StockCountsController : ApiControllerBase +{ + private readonly ICountService _counts; + + public StockCountsController(ICountService counts) => _counts = counts; + + [HttpGet("{countId:long}")] + [ProducesResponseType(typeof(CountDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetById(long countId, CancellationToken ct) + { + var dto = await _counts.GetAsync(countId, ct); + return dto is null ? NotFound() : Ok(dto); + } + + /// Create a count with system quantities snapshotted (immutable). + [HttpPost] + [ProducesResponseType(typeof(CountDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task> Create([FromBody] CreateCountRequest request, CancellationToken ct) + { + var dto = await _counts.CreateAsync(request, ct); + return Created($"/api/v1/stock-counts/{dto.CountId}", dto); + } + + /// Enter counted quantities; variance = counted − system. + [HttpPut("{countId:long}/counts")] + [ProducesResponseType(typeof(CountDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task> EnterCounts(long countId, [FromBody] EnterCountsRequest request, CancellationToken ct) + => Ok(await _counts.EnterCountsAsync(countId, request, ct)); + + /// Post: emit a variance adjustment and close the count. + [HttpPost("{countId:long}/post")] + [ProducesResponseType(typeof(CountPostResultDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task> Post(long countId, CancellationToken ct) + => Ok(await _counts.PostAsync(countId, ct)); +} diff --git a/Backend/ERPCore/Controllers/StockTransfersController.cs b/Backend/ERPCore/Controllers/StockTransfersController.cs new file mode 100644 index 0000000..9d70733 --- /dev/null +++ b/Backend/ERPCore/Controllers/StockTransfersController.cs @@ -0,0 +1,49 @@ +using ERPCore.Dtos.Stock; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// Stock-transfer endpoints (docs/11 §5.4). +[Route("api/v1/stock-transfers")] +public sealed class StockTransfersController : ApiControllerBase +{ + private readonly ITransferService _transfers; + + public StockTransfersController(ITransferService transfers) => _transfers = transfers; + + [HttpGet("{transferId:long}")] + [ProducesResponseType(typeof(TransferDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetById(long transferId, CancellationToken ct) + { + var dto = await _transfers.GetAsync(transferId, ct); + return dto is null ? NotFound() : Ok(dto); + } + + [HttpPost] + [ProducesResponseType(typeof(TransferDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task> Create([FromBody] CreateTransferRequest request, CancellationToken ct) + { + var dto = await _transfers.CreateAsync(request, ct); + return Created($"/api/v1/stock-transfers/{dto.TransferId}", dto); + } + + /// Dispatch: consume source FIFO layers into in-transit. 409 STOCK_NEGATIVE_BLOCKED if short. + [HttpPost("{transferId:long}/dispatch")] + [ProducesResponseType(typeof(DispatchResultDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task> Dispatch(long transferId, CancellationToken ct) + => Ok(await _transfers.DispatchAsync(transferId, ct)); + + /// Receive: create the destination layer at the inherited cost (cost-preserving). + [HttpPost("{transferId:long}/receive")] + [ProducesResponseType(typeof(ReceiveResultDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task> Receive(long transferId, [FromBody] ReceiveTransferRequest request, CancellationToken ct) + => Ok(await _transfers.ReceiveAsync(transferId, request, ct)); +} diff --git a/Backend/ERPCore/Domain/Entities/AuditLog.cs b/Backend/ERPCore/Domain/Entities/AuditLog.cs new file mode 100644 index 0000000..833a68c --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/AuditLog.cs @@ -0,0 +1,23 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Immutable audit trail entry (FR-X-02) — the compensating control for the deferred +/// RBAC (02-SECURITY AR-01/B.3). One row per create/update/delete of an audited +/// entity, capturing who / when / what changed (old→new in ). +/// Written automatically by ErpDbContext.SaveChangesAsync. Append-only at the +/// app level; DB-role revocation of UPDATE/DELETE is deferred hardening (B.3). +/// Model: docs/10 Part C.7. +/// +public class AuditLog +{ + public long AuditId { get; set; } + public long UserId { get; set; } + public string EntityType { get; set; } = string.Empty; + public long EntityId { get; set; } + public AuditAction Action { get; set; } + /// JSON change set: field→value (create/delete) or field→{old,new} (update). + public string ChangeSet { get; set; } = "{}"; + public DateTime CreatedAt { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/Batch.cs b/Backend/ERPCore/Domain/Entities/Batch.cs new file mode 100644 index 0000000..cc51ae4 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/Batch.cs @@ -0,0 +1,16 @@ +namespace ERPCore.Domain.Entities; + +/// +/// Batch/lot for a batch-tracked item (FR-GRN-04, FR-WH-03). Expiry drives FEFO +/// picking of perishables. Model: docs/10 Part C.4. +/// +public class Batch +{ + public long BatchId { get; set; } + + public long ItemId { get; set; } + public Item? Item { get; set; } + + public string BatchNo { get; set; } = string.Empty; + public DateOnly? ExpiryDate { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/Grn.cs b/Backend/ERPCore/Domain/Entities/Grn.cs new file mode 100644 index 0000000..9fc2516 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/Grn.cs @@ -0,0 +1,37 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Goods Receipt Note header (FR-GRN-01/02). Raised against a PO or direct +/// ( null). On confirm each line creates a FIFO layer and posts +/// an inbound ledger entry. Mutable aggregate with an +/// concurrency token (docs/10 C.10). Model: docs/10 Part C.3. +/// +public class Grn +{ + public long GrnId { get; set; } + public string DocNo { get; set; } = string.Empty; + + public long? PoId { get; set; } + public PurchaseOrder? PurchaseOrder { get; set; } + + public long VendorId { get; set; } + public Vendor? Vendor { get; set; } + + public long WarehouseId { get; set; } + public Warehouse? Warehouse { get; set; } + + public GrnStatus Status { get; set; } = GrnStatus.Draft; + + public long CreatedBy { get; set; } + public User? Creator { get; set; } + + public DateTime CreatedAt { get; set; } + public DateTime? PostedAt { get; set; } + + /// PostgreSQL xmin-backed optimistic concurrency token. + public uint RowVersion { get; set; } + + public ICollection Lines { get; set; } = new List(); +} diff --git a/Backend/ERPCore/Domain/Entities/GrnLine.cs b/Backend/ERPCore/Domain/Entities/GrnLine.cs new file mode 100644 index 0000000..28dce48 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/GrnLine.cs @@ -0,0 +1,37 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// GRN line (FR-GRN-04..08). is the PO-derived cost for +/// PO-based receipts (client cost ignored — 02-SECURITY C.3) or the entered cost +/// for direct receipts. = qty × unitCost. +/// gates issuability. Model: docs/10 Part C.3. +/// +public class GrnLine +{ + public long GrnLineId { get; set; } + + public long GrnId { get; set; } + public Grn? Grn { get; set; } + + public long? PoLineId { get; set; } + public PoLine? PoLine { get; set; } + + public long ItemId { get; set; } + public Item? Item { get; set; } + + public long UomId { get; set; } + public Uom? Uom { get; set; } + + public long? BinId { get; set; } + public Bin? Bin { get; set; } + + public long? BatchId { get; set; } + public Batch? Batch { get; set; } + + public decimal Qty { get; set; } + public decimal UnitCost { get; set; } + public decimal ReceivedValue { get; set; } + public HoldStatus HoldStatus { get; set; } = HoldStatus.Available; +} diff --git a/Backend/ERPCore/Domain/Entities/JournalEntryStub.cs b/Backend/ERPCore/Domain/Entities/JournalEntryStub.cs new file mode 100644 index 0000000..7f458c7 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/JournalEntryStub.cs @@ -0,0 +1,18 @@ +namespace ERPCore.Domain.Entities; + +/// +/// GL-ready journal entry emitted per stock movement (FR-STK-13) — data only, no +/// posting in Phase 1 (the Accounting module consumes these later). One row per +/// ledger entry, referencing the same source document polymorphically. Account +/// codes are Phase-1 placeholders until a chart of accounts exists. +/// Model: docs/10 Part C.7. +/// +public class JournalEntryStub +{ + public long JournalId { get; set; } + public string SourceDocType { get; set; } = string.Empty; + public long SourceDocId { get; set; } + public string DebitAccount { get; set; } = string.Empty; + public string CreditAccount { get; set; } = string.Empty; + public decimal Amount { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/PurchaseReturn.cs b/Backend/ERPCore/Domain/Entities/PurchaseReturn.cs new file mode 100644 index 0000000..1acd501 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/PurchaseReturn.cs @@ -0,0 +1,32 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Purchase return header (FR-PROC-08) — returns received goods to a vendor, +/// generating an outbound stock movement. Auto-posts with a mandatory reason code. +/// Model: docs/10 Part C.2. +/// +public class PurchaseReturn +{ + public long ReturnId { get; set; } + public string DocNo { get; set; } = string.Empty; + + public long VendorId { get; set; } + public Vendor? Vendor { get; set; } + + public long WarehouseId { get; set; } + public Warehouse? Warehouse { get; set; } + + public long ReasonCodeId { get; set; } + public ReasonCode? ReasonCode { get; set; } + + public ReturnStatus Status { get; set; } = ReturnStatus.Posted; + + public long CreatedBy { get; set; } + public User? Creator { get; set; } + + public DateTime CreatedAt { get; set; } + + public ICollection Lines { get; set; } = new List(); +} diff --git a/Backend/ERPCore/Domain/Entities/PurchaseReturnLine.cs b/Backend/ERPCore/Domain/Entities/PurchaseReturnLine.cs new file mode 100644 index 0000000..f42c0e0 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/PurchaseReturnLine.cs @@ -0,0 +1,21 @@ +namespace ERPCore.Domain.Entities; + +/// +/// Purchase-return line (FR-PROC-08) referencing the original GRN line for +/// traceability. is in base UOM. Model: docs/10 Part C.2. +/// +public class PurchaseReturnLine +{ + public long ReturnLineId { get; set; } + + public long ReturnId { get; set; } + public PurchaseReturn? Return { get; set; } + + public long? GrnLineId { get; set; } + public GrnLine? GrnLine { get; set; } + + public long ItemId { get; set; } + public Item? Item { get; set; } + + public decimal Qty { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/ReasonCode.cs b/Backend/ERPCore/Domain/Entities/ReasonCode.cs new file mode 100644 index 0000000..662bb49 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/ReasonCode.cs @@ -0,0 +1,15 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Configurable reason code for adjustments, returns and count variances +/// (FR-X-04). Model: docs/10 Part C.7. +/// +public class ReasonCode +{ + public long ReasonCodeId { get; set; } + public string Code { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public ReasonContext Context { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/Serial.cs b/Backend/ERPCore/Domain/Entities/Serial.cs new file mode 100644 index 0000000..2fdd5a4 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/Serial.cs @@ -0,0 +1,16 @@ +namespace ERPCore.Domain.Entities; + +/// +/// Serial number for a serial-tracked item across its lifecycle (FR-WH-04). +/// Model: docs/10 Part C.4. +/// +public class Serial +{ + public long SerialId { get; set; } + + public long ItemId { get; set; } + public Item? Item { get; set; } + + public string SerialNo { get; set; } = string.Empty; + public string Status { get; set; } = "InStock"; +} diff --git a/Backend/ERPCore/Domain/Entities/StockAdjustment.cs b/Backend/ERPCore/Domain/Entities/StockAdjustment.cs new file mode 100644 index 0000000..3b47f1a --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/StockAdjustment.cs @@ -0,0 +1,31 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Stock adjustment header (FR-STK-07) — the highest-risk feature in the phase +/// (02-SECURITY C.5). Auto-posts in Phase 1 with a mandatory reason code and user +/// stamp. Mutable aggregate with an token (docs/10 C.10). +/// Model: docs/10 Part C.6. +/// +public class StockAdjustment +{ + public long AdjustmentId { get; set; } + public string DocNo { get; set; } = string.Empty; + + public long WarehouseId { get; set; } + public Warehouse? Warehouse { get; set; } + + public long ReasonCodeId { get; set; } + public ReasonCode? ReasonCode { get; set; } + + public AdjustmentStatus Status { get; set; } = AdjustmentStatus.Posted; + + public long CreatedBy { get; set; } + public User? Creator { get; set; } + + public DateTime CreatedAt { get; set; } + public uint RowVersion { get; set; } + + public ICollection Lines { get; set; } = new List(); +} diff --git a/Backend/ERPCore/Domain/Entities/StockAdjustmentLine.cs b/Backend/ERPCore/Domain/Entities/StockAdjustmentLine.cs new file mode 100644 index 0000000..dbdae9b --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/StockAdjustmentLine.cs @@ -0,0 +1,23 @@ +namespace ERPCore.Domain.Entities; + +/// +/// Adjustment line (FR-STK-07). is a signed base-UOM +/// quantity: negative consumes FIFO layers, positive creates a layer at last cost. +/// Model: docs/10 Part C.6. +/// +public class StockAdjustmentLine +{ + public long AdjLineId { get; set; } + + public long AdjustmentId { get; set; } + public StockAdjustment? Adjustment { get; set; } + + public long ItemId { get; set; } + public Item? Item { get; set; } + + public long? BinId { get; set; } + public long? BatchId { get; set; } + public long? SerialId { get; set; } + + public decimal QtyDelta { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/StockCount.cs b/Backend/ERPCore/Domain/Entities/StockCount.cs new file mode 100644 index 0000000..5d652d8 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/StockCount.cs @@ -0,0 +1,29 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Cycle/full physical count header (FR-STK-08). System quantities are snapshotted +/// at creation and are immutable once opened (02-SECURITY C.7); posting emits a +/// variance adjustment. Mutable aggregate with an token. +/// Model: docs/10 Part C.6. +/// +public class StockCount +{ + public long CountId { get; set; } + public string DocNo { get; set; } = string.Empty; + + public long WarehouseId { get; set; } + public Warehouse? Warehouse { get; set; } + + public CountType CountType { get; set; } + public CountStatus Status { get; set; } = CountStatus.Draft; + + public long CreatedBy { get; set; } + public User? Creator { get; set; } + + public DateTime CreatedAt { get; set; } + public uint RowVersion { get; set; } + + public ICollection Lines { get; set; } = new List(); +} diff --git a/Backend/ERPCore/Domain/Entities/StockCountLine.cs b/Backend/ERPCore/Domain/Entities/StockCountLine.cs new file mode 100644 index 0000000..2a8ef40 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/StockCountLine.cs @@ -0,0 +1,22 @@ +namespace ERPCore.Domain.Entities; + +/// +/// Count line (FR-STK-08). is the immutable snapshot; +/// = counted − system (in base UOM). Model: docs/10 Part C.6. +/// +public class StockCountLine +{ + public long CountLineId { get; set; } + + public long CountId { get; set; } + public StockCount? Count { get; set; } + + public long ItemId { get; set; } + public Item? Item { get; set; } + + public long? BinId { get; set; } + + public decimal SystemQty { get; set; } + public decimal? CountedQty { get; set; } + public decimal? Variance { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/StockLayer.cs b/Backend/ERPCore/Domain/Entities/StockLayer.cs new file mode 100644 index 0000000..de686c1 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/StockLayer.cs @@ -0,0 +1,33 @@ +namespace ERPCore.Domain.Entities; + +/// +/// FIFO cost layer — a quantity received at a specific unit cost, consumed +/// oldest-first (FR-STK-02). Keyed per item **per warehouse**; quantities and +/// are in the item's base UOM. Answers valuation +/// ("what's on hand and at what cost"). Model: docs/10 Part C.5. +/// +public class StockLayer +{ + public long LayerId { get; set; } + + public long ItemId { get; set; } + public Item? Item { get; set; } + + public long WarehouseId { get; set; } + public Warehouse? Warehouse { get; set; } + + public long? BatchId { get; set; } + public Batch? Batch { get; set; } + + public long? SerialId { get; set; } + public Serial? Serial { get; set; } + + /// Originating GRN line — carries the inspection hold status for this stock. + public long? GrnLineId { get; set; } + public GrnLine? GrnLine { get; set; } + + public decimal QtyReceived { get; set; } + public decimal QtyRemaining { get; set; } + public decimal UnitCost { get; set; } + public DateTime ReceiptDate { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/StockLedger.cs b/Backend/ERPCore/Domain/Entities/StockLedger.cs new file mode 100644 index 0000000..cbffbf7 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/StockLedger.cs @@ -0,0 +1,32 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Immutable, append-only stock ledger (FR-STK-01, FR-X-05). One row per costed +/// movement; answers history ("what moved, when, by whom"). The originating +/// document is referenced polymorphically via +/// / (no hard FK per type) so +/// new transaction types write here without a schema change. Model: docs/10 Part C.5. +/// +public class StockLedger +{ + public long LedgerId { get; set; } + + public long ItemId { get; set; } + public long WarehouseId { get; set; } + public long? BinId { get; set; } + public long? BatchId { get; set; } + public long? SerialId { get; set; } + public long UserId { get; set; } + + public Direction Direction { get; set; } + public decimal QtyBase { get; set; } + public decimal UnitCost { get; set; } + public decimal Value { get; set; } + public decimal RunningBalance { get; set; } + + public string SourceDocType { get; set; } = string.Empty; + public long SourceDocId { get; set; } + public DateTime CreatedAt { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/StockTransfer.cs b/Backend/ERPCore/Domain/Entities/StockTransfer.cs new file mode 100644 index 0000000..7bfc244 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/StockTransfer.cs @@ -0,0 +1,31 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Inter-warehouse stock transfer header (FR-STK-05/06). Dispatch consumes source +/// FIFO layers into in-transit; receive creates the destination layer at the +/// inherited cost (cost-preserving). Mutable aggregate with an +/// token (docs/10 C.10). Model: docs/10 Part C.6. +/// +public class StockTransfer +{ + public long TransferId { get; set; } + public string DocNo { get; set; } = string.Empty; + + public long SrcWarehouseId { get; set; } + public Warehouse? SrcWarehouse { get; set; } + + public long DestWarehouseId { get; set; } + public Warehouse? DestWarehouse { get; set; } + + public TransferStatus Status { get; set; } = TransferStatus.Draft; + + public long CreatedBy { get; set; } + public User? Creator { get; set; } + + public DateTime CreatedAt { get; set; } + public uint RowVersion { get; set; } + + public ICollection Lines { get; set; } = new List(); +} diff --git a/Backend/ERPCore/Domain/Entities/StockTransferLine.cs b/Backend/ERPCore/Domain/Entities/StockTransferLine.cs new file mode 100644 index 0000000..3959676 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/StockTransferLine.cs @@ -0,0 +1,35 @@ +namespace ERPCore.Domain.Entities; + +/// +/// Transfer line (FR-STK-05/06). is in base UOM. +/// +/// Deviation note: and extend +/// docs/10 Part C.6's STOCK_TRANSFER_LINE to make the transfer +/// cost-preserving: at dispatch the value-weighted cost of the consumed source +/// layers is stored here, and receive recreates the destination layer at that cost +/// (supports partial receive via ). +/// +/// +public class StockTransferLine +{ + public long TransferLineId { get; set; } + + public long TransferId { get; set; } + public StockTransfer? Transfer { get; set; } + + public long ItemId { get; set; } + public Item? Item { get; set; } + + public long? SrcBinId { get; set; } + public long? DestBinId { get; set; } + public long? BatchId { get; set; } + public long? SerialId { get; set; } + + public decimal Qty { get; set; } + + /// Value-weighted unit cost of the consumed source layers (set at dispatch). + public decimal? UnitCost { get; set; } + + /// Quantity already received at the destination (partial-receive support). + public decimal QtyReceived { get; set; } +} diff --git a/Backend/ERPCore/Domain/Enums/AdjustmentStatus.cs b/Backend/ERPCore/Domain/Enums/AdjustmentStatus.cs new file mode 100644 index 0000000..7d56708 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/AdjustmentStatus.cs @@ -0,0 +1,13 @@ +namespace ERPCore.Domain.Enums; + +/// +/// Stock-adjustment lifecycle (docs/10 §B.8.1). Phase 1 auto-posts, so +/// is reserved for the future threshold-approval +/// workflow (FR-STK-07). Stored as a string. +/// +public enum AdjustmentStatus +{ + Draft, + PendingApproval, + Posted +} diff --git a/Backend/ERPCore/Domain/Enums/AuditAction.cs b/Backend/ERPCore/Domain/Enums/AuditAction.cs new file mode 100644 index 0000000..f7644e8 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/AuditAction.cs @@ -0,0 +1,9 @@ +namespace ERPCore.Domain.Enums; + +/// Kind of mutation recorded in the audit trail (FR-X-02). Stored as a string. +public enum AuditAction +{ + Create, + Update, + Delete +} diff --git a/Backend/ERPCore/Domain/Enums/CountStatus.cs b/Backend/ERPCore/Domain/Enums/CountStatus.cs new file mode 100644 index 0000000..8e3e958 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/CountStatus.cs @@ -0,0 +1,9 @@ +namespace ERPCore.Domain.Enums; + +/// Stock-count lifecycle (docs/11 §8; docs/10 §B.8.1). Stored as a string. +public enum CountStatus +{ + Draft, + Counted, + Posted +} diff --git a/Backend/ERPCore/Domain/Enums/CountType.cs b/Backend/ERPCore/Domain/Enums/CountType.cs new file mode 100644 index 0000000..29015d8 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/CountType.cs @@ -0,0 +1,8 @@ +namespace ERPCore.Domain.Enums; + +/// Physical-count scope (docs/11 §8; FR-STK-08). Stored as a string. +public enum CountType +{ + Cycle, + Full +} diff --git a/Backend/ERPCore/Domain/Enums/Direction.cs b/Backend/ERPCore/Domain/Enums/Direction.cs new file mode 100644 index 0000000..afee991 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/Direction.cs @@ -0,0 +1,8 @@ +namespace ERPCore.Domain.Enums; + +/// Stock-ledger movement direction (docs/11 §8). Stored as a string. +public enum Direction +{ + In, + Out +} diff --git a/Backend/ERPCore/Domain/Enums/GrnStatus.cs b/Backend/ERPCore/Domain/Enums/GrnStatus.cs new file mode 100644 index 0000000..550cca2 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/GrnStatus.cs @@ -0,0 +1,9 @@ +namespace ERPCore.Domain.Enums; + +/// Goods-receipt-note lifecycle (docs/11 §8; docs/10 §B.8.1). Stored as a string. +public enum GrnStatus +{ + Draft, + Confirmed, + Closed +} diff --git a/Backend/ERPCore/Domain/Enums/HoldStatus.cs b/Backend/ERPCore/Domain/Enums/HoldStatus.cs new file mode 100644 index 0000000..e38dd1d --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/HoldStatus.cs @@ -0,0 +1,12 @@ +namespace ERPCore.Domain.Enums; + +/// +/// Inspection-hold state of received stock (docs/11 §8; FR-GRN-05). +/// stock is on-hand but not issuable until released (FR-WH-07). Stored as a string. +/// +public enum HoldStatus +{ + Available, + OnHold, + Rejected +} diff --git a/Backend/ERPCore/Domain/Enums/ReasonContext.cs b/Backend/ERPCore/Domain/Enums/ReasonContext.cs new file mode 100644 index 0000000..b569040 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/ReasonContext.cs @@ -0,0 +1,9 @@ +namespace ERPCore.Domain.Enums; + +/// Where a reason code applies (FR-X-04; docs/10 §B.8.3). Stored as a string. +public enum ReasonContext +{ + Adjustment, + Return, + Count +} diff --git a/Backend/ERPCore/Domain/Enums/ReturnStatus.cs b/Backend/ERPCore/Domain/Enums/ReturnStatus.cs new file mode 100644 index 0000000..6214b0b --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/ReturnStatus.cs @@ -0,0 +1,8 @@ +namespace ERPCore.Domain.Enums; + +/// Purchase-return lifecycle (docs/11 §3.4). Auto-posts in Phase 1. Stored as a string. +public enum ReturnStatus +{ + Draft, + Posted +} diff --git a/Backend/ERPCore/Domain/Enums/TransferStatus.cs b/Backend/ERPCore/Domain/Enums/TransferStatus.cs new file mode 100644 index 0000000..0f96724 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/TransferStatus.cs @@ -0,0 +1,10 @@ +namespace ERPCore.Domain.Enums; + +/// Stock-transfer lifecycle (docs/11 §8; docs/10 §B.8.1). Stored as a string. +public enum TransferStatus +{ + Draft, + InTransit, + Received, + Closed +} diff --git a/Backend/ERPCore/Dtos/Audit/AuditDtos.cs b/Backend/ERPCore/Dtos/Audit/AuditDtos.cs new file mode 100644 index 0000000..4870725 --- /dev/null +++ b/Backend/ERPCore/Dtos/Audit/AuditDtos.cs @@ -0,0 +1,12 @@ +using System.Text.Json; +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Audit; + +/// An audit-trail entry (FR-X-02). ChangeSet is the stored JSON, inlined. +public sealed record AuditLogDto( + long AuditId, long UserId, string EntityType, long EntityId, AuditAction Action, JsonElement ChangeSet, DateTime CreatedAt); + +/// A GL-ready journal stub emitted per stock movement (FR-STK-13). +public sealed record JournalEntryStubDto( + long JournalId, string SourceDocType, long SourceDocId, string DebitAccount, string CreditAccount, decimal Amount); diff --git a/Backend/ERPCore/Dtos/Grn/GrnDtos.cs b/Backend/ERPCore/Dtos/Grn/GrnDtos.cs new file mode 100644 index 0000000..72582ed --- /dev/null +++ b/Backend/ERPCore/Dtos/Grn/GrnDtos.cs @@ -0,0 +1,63 @@ +using System.ComponentModel.DataAnnotations; +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Grn; + +// Responses (docs/11 §4) -------------------------------------------------------- + +public sealed record GrnLineDto( + long GrnLineId, long? PoLineId, long ItemId, long UomId, long? BinId, + decimal Qty, decimal UnitCost, decimal ReceivedValue, HoldStatus HoldStatus, long? BatchId); + +public sealed record GrnDto( + long GrnId, string DocNo, long? PoId, long VendorId, long WarehouseId, GrnStatus Status, + long CreatedBy, DateTime CreatedAt, DateTime? PostedAt, IReadOnlyList Lines); + +public sealed record CreatedLayerDto( + long LayerId, long ItemId, long WarehouseId, long? BatchId, + decimal QtyReceived, decimal QtyRemaining, decimal UnitCost, DateTime ReceiptDate); + +public sealed record GrnConfirmResultDto( + long GrnId, GrnStatus Status, DateTime PostedAt, + IReadOnlyList CreatedLayers, IReadOnlyList LedgerRefs, PurchaseOrderStatus? PoStatus); + +public sealed record ReleaseLineResultDto(long GrnLineId, HoldStatus HoldStatus); + +// Requests ---------------------------------------------------------------------- + +public sealed class BatchInput +{ + [Required, StringLength(50)] public string BatchNo { get; set; } = string.Empty; + public DateOnly? ExpiryDate { get; set; } +} + +public sealed class CreateGrnLineInput +{ + /// Set for a PO-based receipt; cost is then derived from the PO line (02-SECURITY C.3). + public long? PoLineId { get; set; } + [Required] public long ItemId { get; set; } + [Required] public long UomId { get; set; } + public long? BinId { get; set; } + [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } + /// Used only for direct (no-PO) receipts; ignored when is set. + [Range(0, double.MaxValue)] public decimal UnitCost { get; set; } + [EnumDataType(typeof(HoldStatus))] public HoldStatus HoldStatus { get; set; } = HoldStatus.Available; + public BatchInput? Batch { get; set; } +} + +public sealed class CreateGrnRequest +{ + /// PO to receive against; null for a direct/emergency receipt (FR-GRN-02). + public long? PoId { get; set; } + /// Required for a direct receipt (no PO); ignored otherwise (vendor derives from the PO). + public long? VendorId { get; set; } + [Required] public long WarehouseId { get; set; } + [Required, MinLength(1)] public List Lines { get; set; } = new(); +} + +public sealed class ReleaseLineRequest +{ + /// Release makes the stock available; Reject removes it from on-hand. + [Required, RegularExpression("Release|Reject")] + public string Action { get; set; } = "Release"; +} diff --git a/Backend/ERPCore/Dtos/Procurement/PurchaseReturnDtos.cs b/Backend/ERPCore/Dtos/Procurement/PurchaseReturnDtos.cs new file mode 100644 index 0000000..85328ed --- /dev/null +++ b/Backend/ERPCore/Dtos/Procurement/PurchaseReturnDtos.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Procurement; + +// Responses (docs/11 §3.4) ------------------------------------------------------ + +public sealed record PurchaseReturnLineDto(long ReturnLineId, long? GrnLineId, long ItemId, decimal Qty); + +public sealed record PurchaseReturnDto( + long ReturnId, string DocNo, long VendorId, long WarehouseId, long ReasonCodeId, ReturnStatus Status, + long CreatedBy, IReadOnlyList Lines, IReadOnlyList LedgerRefs); + +// Requests ---------------------------------------------------------------------- + +public sealed class CreatePurchaseReturnLineInput +{ + /// Original GRN line, for traceability against the receipt. + public long? GrnLineId { get; set; } + [Required] public long ItemId { get; set; } + [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } +} + +public sealed class CreatePurchaseReturnRequest +{ + [Required] public long VendorId { get; set; } + [Required] public long WarehouseId { get; set; } + /// Nullable so an omitted value is a distinct REASON_CODE_REQUIRED error. + public long? ReasonCodeId { get; set; } + [Required, MinLength(1)] public List Lines { get; set; } = new(); +} diff --git a/Backend/ERPCore/Dtos/Reference/ReasonCodeDtos.cs b/Backend/ERPCore/Dtos/Reference/ReasonCodeDtos.cs new file mode 100644 index 0000000..60080f2 --- /dev/null +++ b/Backend/ERPCore/Dtos/Reference/ReasonCodeDtos.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Reference; + +/// Reason code (docs/11 §6). +public sealed record ReasonCodeDto(long ReasonCodeId, string Code, string Description, ReasonContext Context); + +public sealed class CreateReasonCodeRequest +{ + [Required, StringLength(20)] public string Code { get; set; } = string.Empty; + [Required, StringLength(200)] public string Description { get; set; } = string.Empty; + [Required, EnumDataType(typeof(ReasonContext))] public ReasonContext Context { get; set; } +} diff --git a/Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs b/Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs new file mode 100644 index 0000000..57d41a3 --- /dev/null +++ b/Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Stock; + +// Responses (docs/11 §5.5) ------------------------------------------------------ + +public sealed record AdjustmentLineDto(long AdjLineId, long ItemId, long? BinId, long? BatchId, decimal QtyDelta); + +public sealed record AdjustmentDto( + long AdjustmentId, string DocNo, long WarehouseId, long ReasonCodeId, AdjustmentStatus Status, + long CreatedBy, DateTime CreatedAt, IReadOnlyList Lines, IReadOnlyList LedgerRefs); + +// Requests ---------------------------------------------------------------------- + +public sealed class CreateAdjustmentLineInput +{ + [Required] public long ItemId { get; set; } + public long? BinId { get; set; } + public long? BatchId { get; set; } + /// Signed base-UOM delta: negative consumes FIFO layers, positive adds stock. + public decimal QtyDelta { get; set; } +} + +public sealed class CreateAdjustmentRequest +{ + [Required] public long WarehouseId { get; set; } + /// Nullable so an omitted value is a distinct REASON_CODE_REQUIRED error, not 0. + public long? ReasonCodeId { get; set; } + [Required, MinLength(1)] public List Lines { get; set; } = new(); +} diff --git a/Backend/ERPCore/Dtos/Stock/CountDtos.cs b/Backend/ERPCore/Dtos/Stock/CountDtos.cs new file mode 100644 index 0000000..79a4b73 --- /dev/null +++ b/Backend/ERPCore/Dtos/Stock/CountDtos.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Stock; + +// Responses (docs/11 §5.6) ------------------------------------------------------ + +public sealed record CountLineDto(long CountLineId, long ItemId, long? BinId, decimal SystemQty, decimal? CountedQty, decimal? Variance); + +public sealed record CountDto( + long CountId, string DocNo, long WarehouseId, CountType CountType, CountStatus Status, IReadOnlyList Lines); + +public sealed record CountPostResultDto(long CountId, CountStatus Status, long? AdjustmentId, IReadOnlyList LedgerRefs); + +// Requests ---------------------------------------------------------------------- + +public sealed class CreateCountRequest +{ + [Required] public long WarehouseId { get; set; } + [Required, EnumDataType(typeof(CountType))] public CountType CountType { get; set; } + [Required, MinLength(1)] public List ItemIds { get; set; } = new(); +} + +public sealed class EnterCountLineInput +{ + [Required] public long CountLineId { get; set; } + [Range(0, double.MaxValue)] public decimal CountedQty { get; set; } +} + +public sealed class EnterCountsRequest +{ + [Required, MinLength(1)] public List Lines { get; set; } = new(); +} diff --git a/Backend/ERPCore/Dtos/Stock/ReorderDtos.cs b/Backend/ERPCore/Dtos/Stock/ReorderDtos.cs new file mode 100644 index 0000000..48d1b7c --- /dev/null +++ b/Backend/ERPCore/Dtos/Stock/ReorderDtos.cs @@ -0,0 +1,6 @@ +namespace ERPCore.Dtos.Stock; + +/// An item at/below its reorder point (docs/11 §5.7; FR-STK-10). Computed on read. +public sealed record ReorderAlertDto( + long ItemId, long WarehouseId, decimal Available, + decimal ReorderPoint, decimal ReorderQty, decimal SuggestedRequisitionQty); diff --git a/Backend/ERPCore/Dtos/Stock/StockDtos.cs b/Backend/ERPCore/Dtos/Stock/StockDtos.cs new file mode 100644 index 0000000..a1663e8 --- /dev/null +++ b/Backend/ERPCore/Dtos/Stock/StockDtos.cs @@ -0,0 +1,22 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Stock; + +/// Stock enquiry (docs/11 §5.1). available = onHand − onHold − reserved − inTransit(out). +public sealed record StockOnHandDto( + long ItemId, long WarehouseId, decimal OnHand, decimal Available, + decimal OnHold, decimal InTransit, decimal Reserved, DateTime AsOf); + +/// A stock-ledger row (docs/11 §5.2). +public sealed record StockLedgerRowDto( + long LedgerId, long ItemId, long WarehouseId, long? BinId, long? BatchId, long? SerialId, + Direction Direction, decimal QtyBase, decimal UnitCost, decimal Value, decimal RunningBalance, + string SourceDocType, long SourceDocId, long UserId, DateTime CreatedAt); + +/// An open FIFO layer in a valuation (docs/11 §5.3). +public sealed record StockValuationLayerDto(long LayerId, decimal QtyRemaining, decimal UnitCost, decimal Value, DateTime ReceiptDate); + +/// Valuation of on-hand stock from open FIFO layers (docs/11 §5.3). +public sealed record StockValuationDto( + long ItemId, long WarehouseId, IReadOnlyList Layers, + decimal TotalQty, decimal TotalValue, string Currency, string CostingMethod); diff --git a/Backend/ERPCore/Dtos/Stock/TransferDtos.cs b/Backend/ERPCore/Dtos/Stock/TransferDtos.cs new file mode 100644 index 0000000..3974852 --- /dev/null +++ b/Backend/ERPCore/Dtos/Stock/TransferDtos.cs @@ -0,0 +1,52 @@ +using System.ComponentModel.DataAnnotations; +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Stock; + +// Responses (docs/11 §5.4) ------------------------------------------------------ + +public sealed record TransferLineDto( + long TransferLineId, long ItemId, long? SrcBinId, long? DestBinId, long? BatchId, decimal Qty, decimal QtyReceived); + +public sealed record TransferDto( + long TransferId, string DocNo, long SrcWarehouseId, long DestWarehouseId, + TransferStatus Status, IReadOnlyList Lines); + +public sealed record ConsumedLayerDto(long LayerId, decimal QtyConsumed, decimal UnitCost); + +public sealed record DispatchResultDto( + long TransferId, TransferStatus Status, IReadOnlyList ConsumedLayers, IReadOnlyList LedgerRefs); + +public sealed record TransferCreatedLayerDto(long LayerId, long WarehouseId, decimal QtyReceived, decimal UnitCost); + +public sealed record ReceiveResultDto( + long TransferId, TransferStatus Status, IReadOnlyList CreatedLayers, IReadOnlyList LedgerRefs); + +// Requests ---------------------------------------------------------------------- + +public sealed class CreateTransferLineInput +{ + [Required] public long ItemId { get; set; } + public long? SrcBinId { get; set; } + public long? DestBinId { get; set; } + public long? BatchId { get; set; } + [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } +} + +public sealed class CreateTransferRequest +{ + [Required] public long SrcWarehouseId { get; set; } + [Required] public long DestWarehouseId { get; set; } + [Required, MinLength(1)] public List Lines { get; set; } = new(); +} + +public sealed class ReceiveTransferLineInput +{ + [Required] public long TransferLineId { get; set; } + [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } +} + +public sealed class ReceiveTransferRequest +{ + [Required, MinLength(1)] public List Lines { get; set; } = new(); +} diff --git a/Backend/ERPCore/Infra/Persistence/Auditing/AuditScribe.cs b/Backend/ERPCore/Infra/Persistence/Auditing/AuditScribe.cs new file mode 100644 index 0000000..a65e413 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Auditing/AuditScribe.cs @@ -0,0 +1,98 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace ERPCore.Infra.Persistence.Auditing; + +/// A mutation captured before save, awaiting its (possibly generated) key. +public sealed record PendingAudit(EntityEntry Entry, string EntityType, AuditAction Action, string ChangeSet, long CapturedId, bool IsAdded); + +/// +/// Builds audit-trail rows from the EF change tracker (FR-X-02). High-volume / +/// derived / self-referential tables are excluded (the stock ledger is itself the +/// stock movement audit). Change sets are captured before save so old→new is +/// accurate; generated keys for inserts are read after save. +/// +public static class AuditScribe +{ + private static readonly HashSet Excluded = + [ + typeof(AuditLog), typeof(JournalEntryStub), typeof(NumberSequence), + typeof(StockLedger), typeof(StockLayer), + ]; + + private static readonly JsonSerializerOptions Json = new() + { + Converters = { new JsonStringEnumConverter() }, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + public static List Capture(ChangeTracker tracker) + { + var pending = new List(); + foreach (var entry in tracker.Entries()) + { + if (entry.State is not (EntityState.Added or EntityState.Modified or EntityState.Deleted)) continue; + if (Excluded.Contains(entry.Entity.GetType())) continue; + + var action = entry.State switch + { + EntityState.Added => AuditAction.Create, + EntityState.Deleted => AuditAction.Delete, + _ => AuditAction.Update, + }; + + var changeSet = BuildChangeSet(entry, action); + if (action == AuditAction.Update && changeSet == "{}") continue; // only concurrency token touched, etc. + + var isAdded = entry.State == EntityState.Added; + pending.Add(new PendingAudit(entry, entry.Entity.GetType().Name, action, changeSet, isAdded ? 0 : ReadKey(entry), isAdded)); + } + return pending; + } + + public static AuditLog ToLog(PendingAudit p, long userId, DateTime now) => new() + { + UserId = userId, + EntityType = p.EntityType, + EntityId = p.IsAdded ? ReadKey(p.Entry) : p.CapturedId, + Action = p.Action, + ChangeSet = p.ChangeSet, + CreatedAt = now, + }; + + private static long ReadKey(EntityEntry entry) + { + var pk = entry.Metadata.FindPrimaryKey(); + if (pk is null || pk.Properties.Count != 1) return 0; + var value = entry.Property(pk.Properties[0].Name).CurrentValue; + return value is null ? 0 : Convert.ToInt64(value); + } + + private static string BuildChangeSet(EntityEntry entry, AuditAction action) + { + var set = new Dictionary(); + foreach (var p in entry.Properties) + { + if (p.Metadata.IsPrimaryKey()) continue; + if (p.Metadata.Name == nameof(Item.RowVersion)) continue; + + switch (action) + { + case AuditAction.Create when p.CurrentValue is not null: + set[p.Metadata.Name] = p.CurrentValue; + break; + case AuditAction.Delete: + set[p.Metadata.Name] = p.OriginalValue; + break; + case AuditAction.Update when p.IsModified && !Equals(p.OriginalValue, p.CurrentValue): + set[p.Metadata.Name] = new Dictionary { ["old"] = p.OriginalValue, ["new"] = p.CurrentValue }; + break; + } + } + return JsonSerializer.Serialize(set, Json); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/AuditConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/AuditConfiguration.cs new file mode 100644 index 0000000..a93681c --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/AuditConfiguration.cs @@ -0,0 +1,41 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class AuditLogConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("audit_logs"); + builder.HasKey(a => a.AuditId); + + builder.Property(a => a.EntityType).IsRequired().HasMaxLength(80); + builder.Property(a => a.Action).HasConversion().HasMaxLength(10).IsRequired(); + builder.Property(a => a.ChangeSet).IsRequired().HasColumnType("jsonb"); + builder.Property(a => a.CreatedAt).IsRequired(); + + builder.HasOne().WithMany().HasForeignKey(a => a.UserId).OnDelete(DeleteBehavior.Restrict); + + builder.HasIndex(a => new { a.EntityType, a.EntityId }); + builder.HasIndex(a => a.CreatedAt); + builder.HasIndex(a => a.UserId); + } +} + +public sealed class JournalEntryStubConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("journal_entry_stubs"); + builder.HasKey(j => j.JournalId); + + builder.Property(j => j.SourceDocType).IsRequired().HasMaxLength(10); + builder.Property(j => j.DebitAccount).IsRequired().HasMaxLength(20); + builder.Property(j => j.CreditAccount).IsRequired().HasMaxLength(20); + builder.Property(j => j.Amount).HasPrecision(18, 4); + + builder.HasIndex(j => new { j.SourceDocType, j.SourceDocId }); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/BatchSerialConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/BatchSerialConfiguration.cs new file mode 100644 index 0000000..8de6adf --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/BatchSerialConfiguration.cs @@ -0,0 +1,43 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class BatchConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("batches"); + builder.HasKey(b => b.BatchId); + + builder.Property(b => b.BatchNo).IsRequired().HasMaxLength(50); + + builder.HasOne(b => b.Item) + .WithMany() + .HasForeignKey(b => b.ItemId) + .OnDelete(DeleteBehavior.Restrict); + + // Batch number unique within an item. + builder.HasIndex(b => new { b.ItemId, b.BatchNo }).IsUnique(); + } +} + +public sealed class SerialConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("serials"); + builder.HasKey(s => s.SerialId); + + builder.Property(s => s.SerialNo).IsRequired().HasMaxLength(100); + builder.Property(s => s.Status).IsRequired().HasMaxLength(20); + + builder.HasOne(s => s.Item) + .WithMany() + .HasForeignKey(s => s.ItemId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasIndex(s => new { s.ItemId, s.SerialNo }).IsUnique(); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/GrnConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/GrnConfiguration.cs new file mode 100644 index 0000000..087ea19 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/GrnConfiguration.cs @@ -0,0 +1,50 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class GrnConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("grns"); + builder.HasKey(g => g.GrnId); + + builder.Property(g => g.DocNo).IsRequired().HasMaxLength(30); + builder.HasIndex(g => g.DocNo).IsUnique(); + + builder.Property(g => g.Status).HasConversion().HasMaxLength(20).IsRequired(); + builder.Property(g => g.CreatedAt).IsRequired(); + builder.Property(g => g.RowVersion).IsRowVersion(); + + builder.HasOne(g => g.PurchaseOrder).WithMany().HasForeignKey(g => g.PoId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(g => g.Vendor).WithMany().HasForeignKey(g => g.VendorId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(g => g.Warehouse).WithMany().HasForeignKey(g => g.WarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(g => g.Creator).WithMany().HasForeignKey(g => g.CreatedBy).OnDelete(DeleteBehavior.Restrict); + + builder.HasIndex(g => g.Status); + builder.HasIndex(g => g.PoId); + } +} + +public sealed class GrnLineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("grn_lines"); + builder.HasKey(l => l.GrnLineId); + + builder.Property(l => l.Qty).HasPrecision(18, 4); + builder.Property(l => l.UnitCost).HasPrecision(18, 6); + builder.Property(l => l.ReceivedValue).HasPrecision(18, 4); + builder.Property(l => l.HoldStatus).HasConversion().HasMaxLength(20).IsRequired(); + + builder.HasOne(l => l.Grn).WithMany(g => g.Lines).HasForeignKey(l => l.GrnId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne(l => l.PoLine).WithMany().HasForeignKey(l => l.PoLineId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.Uom).WithMany().HasForeignKey(l => l.UomId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.Bin).WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.Batch).WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/PurchaseReturnConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/PurchaseReturnConfiguration.cs new file mode 100644 index 0000000..00add0a --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/PurchaseReturnConfiguration.cs @@ -0,0 +1,40 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class PurchaseReturnConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("purchase_returns"); + builder.HasKey(r => r.ReturnId); + + builder.Property(r => r.DocNo).IsRequired().HasMaxLength(30); + builder.HasIndex(r => r.DocNo).IsUnique(); + + builder.Property(r => r.Status).HasConversion().HasMaxLength(20).IsRequired(); + builder.Property(r => r.CreatedAt).IsRequired(); + + builder.HasOne(r => r.Vendor).WithMany().HasForeignKey(r => r.VendorId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(r => r.Warehouse).WithMany().HasForeignKey(r => r.WarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(r => r.ReasonCode).WithMany().HasForeignKey(r => r.ReasonCodeId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(r => r.Creator).WithMany().HasForeignKey(r => r.CreatedBy).OnDelete(DeleteBehavior.Restrict); + } +} + +public sealed class PurchaseReturnLineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("purchase_return_lines"); + builder.HasKey(l => l.ReturnLineId); + + builder.Property(l => l.Qty).HasPrecision(18, 4); + + builder.HasOne(l => l.Return).WithMany(r => r.Lines).HasForeignKey(l => l.ReturnId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne(l => l.GrnLine).WithMany().HasForeignKey(l => l.GrnLineId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/ReasonCodeConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/ReasonCodeConfiguration.cs new file mode 100644 index 0000000..4746889 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/ReasonCodeConfiguration.cs @@ -0,0 +1,20 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class ReasonCodeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("reason_codes"); + builder.HasKey(r => r.ReasonCodeId); + + builder.Property(r => r.Code).IsRequired().HasMaxLength(20); + builder.Property(r => r.Description).IsRequired().HasMaxLength(200); + builder.Property(r => r.Context).HasConversion().HasMaxLength(20).IsRequired(); + + builder.HasIndex(r => new { r.Context, r.Code }).IsUnique(); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/StockAdjustmentConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/StockAdjustmentConfiguration.cs new file mode 100644 index 0000000..d50a0e6 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/StockAdjustmentConfiguration.cs @@ -0,0 +1,42 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class StockAdjustmentConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("stock_adjustments"); + builder.HasKey(a => a.AdjustmentId); + + builder.Property(a => a.DocNo).IsRequired().HasMaxLength(30); + builder.HasIndex(a => a.DocNo).IsUnique(); + + builder.Property(a => a.Status).HasConversion().HasMaxLength(20).IsRequired(); + builder.Property(a => a.CreatedAt).IsRequired(); + builder.Property(a => a.RowVersion).IsRowVersion(); + + builder.HasOne(a => a.Warehouse).WithMany().HasForeignKey(a => a.WarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(a => a.ReasonCode).WithMany().HasForeignKey(a => a.ReasonCodeId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(a => a.Creator).WithMany().HasForeignKey(a => a.CreatedBy).OnDelete(DeleteBehavior.Restrict); + } +} + +public sealed class StockAdjustmentLineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("stock_adjustment_lines"); + builder.HasKey(l => l.AdjLineId); + + builder.Property(l => l.QtyDelta).HasPrecision(18, 4); + + builder.HasOne(l => l.Adjustment).WithMany(a => a.Lines).HasForeignKey(l => l.AdjustmentId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/StockConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/StockConfiguration.cs new file mode 100644 index 0000000..57d51a6 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/StockConfiguration.cs @@ -0,0 +1,60 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class StockLayerConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("stock_layers"); + builder.HasKey(l => l.LayerId); + + builder.Property(l => l.QtyReceived).HasPrecision(18, 4); + builder.Property(l => l.QtyRemaining).HasPrecision(18, 4); + builder.Property(l => l.UnitCost).HasPrecision(18, 6); + builder.Property(l => l.ReceiptDate).IsRequired(); + + builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.Warehouse).WithMany().HasForeignKey(l => l.WarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.Batch).WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.Serial).WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(l => l.GrnLine).WithMany().HasForeignKey(l => l.GrnLineId).OnDelete(DeleteBehavior.Restrict); + + // FIFO consumption orders by receipt date then layer id, scoped per item+warehouse. + builder.HasIndex(l => new { l.ItemId, l.WarehouseId, l.ReceiptDate, l.LayerId }); + builder.HasIndex(l => l.GrnLineId); + } +} + +public sealed class StockLedgerConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + // Append-only (FR-STK-01/FR-X-05): the app never updates/deletes ledger rows. + // DB-level revocation of UPDATE/DELETE is a deferred hardening step (02-SECURITY B.3). + builder.ToTable("stock_ledger"); + builder.HasKey(l => l.LedgerId); + + builder.Property(l => l.Direction).HasConversion().HasMaxLength(5).IsRequired(); + builder.Property(l => l.QtyBase).HasPrecision(18, 4); + builder.Property(l => l.UnitCost).HasPrecision(18, 6); + builder.Property(l => l.Value).HasPrecision(18, 4); + builder.Property(l => l.RunningBalance).HasPrecision(18, 4); + builder.Property(l => l.SourceDocType).IsRequired().HasMaxLength(10); + builder.Property(l => l.CreatedAt).IsRequired(); + + builder.HasOne().WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.WarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.UserId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict); + + // Time-series query paths (NFR-06) and polymorphic source tracing. + builder.HasIndex(l => new { l.ItemId, l.WarehouseId, l.LedgerId }); + builder.HasIndex(l => new { l.ItemId, l.WarehouseId, l.CreatedAt }); + builder.HasIndex(l => new { l.SourceDocType, l.SourceDocId }); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/StockCountConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/StockCountConfiguration.cs new file mode 100644 index 0000000..fa44f42 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/StockCountConfiguration.cs @@ -0,0 +1,44 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class StockCountConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("stock_counts"); + builder.HasKey(c => c.CountId); + + builder.Property(c => c.DocNo).IsRequired().HasMaxLength(30); + builder.HasIndex(c => c.DocNo).IsUnique(); + + builder.Property(c => c.CountType).HasConversion().HasMaxLength(20).IsRequired(); + builder.Property(c => c.Status).HasConversion().HasMaxLength(20).IsRequired(); + builder.Property(c => c.CreatedAt).IsRequired(); + builder.Property(c => c.RowVersion).IsRowVersion(); + + builder.HasOne(c => c.Warehouse).WithMany().HasForeignKey(c => c.WarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(c => c.Creator).WithMany().HasForeignKey(c => c.CreatedBy).OnDelete(DeleteBehavior.Restrict); + + builder.HasIndex(c => c.Status); + } +} + +public sealed class StockCountLineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("stock_count_lines"); + builder.HasKey(l => l.CountLineId); + + builder.Property(l => l.SystemQty).HasPrecision(18, 4); + builder.Property(l => l.CountedQty).HasPrecision(18, 4); + builder.Property(l => l.Variance).HasPrecision(18, 4); + + builder.HasOne(l => l.Count).WithMany(c => c.Lines).HasForeignKey(l => l.CountId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/StockTransferConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/StockTransferConfiguration.cs new file mode 100644 index 0000000..2aae8bb --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/StockTransferConfiguration.cs @@ -0,0 +1,47 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +public sealed class StockTransferConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("stock_transfers"); + builder.HasKey(t => t.TransferId); + + builder.Property(t => t.DocNo).IsRequired().HasMaxLength(30); + builder.HasIndex(t => t.DocNo).IsUnique(); + + builder.Property(t => t.Status).HasConversion().HasMaxLength(20).IsRequired(); + builder.Property(t => t.CreatedAt).IsRequired(); + builder.Property(t => t.RowVersion).IsRowVersion(); + + builder.HasOne(t => t.SrcWarehouse).WithMany().HasForeignKey(t => t.SrcWarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(t => t.DestWarehouse).WithMany().HasForeignKey(t => t.DestWarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(t => t.Creator).WithMany().HasForeignKey(t => t.CreatedBy).OnDelete(DeleteBehavior.Restrict); + + builder.HasIndex(t => t.Status); + } +} + +public sealed class StockTransferLineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("stock_transfer_lines"); + builder.HasKey(l => l.TransferLineId); + + builder.Property(l => l.Qty).HasPrecision(18, 4); + builder.Property(l => l.UnitCost).HasPrecision(18, 6); + builder.Property(l => l.QtyReceived).HasPrecision(18, 4); + + builder.HasOne(l => l.Transfer).WithMany(t => t.Lines).HasForeignKey(l => l.TransferId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.SrcBinId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.DestBinId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/DataSeeder.cs b/Backend/ERPCore/Infra/Persistence/DataSeeder.cs new file mode 100644 index 0000000..59a8803 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/DataSeeder.cs @@ -0,0 +1,45 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Infra.Persistence; + +/// +/// Idempotent startup seeding of configurable reference data (docs/10 §B.8.3). +/// Reason codes are seeded at runtime (not via HasData) so the identity +/// sequence advances normally and later admin POST /reason-codes calls +/// cannot collide with seeded ids. +/// +public static class DataSeeder +{ + private static readonly (string Code, string Description, ReasonContext Context)[] StandardReasonCodes = + [ + ("DMG", "Damage", ReasonContext.Adjustment), + ("THEFT", "Theft/Loss", ReasonContext.Adjustment), + ("VAR", "Count Variance", ReasonContext.Adjustment), + ("EXP", "Expiry Write-off", ReasonContext.Adjustment), + ("SYS", "System Correction", ReasonContext.Adjustment), + ("DEF", "Defective", ReasonContext.Return), + ("WRONG", "Wrong Item", ReasonContext.Return), + ("OVER", "Over-supply", ReasonContext.Return), + ("QREJ", "Quality Reject", ReasonContext.Return), + ]; + + public static async Task SeedAsync(ErpDbContext db, CancellationToken ct = default) + { + var existing = await db.ReasonCodes + .Select(r => new { r.Context, r.Code }) + .ToListAsync(ct); + var have = existing.Select(x => (x.Context, x.Code)).ToHashSet(); + + var toAdd = StandardReasonCodes + .Where(r => !have.Contains((r.Context, r.Code))) + .Select(r => new ReasonCode { Code = r.Code, Description = r.Description, Context = r.Context }) + .ToList(); + + if (toAdd.Count == 0) return; + + db.ReasonCodes.AddRange(toAdd); + await db.SaveChangesAsync(ct); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs b/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs index d8bf5d5..6a7d7e6 100644 --- a/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs +++ b/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs @@ -1,4 +1,6 @@ using ERPCore.Domain.Entities; +using ERPCore.Infra.Auth; +using ERPCore.Infra.Persistence.Auditing; using Microsoft.EntityFrameworkCore; namespace ERPCore.Infra.Persistence; @@ -8,11 +10,15 @@ namespace ERPCore.Infra.Persistence; /// configurations are added under /// Domain/Entities and Infra/Persistence/Configurations as they are implemented. /// The authoritative schema lives in docs/10-BACKEND-PHASE1.md — do not invent it here. +/// Every save writes an immutable audit trail (FR-X-02) via . /// public class ErpDbContext : DbContext { - public ErpDbContext(DbContextOptions options) : base(options) + private readonly ICurrentUser _currentUser; + + public ErpDbContext(DbContextOptions options, ICurrentUser currentUser) : base(options) { + _currentUser = currentUser; } // --- Master Data (docs/10 Part C.1) --- @@ -39,6 +45,37 @@ public class ErpDbContext : DbContext public DbSet PurchaseOrders => Set(); public DbSet PoLines => Set(); + // --- Goods Receipt (docs/10 Part C.3) --- + public DbSet Grns => Set(); + public DbSet GrnLines => Set(); + + // --- Batch / Serial (docs/10 Part C.4) --- + public DbSet Batches => Set(); + public DbSet Serials => Set(); + + // --- Stock core: FIFO layers + immutable ledger (docs/10 Part C.5) --- + public DbSet StockLayers => Set(); + public DbSet StockLedger => Set(); + + // --- Stock transactions (docs/10 Part C.6) --- + public DbSet StockTransfers => Set(); + public DbSet StockTransferLines => Set(); + public DbSet StockAdjustments => Set(); + public DbSet StockAdjustmentLines => Set(); + public DbSet StockCounts => Set(); + public DbSet StockCountLines => Set(); + + // --- Purchase returns (docs/10 Part C.2) --- + public DbSet PurchaseReturns => Set(); + public DbSet PurchaseReturnLines => Set(); + + // --- Reference data (docs/10 Part C.7) --- + public DbSet ReasonCodes => Set(); + + // --- Cross-cutting: audit trail + GL-ready journal (docs/10 Part C.7) --- + public DbSet AuditLogs => Set(); + public DbSet JournalEntryStubs => Set(); + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -47,4 +84,39 @@ public class ErpDbContext : DbContext // (Infra/Persistence/Configurations/*). modelBuilder.ApplyConfigurationsFromAssembly(typeof(ErpDbContext).Assembly); } + + // Audit trail (FR-X-02): capture mutations before save (accurate old→new), then + // write the log rows once inserts have their generated keys. A second base save + // persists the logs without re-auditing them. + public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken ct = default) + { + var pending = AuditScribe.Capture(ChangeTracker); + var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct); + if (pending.Count > 0) + { + WriteAuditLogs(pending); + await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct); + } + return result; + } + + public override int SaveChanges(bool acceptAllChangesOnSuccess) + { + var pending = AuditScribe.Capture(ChangeTracker); + var result = base.SaveChanges(acceptAllChangesOnSuccess); + if (pending.Count > 0) + { + WriteAuditLogs(pending); + base.SaveChanges(acceptAllChangesOnSuccess); + } + return result; + } + + private void WriteAuditLogs(IReadOnlyList pending) + { + var now = DateTime.UtcNow; + var userId = _currentUser.AuditUserId; + foreach (var p in pending) + AuditLogs.Add(AuditScribe.ToLog(p, userId, now)); + } } diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.Designer.cs new file mode 100644 index 0000000..d10be22 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.Designer.cs @@ -0,0 +1,1480 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20260713054124_AddStockAndGrn")] + partial class AddStockAndGrn + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.HasKey("CategoryId"); + + b.HasIndex("ParentId"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("GrnId") + .HasColumnType("bigint"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoLineId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + + b.Property("BaseUomId") + .HasColumnType("bigint"); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultVendorId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxClass") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ItemId"); + + b.HasIndex("BaseUomId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("bigint") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("integer") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("DocType", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("PoId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.Property("ConversionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + + b.Property("Factor") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("FromUomId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ToUomId") + .HasColumnType("bigint"); + + b.HasKey("ConversionId"); + + b.HasIndex("FromUomId"); + + b.HasIndex("ToUomId"); + + b.HasIndex("ItemId", "FromUomId", "ToUomId") + .IsUnique(); + + b.ToTable("uom_conversions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("UserId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1L, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("character varying(3)") + .HasDefaultValue("LKR"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("VendorId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("LeadDays") + .HasColumnType("integer"); + + b.Property("QuotationId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("WarehouseId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") + .WithMany() + .HasForeignKey("FromUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("UomConversions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") + .WithMany() + .HasForeignKey("ToUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + + b.Navigation("UomConversions"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.cs new file mode 100644 index 0000000..a43fc8c --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.cs @@ -0,0 +1,434 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + /// + public partial class AddStockAndGrn : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "batches", + columns: table => new + { + BatchId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "bigint", nullable: false), + BatchNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + ExpiryDate = table.Column(type: "date", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_batches", x => x.BatchId); + table.ForeignKey( + name: "FK_batches_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "grns", + columns: table => new + { + GrnId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + PoId = table.Column(type: "bigint", nullable: true), + VendorId = table.Column(type: "bigint", nullable: false), + WarehouseId = table.Column(type: "bigint", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + PostedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_grns", x => x.GrnId); + table.ForeignKey( + name: "FK_grns_purchase_orders_PoId", + column: x => x.PoId, + principalTable: "purchase_orders", + principalColumn: "PoId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "serials", + columns: table => new + { + SerialId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "bigint", nullable: false), + SerialNo = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_serials", x => x.SerialId); + table.ForeignKey( + name: "FK_serials_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "grn_lines", + columns: table => new + { + GrnLineId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + GrnId = table.Column(type: "bigint", nullable: false), + PoLineId = table.Column(type: "bigint", nullable: true), + ItemId = table.Column(type: "bigint", nullable: false), + UomId = table.Column(type: "bigint", nullable: false), + BinId = table.Column(type: "bigint", nullable: true), + BatchId = table.Column(type: "bigint", nullable: true), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + ReceivedValue = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + HoldStatus = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_grn_lines", x => x.GrnLineId); + table.ForeignKey( + name: "FK_grn_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_grns_GrnId", + column: x => x.GrnId, + principalTable: "grns", + principalColumn: "GrnId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_grn_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_po_lines_PoLineId", + column: x => x.PoLineId, + principalTable: "po_lines", + principalColumn: "PoLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_uoms_UomId", + column: x => x.UomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_ledger", + columns: table => new + { + LedgerId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "bigint", nullable: false), + WarehouseId = table.Column(type: "bigint", nullable: false), + BinId = table.Column(type: "bigint", nullable: true), + BatchId = table.Column(type: "bigint", nullable: true), + SerialId = table.Column(type: "bigint", nullable: true), + UserId = table.Column(type: "bigint", nullable: false), + Direction = table.Column(type: "character varying(5)", maxLength: 5, nullable: false), + QtyBase = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + Value = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + RunningBalance = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + SourceDocType = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + SourceDocId = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_ledger", x => x.LedgerId); + table.ForeignKey( + name: "FK_stock_ledger_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_users_UserId", + column: x => x.UserId, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_layers", + columns: table => new + { + LayerId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "bigint", nullable: false), + WarehouseId = table.Column(type: "bigint", nullable: false), + BatchId = table.Column(type: "bigint", nullable: true), + SerialId = table.Column(type: "bigint", nullable: true), + GrnLineId = table.Column(type: "bigint", nullable: true), + QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + QtyRemaining = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + ReceiptDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_layers", x => x.LayerId); + table.ForeignKey( + name: "FK_stock_layers_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_grn_lines_GrnLineId", + column: x => x.GrnLineId, + principalTable: "grn_lines", + principalColumn: "GrnLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_batches_ItemId_BatchNo", + table: "batches", + columns: new[] { "ItemId", "BatchNo" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_BatchId", + table: "grn_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_BinId", + table: "grn_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_GrnId", + table: "grn_lines", + column: "GrnId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_ItemId", + table: "grn_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_PoLineId", + table: "grn_lines", + column: "PoLineId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_UomId", + table: "grn_lines", + column: "UomId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_CreatedBy", + table: "grns", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_grns_DocNo", + table: "grns", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_grns_PoId", + table: "grns", + column: "PoId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_Status", + table: "grns", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_grns_VendorId", + table: "grns", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_WarehouseId", + table: "grns", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_serials_ItemId_SerialNo", + table: "serials", + columns: new[] { "ItemId", "SerialNo" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_BatchId", + table: "stock_layers", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_GrnLineId", + table: "stock_layers", + column: "GrnLineId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_ItemId_WarehouseId_ReceiptDate_LayerId", + table: "stock_layers", + columns: new[] { "ItemId", "WarehouseId", "ReceiptDate", "LayerId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_SerialId", + table: "stock_layers", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_WarehouseId", + table: "stock_layers", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_BatchId", + table: "stock_ledger", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_BinId", + table: "stock_ledger", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_ItemId_WarehouseId_CreatedAt", + table: "stock_ledger", + columns: new[] { "ItemId", "WarehouseId", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_ItemId_WarehouseId_LedgerId", + table: "stock_ledger", + columns: new[] { "ItemId", "WarehouseId", "LedgerId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_SerialId", + table: "stock_ledger", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_SourceDocType_SourceDocId", + table: "stock_ledger", + columns: new[] { "SourceDocType", "SourceDocId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_UserId", + table: "stock_ledger", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_WarehouseId", + table: "stock_ledger", + column: "WarehouseId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "stock_layers"); + + migrationBuilder.DropTable( + name: "stock_ledger"); + + migrationBuilder.DropTable( + name: "grn_lines"); + + migrationBuilder.DropTable( + name: "serials"); + + migrationBuilder.DropTable( + name: "batches"); + + migrationBuilder.DropTable( + name: "grns"); + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.Designer.cs new file mode 100644 index 0000000..ff45314 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.Designer.cs @@ -0,0 +1,1847 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20260713061141_AddStockTransactions")] + partial class AddStockTransactions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.HasKey("CategoryId"); + + b.HasIndex("ParentId"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("GrnId") + .HasColumnType("bigint"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoLineId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + + b.Property("BaseUomId") + .HasColumnType("bigint"); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultVendorId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxClass") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ItemId"); + + b.HasIndex("BaseUomId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("bigint") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("integer") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("DocType", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("PoId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("bigint"); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DestWarehouseId") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SrcWarehouseId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("DestBinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SrcBinId") + .HasColumnType("bigint"); + + b.Property("TransferId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.Property("ConversionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + + b.Property("Factor") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("FromUomId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ToUomId") + .HasColumnType("bigint"); + + b.HasKey("ConversionId"); + + b.HasIndex("FromUomId"); + + b.HasIndex("ToUomId"); + + b.HasIndex("ItemId", "FromUomId", "ToUomId") + .IsUnique(); + + b.ToTable("uom_conversions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("UserId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1L, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("character varying(3)") + .HasDefaultValue("LKR"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("VendorId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("LeadDays") + .HasColumnType("integer"); + + b.Property("QuotationId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("WarehouseId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") + .WithMany() + .HasForeignKey("FromUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("UomConversions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") + .WithMany() + .HasForeignKey("ToUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + + b.Navigation("UomConversions"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.cs new file mode 100644 index 0000000..08a79bd --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.cs @@ -0,0 +1,337 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + /// + public partial class AddStockTransactions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "reason_codes", + columns: table => new + { + ReasonCodeId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Context = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_reason_codes", x => x.ReasonCodeId); + }); + + migrationBuilder.CreateTable( + name: "stock_transfers", + columns: table => new + { + TransferId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + SrcWarehouseId = table.Column(type: "bigint", nullable: false), + DestWarehouseId = table.Column(type: "bigint", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_transfers", x => x.TransferId); + table.ForeignKey( + name: "FK_stock_transfers_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfers_warehouses_DestWarehouseId", + column: x => x.DestWarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfers_warehouses_SrcWarehouseId", + column: x => x.SrcWarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_adjustments", + columns: table => new + { + AdjustmentId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + WarehouseId = table.Column(type: "bigint", nullable: false), + ReasonCodeId = table.Column(type: "bigint", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_adjustments", x => x.AdjustmentId); + table.ForeignKey( + name: "FK_stock_adjustments_reason_codes_ReasonCodeId", + column: x => x.ReasonCodeId, + principalTable: "reason_codes", + principalColumn: "ReasonCodeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustments_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustments_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_transfer_lines", + columns: table => new + { + TransferLineId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + TransferId = table.Column(type: "bigint", nullable: false), + ItemId = table.Column(type: "bigint", nullable: false), + SrcBinId = table.Column(type: "bigint", nullable: true), + DestBinId = table.Column(type: "bigint", nullable: true), + BatchId = table.Column(type: "bigint", nullable: true), + SerialId = table.Column(type: "bigint", nullable: true), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: true), + QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_transfer_lines", x => x.TransferLineId); + table.ForeignKey( + name: "FK_stock_transfer_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_bins_DestBinId", + column: x => x.DestBinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_bins_SrcBinId", + column: x => x.SrcBinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_stock_transfers_TransferId", + column: x => x.TransferId, + principalTable: "stock_transfers", + principalColumn: "TransferId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "stock_adjustment_lines", + columns: table => new + { + AdjLineId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + AdjustmentId = table.Column(type: "bigint", nullable: false), + ItemId = table.Column(type: "bigint", nullable: false), + BinId = table.Column(type: "bigint", nullable: true), + BatchId = table.Column(type: "bigint", nullable: true), + SerialId = table.Column(type: "bigint", nullable: true), + QtyDelta = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_adjustment_lines", x => x.AdjLineId); + table.ForeignKey( + name: "FK_stock_adjustment_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_stock_adjustments_AdjustmentId", + column: x => x.AdjustmentId, + principalTable: "stock_adjustments", + principalColumn: "AdjustmentId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_reason_codes_Context_Code", + table: "reason_codes", + columns: new[] { "Context", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_AdjustmentId", + table: "stock_adjustment_lines", + column: "AdjustmentId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_BatchId", + table: "stock_adjustment_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_BinId", + table: "stock_adjustment_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_ItemId", + table: "stock_adjustment_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_SerialId", + table: "stock_adjustment_lines", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_CreatedBy", + table: "stock_adjustments", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_DocNo", + table: "stock_adjustments", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_ReasonCodeId", + table: "stock_adjustments", + column: "ReasonCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_WarehouseId", + table: "stock_adjustments", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_BatchId", + table: "stock_transfer_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_DestBinId", + table: "stock_transfer_lines", + column: "DestBinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_ItemId", + table: "stock_transfer_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_SerialId", + table: "stock_transfer_lines", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_SrcBinId", + table: "stock_transfer_lines", + column: "SrcBinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_TransferId", + table: "stock_transfer_lines", + column: "TransferId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_CreatedBy", + table: "stock_transfers", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_DestWarehouseId", + table: "stock_transfers", + column: "DestWarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_DocNo", + table: "stock_transfers", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_SrcWarehouseId", + table: "stock_transfers", + column: "SrcWarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_Status", + table: "stock_transfers", + column: "Status"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "stock_adjustment_lines"); + + migrationBuilder.DropTable( + name: "stock_transfer_lines"); + + migrationBuilder.DropTable( + name: "stock_adjustments"); + + migrationBuilder.DropTable( + name: "stock_transfers"); + + migrationBuilder.DropTable( + name: "reason_codes"); + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.Designer.cs new file mode 100644 index 0000000..15cd145 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.Designer.cs @@ -0,0 +1,2134 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20260713105512_AddCountsAndReturns")] + partial class AddCountsAndReturns + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.HasKey("CategoryId"); + + b.HasIndex("ParentId"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("GrnId") + .HasColumnType("bigint"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoLineId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + + b.Property("BaseUomId") + .HasColumnType("bigint"); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultVendorId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxClass") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ItemId"); + + b.HasIndex("BaseUomId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("bigint") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("integer") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("DocType", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("PoId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReturnId") + .HasColumnType("bigint"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("bigint"); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("CountId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CountId") + .HasColumnType("bigint"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DestWarehouseId") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SrcWarehouseId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("DestBinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SrcBinId") + .HasColumnType("bigint"); + + b.Property("TransferId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.Property("ConversionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + + b.Property("Factor") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("FromUomId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ToUomId") + .HasColumnType("bigint"); + + b.HasKey("ConversionId"); + + b.HasIndex("FromUomId"); + + b.HasIndex("ToUomId"); + + b.HasIndex("ItemId", "FromUomId", "ToUomId") + .IsUnique(); + + b.ToTable("uom_conversions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("UserId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1L, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("character varying(3)") + .HasDefaultValue("LKR"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("VendorId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("LeadDays") + .HasColumnType("integer"); + + b.Property("QuotationId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("WarehouseId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Count"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") + .WithMany() + .HasForeignKey("FromUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("UomConversions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") + .WithMany() + .HasForeignKey("ToUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + + b.Navigation("UomConversions"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.cs new file mode 100644 index 0000000..bc674d9 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.cs @@ -0,0 +1,253 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + /// + public partial class AddCountsAndReturns : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "purchase_returns", + columns: table => new + { + ReturnId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + VendorId = table.Column(type: "bigint", nullable: false), + WarehouseId = table.Column(type: "bigint", nullable: false), + ReasonCodeId = table.Column(type: "bigint", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_purchase_returns", x => x.ReturnId); + table.ForeignKey( + name: "FK_purchase_returns_reason_codes_ReasonCodeId", + column: x => x.ReasonCodeId, + principalTable: "reason_codes", + principalColumn: "ReasonCodeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_counts", + columns: table => new + { + CountId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + WarehouseId = table.Column(type: "bigint", nullable: false), + CountType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_counts", x => x.CountId); + table.ForeignKey( + name: "FK_stock_counts_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_counts_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "purchase_return_lines", + columns: table => new + { + ReturnLineId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ReturnId = table.Column(type: "bigint", nullable: false), + GrnLineId = table.Column(type: "bigint", nullable: true), + ItemId = table.Column(type: "bigint", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_purchase_return_lines", x => x.ReturnLineId); + table.ForeignKey( + name: "FK_purchase_return_lines_grn_lines_GrnLineId", + column: x => x.GrnLineId, + principalTable: "grn_lines", + principalColumn: "GrnLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_return_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_return_lines_purchase_returns_ReturnId", + column: x => x.ReturnId, + principalTable: "purchase_returns", + principalColumn: "ReturnId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "stock_count_lines", + columns: table => new + { + CountLineId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CountId = table.Column(type: "bigint", nullable: false), + ItemId = table.Column(type: "bigint", nullable: false), + BinId = table.Column(type: "bigint", nullable: true), + SystemQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + CountedQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true), + Variance = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_count_lines", x => x.CountLineId); + table.ForeignKey( + name: "FK_stock_count_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_count_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_count_lines_stock_counts_CountId", + column: x => x.CountId, + principalTable: "stock_counts", + principalColumn: "CountId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_GrnLineId", + table: "purchase_return_lines", + column: "GrnLineId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_ItemId", + table: "purchase_return_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_ReturnId", + table: "purchase_return_lines", + column: "ReturnId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_CreatedBy", + table: "purchase_returns", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_DocNo", + table: "purchase_returns", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_ReasonCodeId", + table: "purchase_returns", + column: "ReasonCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_VendorId", + table: "purchase_returns", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_WarehouseId", + table: "purchase_returns", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_BinId", + table: "stock_count_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_CountId", + table: "stock_count_lines", + column: "CountId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_ItemId", + table: "stock_count_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_CreatedBy", + table: "stock_counts", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_DocNo", + table: "stock_counts", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_Status", + table: "stock_counts", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_WarehouseId", + table: "stock_counts", + column: "WarehouseId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "purchase_return_lines"); + + migrationBuilder.DropTable( + name: "stock_count_lines"); + + migrationBuilder.DropTable( + name: "purchase_returns"); + + migrationBuilder.DropTable( + name: "stock_counts"); + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.Designer.cs new file mode 100644 index 0000000..04650de --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.Designer.cs @@ -0,0 +1,2222 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20260713121825_AddAuditAndJournal")] + partial class AddAuditAndJournal + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EntityId") + .HasColumnType("bigint"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.HasKey("CategoryId"); + + b.HasIndex("ParentId"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("GrnId") + .HasColumnType("bigint"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoLineId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + + b.Property("BaseUomId") + .HasColumnType("bigint"); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultVendorId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxClass") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ItemId"); + + b.HasIndex("BaseUomId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("bigint") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("integer") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("DocType", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("PoId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReturnId") + .HasColumnType("bigint"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("bigint"); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("CountId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CountId") + .HasColumnType("bigint"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DestWarehouseId") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SrcWarehouseId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("DestBinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SrcBinId") + .HasColumnType("bigint"); + + b.Property("TransferId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.Property("ConversionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + + b.Property("Factor") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("FromUomId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ToUomId") + .HasColumnType("bigint"); + + b.HasKey("ConversionId"); + + b.HasIndex("FromUomId"); + + b.HasIndex("ToUomId"); + + b.HasIndex("ItemId", "FromUomId", "ToUomId") + .IsUnique(); + + b.ToTable("uom_conversions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("UserId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1L, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("character varying(3)") + .HasDefaultValue("LKR"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("VendorId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("LeadDays") + .HasColumnType("integer"); + + b.Property("QuotationId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("WarehouseId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Count"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") + .WithMany() + .HasForeignKey("FromUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("UomConversions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") + .WithMany() + .HasForeignKey("ToUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + + b.Navigation("UomConversions"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.cs new file mode 100644 index 0000000..0db846e --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.cs @@ -0,0 +1,87 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + /// + public partial class AddAuditAndJournal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "audit_logs", + columns: table => new + { + AuditId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "bigint", nullable: false), + EntityType = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + EntityId = table.Column(type: "bigint", nullable: false), + Action = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + ChangeSet = table.Column(type: "jsonb", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_audit_logs", x => x.AuditId); + table.ForeignKey( + name: "FK_audit_logs_users_UserId", + column: x => x.UserId, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "journal_entry_stubs", + columns: table => new + { + JournalId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SourceDocType = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + SourceDocId = table.Column(type: "bigint", nullable: false), + DebitAccount = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreditAccount = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Amount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_journal_entry_stubs", x => x.JournalId); + }); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_CreatedAt", + table: "audit_logs", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_EntityType_EntityId", + table: "audit_logs", + columns: new[] { "EntityType", "EntityId" }); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_UserId", + table: "audit_logs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_journal_entry_stubs_SourceDocType_SourceDocId", + table: "journal_entry_stubs", + columns: new[] { "SourceDocType", "SourceDocId" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "audit_logs"); + + migrationBuilder.DropTable( + name: "journal_entry_stubs"); + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs b/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs index bf2c3f1..0d3e800 100644 --- a/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs +++ b/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs @@ -22,6 +22,75 @@ namespace ERPCore.Infra.Persistence.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EntityId") + .HasColumnType("bigint"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => { b.Property("BinId") @@ -73,6 +142,126 @@ namespace ERPCore.Infra.Persistence.Migrations b.ToTable("categories", (string)null); }); + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("GrnId") + .HasColumnType("bigint"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoLineId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => { b.Property("ItemId") @@ -185,6 +374,43 @@ namespace ERPCore.Infra.Persistence.Migrations b.ToTable("item_reorders", (string)null); }); + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => { b.Property("SequenceId") @@ -322,6 +548,118 @@ namespace ERPCore.Infra.Persistence.Migrations b.ToTable("purchase_orders", (string)null); }); + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReturnId") + .HasColumnType("bigint"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => { b.Property("RequisitionId") @@ -449,6 +787,458 @@ namespace ERPCore.Infra.Persistence.Migrations b.ToTable("rfq_lines", (string)null); }); + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("bigint"); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("CountId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CountId") + .HasColumnType("bigint"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DestWarehouseId") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SrcWarehouseId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("DestBinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SrcBinId") + .HasColumnType("bigint"); + + b.Property("TransferId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => { b.Property("UomId") @@ -688,6 +1478,26 @@ namespace ERPCore.Infra.Persistence.Migrations b.ToTable("warehouses", (string)null); }); + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => { b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") @@ -709,6 +1519,88 @@ namespace ERPCore.Infra.Persistence.Migrations b.Navigation("Parent"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => { b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") @@ -815,6 +1707,67 @@ namespace ERPCore.Infra.Persistence.Migrations b.Navigation("Vendor"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => { b.HasOne("ERPCore.Domain.Entities.User", "Requester") @@ -875,6 +1828,263 @@ namespace ERPCore.Infra.Persistence.Migrations b.Navigation("Rfq"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Count"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => { b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") @@ -945,6 +2155,11 @@ namespace ERPCore.Infra.Persistence.Migrations b.Navigation("Children"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => { b.Navigation("ReorderSettings"); @@ -957,6 +2172,11 @@ namespace ERPCore.Infra.Persistence.Migrations b.Navigation("Lines"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => { b.Navigation("Lines"); @@ -969,6 +2189,21 @@ namespace ERPCore.Infra.Persistence.Migrations b.Navigation("Quotations"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => { b.Navigation("Lines"); diff --git a/Backend/ERPCore/Program.cs b/Backend/ERPCore/Program.cs index 73b5f1a..d03cea2 100644 --- a/Backend/ERPCore/Program.cs +++ b/Backend/ERPCore/Program.cs @@ -6,6 +6,7 @@ using ERPCore.Repositories; using ERPCore.Repositories.Interfaces; using ERPCore.Services; using ERPCore.Services.Interfaces; +using ERPCore.Services.Stock; using ERPCore.System.Errors; using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi; @@ -54,6 +55,23 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +// Stock core + goods receipt (docs/11 §4–5) +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// Stock transactions + reference data (docs/11 §5–6) +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// Cross-cutting: audit trail + GL-ready journal read access (docs/11 §6 / FR-X-02, FR-STK-13) +builder.Services.AddScoped(); + // Health checks (EF Core DB) builder.Services.AddHealthChecks().AddDbContextCheck(); @@ -63,6 +81,13 @@ builder.Services.AddSwaggerGen(o => o.SwaggerDoc("v1", new OpenApiInfo { Title = var app = builder.Build(); +// Seed configurable reference data (reason codes) idempotently at startup. +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + await DataSeeder.SeedAsync(db); +} + app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) diff --git a/Backend/ERPCore/Services/AdjustmentService.cs b/Backend/ERPCore/Services/AdjustmentService.cs new file mode 100644 index 0000000..53604f7 --- /dev/null +++ b/Backend/ERPCore/Services/AdjustmentService.cs @@ -0,0 +1,104 @@ +using ERPCore.Domain; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Stock; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Stock-adjustment service — the highest-risk feature (02-SECURITY C.5). Auto-posts +/// with a mandatory reason code and user stamp. Line application (FIFO consume on a +/// decrease, layer create on an increase) + ledger posting is delegated to +/// . Runs in a single UoW transaction (NFR-02/05). +/// +public sealed class AdjustmentService : IAdjustmentService +{ + private readonly IRepository _adjustments; + private readonly IRepository _warehouses; + private readonly IRepository _items; + private readonly IRepository _reasonCodes; + private readonly IStockMutator _mutator; + private readonly INumberSequenceService _numbers; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public AdjustmentService( + IRepository adjustments, IRepository warehouses, IRepository items, + IRepository reasonCodes, IStockMutator mutator, INumberSequenceService numbers, + ICurrentUser currentUser, IUnitOfWork uow) + { + _adjustments = adjustments; + _warehouses = warehouses; + _items = items; + _reasonCodes = reasonCodes; + _mutator = mutator; + _numbers = numbers; + _currentUser = currentUser; + _uow = uow; + } + + public async Task CreateAsync(CreateAdjustmentRequest request, CancellationToken ct = default) + { + if (request.ReasonCodeId is null) + throw new DomainException(ErrorCodes.ReasonCodeRequired, "A reason code is required for an adjustment.", 400); + + if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == request.WarehouseId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Warehouse {request.WarehouseId} does not exist.", 422); + + var reason = await _reasonCodes.Query().AsNoTracking().FirstOrDefaultAsync(r => r.ReasonCodeId == request.ReasonCodeId, ct) + ?? throw new DomainException(ErrorCodes.Validation, $"Reason code {request.ReasonCodeId} does not exist.", 422); + if (reason.Context != ReasonContext.Adjustment) + throw new DomainException(ErrorCodes.Validation, $"Reason code {request.ReasonCodeId} is not an Adjustment reason.", 422); + + foreach (var line in request.Lines) + { + if (line.QtyDelta == 0) + throw new DomainException(ErrorCodes.Validation, "qtyDelta cannot be zero.", 422); + if (!await _items.Query().AnyAsync(i => i.ItemId == line.ItemId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Item {line.ItemId} does not exist.", 422); + } + + var now = DateTime.UtcNow; + var deltas = request.Lines + .Select(l => new StockDelta(l.ItemId, l.BinId, l.BatchId, l.QtyDelta)).ToList(); + + var (adjustment, ledgerRefs) = await _uow.ExecuteInTransactionAsync(async token => + { + var docNo = await _numbers.NextAsync(DocumentTypes.Adjustment, token); + var entity = new StockAdjustment + { + DocNo = docNo, + WarehouseId = request.WarehouseId, + ReasonCodeId = request.ReasonCodeId.Value, + Status = AdjustmentStatus.Posted, + CreatedBy = _currentUser.AuditUserId, + CreatedAt = now, + Lines = request.Lines.Select(l => new StockAdjustmentLine + { + ItemId = l.ItemId, + BinId = l.BinId, + BatchId = l.BatchId, + QtyDelta = l.QtyDelta + }).ToList() + }; + await _adjustments.AddAsync(entity, token); + await _uow.SaveChangesAsync(token); // flush so AdjustmentId is a valid ledger sourceDocId + + var refs = await _mutator.ApplyAsync(request.WarehouseId, DocumentTypes.Adjustment, entity.AdjustmentId, now, deltas, token); + return (entity, refs); + }, ct); + + return new AdjustmentDto( + adjustment.AdjustmentId, adjustment.DocNo, adjustment.WarehouseId, adjustment.ReasonCodeId, + adjustment.Status, adjustment.CreatedBy, adjustment.CreatedAt, + adjustment.Lines.OrderBy(l => l.AdjLineId) + .Select(l => new AdjustmentLineDto(l.AdjLineId, l.ItemId, l.BinId, l.BatchId, l.QtyDelta)).ToList(), + ledgerRefs.Select(l => l.LedgerId).ToList()); + } +} diff --git a/Backend/ERPCore/Services/AuditService.cs b/Backend/ERPCore/Services/AuditService.cs new file mode 100644 index 0000000..49290a1 --- /dev/null +++ b/Backend/ERPCore/Services/AuditService.cs @@ -0,0 +1,58 @@ +using System.Text.Json; +using ERPCore.Domain.Entities; +using ERPCore.Dtos.Audit; +using ERPCore.Dtos.Common; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +public sealed class AuditService : IAuditService +{ + private readonly IRepository _logs; + private readonly IRepository _journal; + + public AuditService(IRepository logs, IRepository journal) + { + _logs = logs; + _journal = journal; + } + + public async Task> ListLogsAsync( + string? entityType, long? entityId, long? userId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default) + { + var q = _logs.Query().AsNoTracking(); + if (!string.IsNullOrWhiteSpace(entityType)) q = q.Where(l => l.EntityType == entityType); + if (entityId is not null) q = q.Where(l => l.EntityId == entityId); + if (userId is not null) q = q.Where(l => l.UserId == userId); + if (from is not null) q = q.Where(l => l.CreatedAt >= from.Value.ToDateTime(TimeOnly.MinValue)); + if (to is not null) q = q.Where(l => l.CreatedAt < to.Value.AddDays(1).ToDateTime(TimeOnly.MinValue)); + + var total = await q.CountAsync(ct); + var rows = await q.OrderByDescending(l => l.AuditId) + .Skip(query.Skip).Take(query.PageSize).ToListAsync(ct); + + var dtos = rows.Select(l => new AuditLogDto( + l.AuditId, l.UserId, l.EntityType, l.EntityId, l.Action, + JsonSerializer.Deserialize(l.ChangeSet), l.CreatedAt)).ToList(); + + return PagedResponse.Create(dtos, query.Page, query.PageSize, total); + } + + public async Task> ListJournalAsync( + string? sourceDocType, long? sourceDocId, PageQuery query, CancellationToken ct = default) + { + var q = _journal.Query().AsNoTracking(); + if (!string.IsNullOrWhiteSpace(sourceDocType)) q = q.Where(j => j.SourceDocType == sourceDocType); + if (sourceDocId is not null) q = q.Where(j => j.SourceDocId == sourceDocId); + + var total = await q.CountAsync(ct); + var rows = await q.OrderByDescending(j => j.JournalId) + .Skip(query.Skip).Take(query.PageSize) + .Select(j => new JournalEntryStubDto(j.JournalId, j.SourceDocType, j.SourceDocId, j.DebitAccount, j.CreditAccount, j.Amount)) + .ToListAsync(ct); + + return PagedResponse.Create(rows, query.Page, query.PageSize, total); + } +} diff --git a/Backend/ERPCore/Services/CountService.cs b/Backend/ERPCore/Services/CountService.cs new file mode 100644 index 0000000..3e34b26 --- /dev/null +++ b/Backend/ERPCore/Services/CountService.cs @@ -0,0 +1,181 @@ +using ERPCore.Domain; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Stock; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Stock-count service (FR-STK-08). Create snapshots system quantities (immutable, +/// 02-SECURITY C.7); posting emits a variance via the +/// shared (a variance is an adjustment in disguise, C.7) +/// and closes the count — all in one UoW transaction. +/// +public sealed class CountService : ICountService +{ + private const string VarianceReasonCode = "VAR"; + + private readonly IRepository _counts; + private readonly IRepository _adjustments; + private readonly IRepository _warehouses; + private readonly IRepository _items; + private readonly IRepository _reasonCodes; + private readonly IFifoCostingService _fifo; + private readonly IStockMutator _mutator; + private readonly INumberSequenceService _numbers; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public CountService( + IRepository counts, IRepository adjustments, IRepository warehouses, + IRepository items, IRepository reasonCodes, IFifoCostingService fifo, IStockMutator mutator, + INumberSequenceService numbers, ICurrentUser currentUser, IUnitOfWork uow) + { + _counts = counts; + _adjustments = adjustments; + _warehouses = warehouses; + _items = items; + _reasonCodes = reasonCodes; + _fifo = fifo; + _mutator = mutator; + _numbers = numbers; + _currentUser = currentUser; + _uow = uow; + } + + public async Task GetAsync(long countId, CancellationToken ct = default) + { + var count = await _counts.Query().AsNoTracking().Include(c => c.Lines) + .FirstOrDefaultAsync(c => c.CountId == countId, ct); + return count is null ? null : Map(count); + } + + public async Task CreateAsync(CreateCountRequest request, CancellationToken ct = default) + { + if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == request.WarehouseId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Warehouse {request.WarehouseId} does not exist.", 422); + + var itemIds = request.ItemIds.Distinct().ToList(); + foreach (var id in itemIds) + if (!await _items.Query().AnyAsync(i => i.ItemId == id, ct)) + throw new DomainException(ErrorCodes.Validation, $"Item {id} does not exist.", 422); + + // Snapshot system quantities now (immutable once opened). + var lines = new List(); + foreach (var id in itemIds) + { + var systemQty = await _fifo.GetOnHandAsync(id, request.WarehouseId, ct); + lines.Add(new StockCountLine { ItemId = id, SystemQty = systemQty }); + } + + var count = await _uow.ExecuteInTransactionAsync(async token => + { + var docNo = await _numbers.NextAsync(DocumentTypes.Count, token); + var entity = new StockCount + { + DocNo = docNo, + WarehouseId = request.WarehouseId, + CountType = request.CountType, + Status = CountStatus.Draft, + CreatedBy = _currentUser.AuditUserId, + CreatedAt = DateTime.UtcNow, + Lines = lines + }; + await _counts.AddAsync(entity, token); + return entity; + }, ct); + + return Map(count); + } + + public async Task EnterCountsAsync(long countId, EnterCountsRequest request, CancellationToken ct = default) + { + var count = await _counts.Query().Include(c => c.Lines) + .FirstOrDefaultAsync(c => c.CountId == countId, ct) + ?? throw new NotFoundException($"Count {countId} was not found."); + if (count.Status == CountStatus.Posted) + throw new ConflictException($"Count {countId} is already posted."); + + foreach (var input in request.Lines) + { + var line = count.Lines.FirstOrDefault(l => l.CountLineId == input.CountLineId) + ?? throw new DomainException(ErrorCodes.Validation, $"Count line {input.CountLineId} is not on count {countId}.", 422); + line.CountedQty = input.CountedQty; + line.Variance = input.CountedQty - line.SystemQty; + } + + count.Status = CountStatus.Counted; + await _uow.SaveChangesAsync(ct); + return Map(count); + } + + public async Task PostAsync(long countId, CancellationToken ct = default) + { + var count = await _counts.Query().Include(c => c.Lines) + .FirstOrDefaultAsync(c => c.CountId == countId, ct) + ?? throw new NotFoundException($"Count {countId} was not found."); + if (count.Status == CountStatus.Posted) + throw new ConflictException($"Count {countId} is already posted."); + if (count.Status != CountStatus.Counted) + throw new ConflictException($"Count {countId} has no entered counts to post."); + + var deltas = count.Lines + .Where(l => l.Variance.HasValue && l.Variance.Value != 0) + .Select(l => new StockDelta(l.ItemId, l.BinId, null, l.Variance!.Value)) + .ToList(); + + // No variances → just close the count, no adjustment. + if (deltas.Count == 0) + { + count.Status = CountStatus.Posted; + await _uow.SaveChangesAsync(ct); + return new CountPostResultDto(count.CountId, count.Status, null, Array.Empty()); + } + + var reason = await _reasonCodes.Query().AsNoTracking() + .FirstOrDefaultAsync(r => r.Context == ReasonContext.Adjustment && r.Code == VarianceReasonCode, ct) + ?? throw new DomainException(ErrorCodes.Validation, $"Reason code '{VarianceReasonCode}' (Count Variance) is not configured.", 422); + + var now = DateTime.UtcNow; + var (adjustmentId, ledgerEntries) = await _uow.ExecuteInTransactionAsync(async token => + { + var docNo = await _numbers.NextAsync(DocumentTypes.Adjustment, token); + var adjustment = new StockAdjustment + { + DocNo = docNo, + WarehouseId = count.WarehouseId, + ReasonCodeId = reason.ReasonCodeId, + Status = AdjustmentStatus.Posted, + CreatedBy = _currentUser.AuditUserId, + CreatedAt = now, + Lines = deltas.Select(d => new StockAdjustmentLine + { + ItemId = d.ItemId, + BinId = d.BinId, + QtyDelta = d.QtyDelta + }).ToList() + }; + await _adjustments.AddAsync(adjustment, token); + await _uow.SaveChangesAsync(token); // flush for a valid ledger sourceDocId + + var refs = await _mutator.ApplyAsync(count.WarehouseId, DocumentTypes.Adjustment, adjustment.AdjustmentId, now, deltas, token); + + count.Status = CountStatus.Posted; + return (adjustment.AdjustmentId, refs); + }, ct); + + // Map ledger ids after commit so they are populated. + return new CountPostResultDto(count.CountId, count.Status, adjustmentId, ledgerEntries.Select(r => r.LedgerId).ToList()); + } + + private static CountDto Map(StockCount c) => new( + c.CountId, c.DocNo, c.WarehouseId, c.CountType, c.Status, + c.Lines.OrderBy(l => l.CountLineId) + .Select(l => new CountLineDto(l.CountLineId, l.ItemId, l.BinId, l.SystemQty, l.CountedQty, l.Variance)).ToList()); +} diff --git a/Backend/ERPCore/Services/GrnService.cs b/Backend/ERPCore/Services/GrnService.cs new file mode 100644 index 0000000..fdfa83e --- /dev/null +++ b/Backend/ERPCore/Services/GrnService.cs @@ -0,0 +1,357 @@ +using ERPCore.Domain; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Grn; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Goods-receipt service. Create builds a Draft GRN (cost derived from the PO line +/// for PO-based receipts — 02-SECURITY C.3; over-receipt tolerance enforced). +/// Confirm posts FIFO layers + inbound ledger and updates PO receipts inside one +/// UoW transaction (NFR-02/05). Quantities are converted to the item's base UOM +/// for the ledger/layers (FR-MD-03). +/// +public sealed class GrnService : IGrnService +{ + // Phase 1: block any receipt beyond the PO line's open quantity (configurable later, NFR-10). + private const decimal OverReceiptTolerance = 0m; + + private readonly IRepository _grns; + private readonly IRepository _pos; + private readonly IRepository _poLines; + private readonly IRepository _items; + private readonly IRepository _uoms; + private readonly IRepository _warehouses; + private readonly IRepository _bins; + private readonly IRepository _vendors; + private readonly IRepository _batches; + private readonly IRepository _conversions; + private readonly IRepository _layers; + private readonly IRepository _ledger; + private readonly IFifoCostingService _fifo; + private readonly INumberSequenceService _numbers; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public GrnService( + IRepository grns, IRepository pos, IRepository poLines, + IRepository items, IRepository uoms, IRepository warehouses, + IRepository bins, IRepository vendors, IRepository batches, + IRepository conversions, IRepository layers, IRepository ledger, + IFifoCostingService fifo, INumberSequenceService numbers, ICurrentUser currentUser, IUnitOfWork uow) + { + _grns = grns; + _pos = pos; + _poLines = poLines; + _items = items; + _uoms = uoms; + _warehouses = warehouses; + _bins = bins; + _vendors = vendors; + _batches = batches; + _conversions = conversions; + _layers = layers; + _ledger = ledger; + _fifo = fifo; + _numbers = numbers; + _currentUser = currentUser; + _uow = uow; + } + + public async Task GetAsync(long grnId, CancellationToken ct = default) + { + var grn = await _grns.Query().AsNoTracking() + .Include(g => g.Lines) + .FirstOrDefaultAsync(g => g.GrnId == grnId, ct); + return grn is null ? null : Map(grn); + } + + public async Task CreateAsync(CreateGrnRequest request, CancellationToken ct = default) + { + if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == request.WarehouseId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Warehouse {request.WarehouseId} does not exist.", 422); + + // Resolve vendor + PO context. + PurchaseOrder? po = null; + long vendorId; + if (request.PoId is not null) + { + po = await _pos.Query().AsNoTracking().Include(p => p.Lines) + .FirstOrDefaultAsync(p => p.PoId == request.PoId, ct) + ?? throw new DomainException(ErrorCodes.Validation, $"Purchase order {request.PoId} does not exist.", 422); + if (po.Status is not (PurchaseOrderStatus.Approved or PurchaseOrderStatus.PartiallyReceived)) + throw new ConflictException($"Purchase order {po.PoId} is {po.Status} and cannot be received against."); + vendorId = po.VendorId; + } + else + { + if (request.VendorId is null) + throw new DomainException(ErrorCodes.Validation, "vendorId is required for a direct (no-PO) receipt.", 422); + if (!await _vendors.Query().AnyAsync(v => v.VendorId == request.VendorId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Vendor {request.VendorId} does not exist.", 422); + vendorId = request.VendorId.Value; + } + + var lines = new List(); + var batchCache = new Dictionary<(long ItemId, string BatchNo), Batch>(); + + foreach (var input in request.Lines) + { + var item = await _items.Query().AsNoTracking().FirstOrDefaultAsync(i => i.ItemId == input.ItemId, ct) + ?? throw new DomainException(ErrorCodes.Validation, $"Item {input.ItemId} does not exist.", 422); + if (!await _uoms.Query().AnyAsync(u => u.UomId == input.UomId, ct)) + throw new DomainException(ErrorCodes.Validation, $"UOM {input.UomId} does not exist.", 422); + if (input.BinId is not null && !await _bins.Query().AnyAsync(b => b.BinId == input.BinId && b.WarehouseId == request.WarehouseId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Bin {input.BinId} is not in warehouse {request.WarehouseId}.", 422); + + // Cost: PO-derived for PO lines (client cost ignored, C.3); entered for direct. + decimal unitCost; + if (input.PoLineId is not null) + { + var poLine = po?.Lines.FirstOrDefault(l => l.PoLineId == input.PoLineId) + ?? throw new DomainException(ErrorCodes.Validation, $"PO line {input.PoLineId} is not on purchase order {request.PoId}.", 422); + if (poLine.ItemId != input.ItemId) + throw new DomainException(ErrorCodes.Validation, $"PO line {input.PoLineId} is for a different item.", 422); + + var openQty = poLine.Qty - poLine.QtyReceived; + if (input.Qty > openQty * (1 + OverReceiptTolerance)) + throw new DomainException(ErrorCodes.OverReceiptTolerance, + $"Receiving {input.Qty} exceeds the open quantity {openQty} on PO line {input.PoLineId}.", 422); + + unitCost = poLine.UnitPrice; + } + else + { + unitCost = input.UnitCost; + } + + var batch = await ResolveBatchAsync(item, input.Batch, batchCache, ct); + + lines.Add(new GrnLine + { + PoLineId = input.PoLineId, + ItemId = input.ItemId, + UomId = input.UomId, + BinId = input.BinId, + Batch = batch, // navigation so EF fixes up BatchId once the batch is inserted + Qty = input.Qty, + UnitCost = unitCost, + ReceivedValue = Math.Round(input.Qty * unitCost, 4, MidpointRounding.AwayFromZero), + HoldStatus = input.HoldStatus + }); + } + + var grn = await _uow.ExecuteInTransactionAsync(async token => + { + var docNo = await _numbers.NextAsync(DocumentTypes.Grn, token); + var entity = new Grn + { + DocNo = docNo, + PoId = request.PoId, + VendorId = vendorId, + WarehouseId = request.WarehouseId, + Status = GrnStatus.Draft, + CreatedBy = _currentUser.AuditUserId, + CreatedAt = DateTime.UtcNow, + Lines = lines + }; + await _grns.AddAsync(entity, token); + return entity; + }, ct); + + return Map(grn); + } + + public async Task ConfirmAsync(long grnId, string? idempotencyKey, CancellationToken ct = default) + { + var grn = await _grns.Query() + .Include(g => g.Lines) + .FirstOrDefaultAsync(g => g.GrnId == grnId, ct) + ?? throw new NotFoundException($"GRN {grnId} was not found."); + + // Idempotent replay: an already-confirmed GRN returns its existing result. + if (grn.Status == GrnStatus.Confirmed) + return await BuildConfirmResultAsync(grn, ct); + if (grn.Status == GrnStatus.Closed) + throw new ConflictException($"GRN {grnId} is closed."); + + var actor = _currentUser.AuditUserId; + var now = DateTime.UtcNow; + var runningBalance = new Dictionary<(long, long), decimal>(); + var createdLayers = new List(); + var ledgerRefs = new List(); + + await _uow.ExecuteInTransactionAsync(async token => + { + foreach (var line in grn.Lines.OrderBy(l => l.GrnLineId)) + { + var item = await _items.Query().AsNoTracking().FirstAsync(i => i.ItemId == line.ItemId, token); + var (qtyBase, unitCostBase) = await ToBaseAsync(item, line.UomId, line.Qty, line.UnitCost, token); + + var layer = await _fifo.CreateInboundLayerAsync( + line.ItemId, grn.WarehouseId, line.BatchId, null, line.GrnLineId, + qtyBase, unitCostBase, now, token); + createdLayers.Add(layer); + + var key = (line.ItemId, grn.WarehouseId); + if (!runningBalance.TryGetValue(key, out var bal)) + bal = await _fifo.GetOnHandAsync(line.ItemId, grn.WarehouseId, token); + bal += qtyBase; + runningBalance[key] = bal; + + var entry = await _fifo.PostLedgerAsync( + line.ItemId, grn.WarehouseId, line.BinId, line.BatchId, null, actor, + Direction.In, qtyBase, unitCostBase, bal, DocumentTypes.Grn, grn.GrnId, now, token); + ledgerRefs.Add(entry); + + if (line.PoLineId is not null) + { + var poLine = await _poLines.GetByIdAsync(line.PoLineId.Value, token); + if (poLine is not null) poLine.QtyReceived += line.Qty; + } + } + + grn.Status = GrnStatus.Confirmed; + grn.PostedAt = now; + + await UpdatePoStatusAsync(grn.PoId, token); + return 0; + }, ct); + + return new GrnConfirmResultDto( + grn.GrnId, grn.Status, now, + createdLayers.Select(ToCreatedLayer).ToList(), + ledgerRefs.Select(l => l.LedgerId).ToList(), + await GetPoStatusAsync(grn.PoId, ct)); + } + + public async Task ReleaseLineAsync(long grnId, long grnLineId, string action, CancellationToken ct = default) + { + var grn = await _grns.Query() + .Include(g => g.Lines) + .FirstOrDefaultAsync(g => g.GrnId == grnId, ct) + ?? throw new NotFoundException($"GRN {grnId} was not found."); + + var line = grn.Lines.FirstOrDefault(l => l.GrnLineId == grnLineId) + ?? throw new NotFoundException($"GRN line {grnLineId} was not found on GRN {grnId}."); + + if (grn.Status != GrnStatus.Confirmed) + throw new ConflictException($"GRN {grnId} must be confirmed before releasing holds."); + if (line.HoldStatus != HoldStatus.OnHold) + throw new ConflictException($"GRN line {grnLineId} is {line.HoldStatus}, not OnHold."); + + if (string.Equals(action, "Release", StringComparison.OrdinalIgnoreCase)) + { + line.HoldStatus = HoldStatus.Available; + await _uow.SaveChangesAsync(ct); + return new ReleaseLineResultDto(grnLineId, HoldStatus.Available); + } + + // Reject: remove the held stock from on-hand and post a reversing ledger entry. + // Linking rejected stock to a formal purchase return is deferred (§3.4). + var actor = _currentUser.AuditUserId; + var now = DateTime.UtcNow; + await _uow.ExecuteInTransactionAsync(async token => + { + line.HoldStatus = HoldStatus.Rejected; + var layers = await _layers.Query() + .Where(l => l.GrnLineId == grnLineId && l.QtyRemaining > 0).ToListAsync(token); + + foreach (var layer in layers) + { + var bal = await _fifo.GetOnHandAsync(layer.ItemId, layer.WarehouseId, token) - layer.QtyRemaining; + await _fifo.PostLedgerAsync( + layer.ItemId, layer.WarehouseId, line.BinId, layer.BatchId, null, actor, + Direction.Out, layer.QtyRemaining, layer.UnitCost, bal, DocumentTypes.Grn, grn.GrnId, now, token); + layer.QtyRemaining = 0; + } + return 0; + }, ct); + + return new ReleaseLineResultDto(grnLineId, HoldStatus.Rejected); + } + + private async Task ResolveBatchAsync( + Item item, BatchInput? batch, Dictionary<(long, string), Batch> cache, CancellationToken ct) + { + if (item.TrackingMode == TrackingMode.Batch && batch is null) + throw new DomainException(ErrorCodes.Validation, $"Item {item.ItemId} is batch-tracked; a batch is required.", 422); + if (batch is null) return null; + + var key = (item.ItemId, batch.BatchNo.Trim()); + if (cache.TryGetValue(key, out var cached)) return cached; + + var existing = await _batches.Query().FirstOrDefaultAsync(b => b.ItemId == item.ItemId && b.BatchNo == key.Item2, ct); + if (existing is not null) + { + cache[key] = existing; + return existing; + } + + var created = new Batch { ItemId = item.ItemId, BatchNo = key.Item2, ExpiryDate = batch.ExpiryDate }; + await _batches.AddAsync(created, ct); + cache[key] = created; + return created; // linked via GrnLine.Batch navigation; FK fixed up on SaveChanges + } + + private async Task<(decimal QtyBase, decimal UnitCostBase)> ToBaseAsync( + Item item, long uomId, decimal qty, decimal unitCostPerUom, CancellationToken ct) + { + if (uomId == item.BaseUomId) + return (qty, unitCostPerUom); + + var conv = await _conversions.Query().AsNoTracking() + .FirstOrDefaultAsync(c => c.ItemId == item.ItemId && c.FromUomId == uomId && c.ToUomId == item.BaseUomId, ct) + ?? throw new DomainException(ErrorCodes.Validation, + $"No UOM conversion from {uomId} to base UOM {item.BaseUomId} for item {item.ItemId}.", 422); + + return (qty * conv.Factor, unitCostPerUom / conv.Factor); + } + + private async Task UpdatePoStatusAsync(long? poId, CancellationToken ct) + { + if (poId is null) return; + var po = await _pos.Query().Include(p => p.Lines).FirstOrDefaultAsync(p => p.PoId == poId, ct); + if (po is null) return; + + po.Status = po.Lines.All(l => l.QtyReceived >= l.Qty) + ? PurchaseOrderStatus.FullyReceived + : PurchaseOrderStatus.PartiallyReceived; + po.UpdatedAt = DateTime.UtcNow; + } + + private async Task GetPoStatusAsync(long? poId, CancellationToken ct) + { + if (poId is null) return null; + return await _pos.Query().AsNoTracking().Where(p => p.PoId == poId).Select(p => (PurchaseOrderStatus?)p.Status).FirstOrDefaultAsync(ct); + } + + private async Task BuildConfirmResultAsync(Grn grn, CancellationToken ct) + { + var lineIds = grn.Lines.Select(l => l.GrnLineId).ToList(); + var layers = await _layers.Query().AsNoTracking() + .Where(l => l.GrnLineId != null && lineIds.Contains(l.GrnLineId.Value)).ToListAsync(ct); + var ledgerRefs = await _ledger.Query().AsNoTracking() + .Where(l => l.SourceDocType == DocumentTypes.Grn && l.SourceDocId == grn.GrnId && l.Direction == Direction.In) + .Select(l => l.LedgerId).ToListAsync(ct); + + return new GrnConfirmResultDto( + grn.GrnId, grn.Status, grn.PostedAt ?? grn.CreatedAt, + layers.Select(ToCreatedLayer).ToList(), ledgerRefs, await GetPoStatusAsync(grn.PoId, ct)); + } + + private static CreatedLayerDto ToCreatedLayer(StockLayer l) => new( + l.LayerId, l.ItemId, l.WarehouseId, l.BatchId, l.QtyReceived, l.QtyRemaining, l.UnitCost, l.ReceiptDate); + + private static GrnDto Map(Grn g) => new( + g.GrnId, g.DocNo, g.PoId, g.VendorId, g.WarehouseId, g.Status, g.CreatedBy, g.CreatedAt, g.PostedAt, + g.Lines.OrderBy(l => l.GrnLineId).Select(l => new GrnLineDto( + l.GrnLineId, l.PoLineId, l.ItemId, l.UomId, l.BinId, l.Qty, l.UnitCost, l.ReceivedValue, l.HoldStatus, l.BatchId)).ToList()); +} diff --git a/Backend/ERPCore/Services/Interfaces/IAdjustmentService.cs b/Backend/ERPCore/Services/Interfaces/IAdjustmentService.cs new file mode 100644 index 0000000..c2f3c29 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IAdjustmentService.cs @@ -0,0 +1,9 @@ +using ERPCore.Dtos.Stock; + +namespace ERPCore.Services.Interfaces; + +/// Stock-adjustment business logic (docs/11 §5.5; FR-STK-07; 02-SECURITY C.5). +public interface IAdjustmentService +{ + Task CreateAsync(CreateAdjustmentRequest request, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IAuditService.cs b/Backend/ERPCore/Services/Interfaces/IAuditService.cs new file mode 100644 index 0000000..c9b503d --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IAuditService.cs @@ -0,0 +1,17 @@ +using ERPCore.Dtos.Audit; +using ERPCore.Dtos.Common; + +namespace ERPCore.Services.Interfaces; + +/// +/// Read access to the audit trail and GL-ready journal stubs (auditor role, +/// 02-SECURITY B.2.3). Both are append-only; no write API. +/// +public interface IAuditService +{ + Task> ListLogsAsync( + string? entityType, long? entityId, long? userId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default); + + Task> ListJournalAsync( + string? sourceDocType, long? sourceDocId, PageQuery query, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/ICountService.cs b/Backend/ERPCore/Services/Interfaces/ICountService.cs new file mode 100644 index 0000000..51d89cc --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/ICountService.cs @@ -0,0 +1,12 @@ +using ERPCore.Dtos.Stock; + +namespace ERPCore.Services.Interfaces; + +/// Cycle/full stock-count business logic (docs/11 §5.6; FR-STK-08). +public interface ICountService +{ + Task GetAsync(long countId, CancellationToken ct = default); + Task CreateAsync(CreateCountRequest request, CancellationToken ct = default); + Task EnterCountsAsync(long countId, EnterCountsRequest request, CancellationToken ct = default); + Task PostAsync(long countId, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs b/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs new file mode 100644 index 0000000..ef78632 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs @@ -0,0 +1,46 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Stock; + +namespace ERPCore.Services.Interfaces; + +/// +/// FIFO cost-layer and ledger domain service (00-CORE §4, docs/10 Part A.2). +/// Invoked by stock services **inside** a UoW transaction — never from a +/// controller/repository. This turn covers inbound layer creation, ledger posting +/// and valuation; oldest-first consumption (issues/transfers/adjustments) arrives +/// with §5 and must row-lock the layers it consumes (NFR-02). +/// +public interface IFifoCostingService +{ + /// Create an inbound FIFO layer (qty and unit cost in the item's base UOM). + Task CreateInboundLayerAsync( + long itemId, long warehouseId, long? batchId, long? serialId, long? grnLineId, + decimal qtyBase, decimal unitCost, DateTime receiptDate, CancellationToken ct = default); + + /// + /// Consume from open layers oldest-first, row-locking + /// the affected layers for the transaction (NFR-02). Skips on-hold and expired + /// stock. Throws STOCK_NEGATIVE_BLOCKED if issuable stock is insufficient, + /// EXPIRED_BATCH_BLOCKED/ONHOLD_NOT_ISSUABLE for an explicit batch + /// that is expired/held. Returns the consumed segments (for cost-preserving moves + /// and ledger costing). Must run inside a UoW transaction. + /// + Task> ConsumeAsync( + long itemId, long warehouseId, long? batchId, decimal qtyBase, CancellationToken ct = default); + + /// Append an immutable ledger entry (value = qtyBase × unitCost). + Task PostLedgerAsync( + long itemId, long warehouseId, long? binId, long? batchId, long? serialId, long userId, + Direction direction, decimal qtyBase, decimal unitCost, decimal runningBalance, + string sourceDocType, long sourceDocId, DateTime createdAt, CancellationToken ct = default); + + /// Current on-hand (Σ open-layer qtyRemaining) for an item at a warehouse. + Task GetOnHandAsync(long itemId, long warehouseId, CancellationToken ct = default); + + /// Valuation over open layers: Σ(qtyRemaining × unitCost) (FR-STK-04). + Task GetValuationAsync(long itemId, long warehouseId, CancellationToken ct = default); +} + +/// A quantity consumed from one FIFO layer at that layer's unit cost. +public sealed record ConsumedSegment(long LayerId, decimal Qty, decimal UnitCost); diff --git a/Backend/ERPCore/Services/Interfaces/IGrnService.cs b/Backend/ERPCore/Services/Interfaces/IGrnService.cs new file mode 100644 index 0000000..a02d883 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IGrnService.cs @@ -0,0 +1,15 @@ +using ERPCore.Dtos.Grn; + +namespace ERPCore.Services.Interfaces; + +/// Goods-receipt business logic (docs/11 §4; FR-GRN-01..08). +public interface IGrnService +{ + Task GetAsync(long grnId, CancellationToken ct = default); + Task CreateAsync(CreateGrnRequest request, CancellationToken ct = default); + + /// Confirm: create FIFO layers + inbound ledger + update PO receipts, atomically. + Task ConfirmAsync(long grnId, string? idempotencyKey, CancellationToken ct = default); + + Task ReleaseLineAsync(long grnId, long grnLineId, string action, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IPurchaseReturnService.cs b/Backend/ERPCore/Services/Interfaces/IPurchaseReturnService.cs new file mode 100644 index 0000000..8dc20b7 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IPurchaseReturnService.cs @@ -0,0 +1,9 @@ +using ERPCore.Dtos.Procurement; + +namespace ERPCore.Services.Interfaces; + +/// Purchase-return business logic (docs/11 §3.4; FR-PROC-08). +public interface IPurchaseReturnService +{ + Task CreateAsync(CreatePurchaseReturnRequest request, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IReasonCodeService.cs b/Backend/ERPCore/Services/Interfaces/IReasonCodeService.cs new file mode 100644 index 0000000..41c8d14 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IReasonCodeService.cs @@ -0,0 +1,12 @@ +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Reference; + +namespace ERPCore.Services.Interfaces; + +/// Reason-code reference data (docs/11 §6; FR-X-04). +public interface IReasonCodeService +{ + Task> ListAsync(ReasonContext? context, PageQuery query, CancellationToken ct = default); + Task CreateAsync(CreateReasonCodeRequest request, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IReorderService.cs b/Backend/ERPCore/Services/Interfaces/IReorderService.cs new file mode 100644 index 0000000..dbb4fd0 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IReorderService.cs @@ -0,0 +1,12 @@ +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Procurement; +using ERPCore.Dtos.Stock; + +namespace ERPCore.Services.Interfaces; + +/// Reorder alerts and suggested requisitions (docs/11 §5.7; FR-STK-10). +public interface IReorderService +{ + Task> GetAlertsAsync(long? warehouseId, PageQuery query, CancellationToken ct = default); + Task CreateSuggestedRequisitionAsync(long itemId, long warehouseId, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IStockMutator.cs b/Backend/ERPCore/Services/Interfaces/IStockMutator.cs new file mode 100644 index 0000000..f603cc5 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IStockMutator.cs @@ -0,0 +1,21 @@ +using ERPCore.Domain.Entities; + +namespace ERPCore.Services.Interfaces; + +/// A signed base-UOM change to an item's stock at one warehouse. +public sealed record StockDelta(long ItemId, long? BinId, long? BatchId, decimal QtyDelta); + +/// +/// Shared poster for stock-affecting documents (adjustments, count variances, +/// purchase returns). Applies signed deltas — negative consumes FIFO layers +/// oldest-first (row-locked, negative-stock blocked), positive creates a layer at +/// last cost — and appends the ledger entries. Runs inside the caller's UoW +/// transaction (the caller owns numbering, the header, and the commit); the +/// document must already be saved so its id is a valid ledger sourceDocId. +/// +public interface IStockMutator +{ + Task> ApplyAsync( + long warehouseId, string sourceDocType, long sourceDocId, DateTime now, + IReadOnlyList deltas, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IStockService.cs b/Backend/ERPCore/Services/Interfaces/IStockService.cs new file mode 100644 index 0000000..d8cc6d1 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IStockService.cs @@ -0,0 +1,15 @@ +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Stock; + +namespace ERPCore.Services.Interfaces; + +/// Read-side stock enquiry, ledger and valuation (docs/11 §5.1–5.3). +public interface IStockService +{ + Task GetOnHandAsync(long itemId, long warehouseId, CancellationToken ct = default); + + Task> GetLedgerAsync( + long? itemId, long? warehouseId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default); + + Task GetValuationAsync(long itemId, long warehouseId, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/ITransferService.cs b/Backend/ERPCore/Services/Interfaces/ITransferService.cs new file mode 100644 index 0000000..6eceb63 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/ITransferService.cs @@ -0,0 +1,16 @@ +using ERPCore.Dtos.Stock; + +namespace ERPCore.Services.Interfaces; + +/// Inter-warehouse transfer business logic (docs/11 §5.4; FR-STK-05/06). +public interface ITransferService +{ + Task GetAsync(long transferId, CancellationToken ct = default); + Task CreateAsync(CreateTransferRequest request, CancellationToken ct = default); + + /// Dispatch: consume source FIFO layers into in-transit (row-locked). + Task DispatchAsync(long transferId, CancellationToken ct = default); + + /// Receive: create the destination layer at the inherited (cost-preserving) cost. + Task ReceiveAsync(long transferId, ReceiveTransferRequest request, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/PurchaseReturnService.cs b/Backend/ERPCore/Services/PurchaseReturnService.cs new file mode 100644 index 0000000..4e0dd11 --- /dev/null +++ b/Backend/ERPCore/Services/PurchaseReturnService.cs @@ -0,0 +1,115 @@ +using ERPCore.Domain; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Procurement; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Purchase-return service (FR-PROC-08). Auto-posts with a mandatory Return reason +/// code and generates an outbound stock movement via the shared +/// (FIFO consume, row-locked; over-return beyond +/// available → STOCK_NEGATIVE_BLOCKED). Single UoW transaction. +/// +public sealed class PurchaseReturnService : IPurchaseReturnService +{ + private readonly IRepository _returns; + private readonly IRepository _vendors; + private readonly IRepository _warehouses; + private readonly IRepository _items; + private readonly IRepository _reasonCodes; + private readonly IRepository _grnLines; + private readonly IStockMutator _mutator; + private readonly INumberSequenceService _numbers; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public PurchaseReturnService( + IRepository returns, IRepository vendors, IRepository warehouses, + IRepository items, IRepository reasonCodes, IRepository grnLines, + IStockMutator mutator, INumberSequenceService numbers, ICurrentUser currentUser, IUnitOfWork uow) + { + _returns = returns; + _vendors = vendors; + _warehouses = warehouses; + _items = items; + _reasonCodes = reasonCodes; + _grnLines = grnLines; + _mutator = mutator; + _numbers = numbers; + _currentUser = currentUser; + _uow = uow; + } + + public async Task CreateAsync(CreatePurchaseReturnRequest request, CancellationToken ct = default) + { + if (request.ReasonCodeId is null) + throw new DomainException(ErrorCodes.ReasonCodeRequired, "A reason code is required for a purchase return.", 400); + + if (!await _vendors.Query().AnyAsync(v => v.VendorId == request.VendorId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Vendor {request.VendorId} does not exist.", 422); + if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == request.WarehouseId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Warehouse {request.WarehouseId} does not exist.", 422); + + var reason = await _reasonCodes.Query().AsNoTracking().FirstOrDefaultAsync(r => r.ReasonCodeId == request.ReasonCodeId, ct) + ?? throw new DomainException(ErrorCodes.Validation, $"Reason code {request.ReasonCodeId} does not exist.", 422); + if (reason.Context != ReasonContext.Return) + throw new DomainException(ErrorCodes.Validation, $"Reason code {request.ReasonCodeId} is not a Return reason.", 422); + + foreach (var line in request.Lines) + { + if (!await _items.Query().AnyAsync(i => i.ItemId == line.ItemId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Item {line.ItemId} does not exist.", 422); + if (line.GrnLineId is not null) + { + var grnLine = await _grnLines.Query().AsNoTracking().FirstOrDefaultAsync(g => g.GrnLineId == line.GrnLineId, ct) + ?? throw new DomainException(ErrorCodes.Validation, $"GRN line {line.GrnLineId} does not exist.", 422); + if (grnLine.ItemId != line.ItemId) + throw new DomainException(ErrorCodes.Validation, $"GRN line {line.GrnLineId} is for a different item.", 422); + } + } + + var now = DateTime.UtcNow; + var deltas = request.Lines.Select(l => new StockDelta(l.ItemId, null, null, -l.Qty)).ToList(); + + var (entity, ledgerEntries) = await _uow.ExecuteInTransactionAsync(async token => + { + var docNo = await _numbers.NextAsync(DocumentTypes.PurchaseReturn, token); + var ret = new PurchaseReturn + { + DocNo = docNo, + VendorId = request.VendorId, + WarehouseId = request.WarehouseId, + ReasonCodeId = request.ReasonCodeId.Value, + Status = ReturnStatus.Posted, + CreatedBy = _currentUser.AuditUserId, + CreatedAt = now, + Lines = request.Lines.Select(l => new PurchaseReturnLine + { + GrnLineId = l.GrnLineId, + ItemId = l.ItemId, + Qty = l.Qty + }).ToList() + }; + await _returns.AddAsync(ret, token); + await _uow.SaveChangesAsync(token); // flush for a valid ledger sourceDocId + + var refs = await _mutator.ApplyAsync(request.WarehouseId, DocumentTypes.PurchaseReturn, ret.ReturnId, now, deltas, token); + return (ret, refs); + }, ct); + + // Map ledger ids after commit so they are populated. + return new PurchaseReturnDto( + entity.ReturnId, entity.DocNo, entity.VendorId, entity.WarehouseId, entity.ReasonCodeId, entity.Status, + entity.CreatedBy, + entity.Lines.OrderBy(l => l.ReturnLineId) + .Select(l => new PurchaseReturnLineDto(l.ReturnLineId, l.GrnLineId, l.ItemId, l.Qty)).ToList(), + ledgerEntries.Select(r => r.LedgerId).ToList()); + } +} diff --git a/Backend/ERPCore/Services/ReasonCodeService.cs b/Backend/ERPCore/Services/ReasonCodeService.cs new file mode 100644 index 0000000..3dfd342 --- /dev/null +++ b/Backend/ERPCore/Services/ReasonCodeService.cs @@ -0,0 +1,50 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Reference; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +public sealed class ReasonCodeService : IReasonCodeService +{ + private readonly IRepository _codes; + private readonly IUnitOfWork _uow; + + public ReasonCodeService(IRepository codes, IUnitOfWork uow) + { + _codes = codes; + _uow = uow; + } + + public async Task> ListAsync(ReasonContext? context, PageQuery query, CancellationToken ct = default) + { + var q = _codes.Query().AsNoTracking(); + if (context is not null) q = q.Where(r => r.Context == context); + + var total = await q.CountAsync(ct); + var rows = await q.OrderBy(r => r.Context).ThenBy(r => r.Code) + .Skip(query.Skip).Take(query.PageSize) + .Select(r => new ReasonCodeDto(r.ReasonCodeId, r.Code, r.Description, r.Context)) + .ToListAsync(ct); + + return PagedResponse.Create(rows, query.Page, query.PageSize, total); + } + + public async Task CreateAsync(CreateReasonCodeRequest request, CancellationToken ct = default) + { + var code = request.Code.Trim().ToUpperInvariant(); + if (await _codes.Query().AnyAsync(r => r.Context == request.Context && r.Code == code, ct)) + throw new ConflictException($"Reason code '{code}' already exists in context {request.Context}."); + + var entity = new ReasonCode { Code = code, Description = request.Description.Trim(), Context = request.Context }; + await _codes.AddAsync(entity, ct); + await _uow.SaveChangesAsync(ct); + + return new ReasonCodeDto(entity.ReasonCodeId, entity.Code, entity.Description, entity.Context); + } +} diff --git a/Backend/ERPCore/Services/ReorderService.cs b/Backend/ERPCore/Services/ReorderService.cs new file mode 100644 index 0000000..28a5322 --- /dev/null +++ b/Backend/ERPCore/Services/ReorderService.cs @@ -0,0 +1,59 @@ +using ERPCore.Domain.Entities; +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Procurement; +using ERPCore.Dtos.Stock; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Reorder alerts are a query, not a stored entity (docs/10 C.9): available stock +/// (from the FIFO layers) is compared to policy on read. +/// +public sealed class ReorderService : IReorderService +{ + private readonly IRepository _reorders; + private readonly IStockService _stock; + private readonly IRequisitionService _requisitions; + + public ReorderService(IRepository reorders, IStockService stock, IRequisitionService requisitions) + { + _reorders = reorders; + _stock = stock; + _requisitions = requisitions; + } + + public async Task> GetAlertsAsync(long? warehouseId, PageQuery query, CancellationToken ct = default) + { + var q = _reorders.Query().AsNoTracking(); + if (warehouseId is not null) q = q.Where(r => r.WarehouseId == warehouseId); + var policies = await q.OrderBy(r => r.ItemId).ToListAsync(ct); + + var alerts = new List(); + foreach (var p in policies) + { + var available = (await _stock.GetOnHandAsync(p.ItemId, p.WarehouseId, ct)).Available; + if (available <= p.ReorderPoint) + alerts.Add(new ReorderAlertDto(p.ItemId, p.WarehouseId, available, p.ReorderPoint, p.ReorderQty, p.ReorderQty)); + } + + var page = alerts.Skip(query.Skip).Take(query.PageSize).ToList(); + return PagedResponse.Create(page, query.Page, query.PageSize, alerts.Count); + } + + public async Task CreateSuggestedRequisitionAsync(long itemId, long warehouseId, CancellationToken ct = default) + { + var policy = await _reorders.Query().AsNoTracking() + .FirstOrDefaultAsync(r => r.ItemId == itemId && r.WarehouseId == warehouseId, ct) + ?? throw new DomainException(ErrorCodes.Validation, + $"No reorder policy for item {itemId} at warehouse {warehouseId}.", 422); + + return await _requisitions.CreateAsync(new CreateRequisitionRequest + { + Lines = [new CreateRequisitionLineInput { ItemId = itemId, Qty = policy.ReorderQty }] + }, ct); + } +} diff --git a/Backend/ERPCore/Services/Stock/FifoCostingService.cs b/Backend/ERPCore/Services/Stock/FifoCostingService.cs new file mode 100644 index 0000000..6f4f0a8 --- /dev/null +++ b/Backend/ERPCore/Services/Stock/FifoCostingService.cs @@ -0,0 +1,183 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Stock; +using ERPCore.Infra.Persistence; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services.Stock; + +/// +/// FIFO cost-layer + ledger writer and valuation reader (docs/10 Part A.2). +/// Writes (layer/ledger creation) are added to the tracked context and persisted +/// by the caller's UoW transaction; they are never saved here. +/// +public sealed class FifoCostingService : IFifoCostingService +{ + public const string BaseCurrency = "LKR"; + public const string Method = "FIFO"; + + // Phase-1 placeholder GL accounts (data only, no posting — FR-STK-13). + private const string InventoryAccount = "1300"; + private const string ClearingAccount = "2100"; + + private readonly IRepository _layers; + private readonly IRepository _ledger; + private readonly IRepository _journal; + private readonly ErpDbContext _db; + + public FifoCostingService( + IRepository layers, IRepository ledger, + IRepository journal, ErpDbContext db) + { + _layers = layers; + _ledger = ledger; + _journal = journal; + _db = db; + } + + public async Task CreateInboundLayerAsync( + long itemId, long warehouseId, long? batchId, long? serialId, long? grnLineId, + decimal qtyBase, decimal unitCost, DateTime receiptDate, CancellationToken ct = default) + { + var layer = new StockLayer + { + ItemId = itemId, + WarehouseId = warehouseId, + BatchId = batchId, + SerialId = serialId, + GrnLineId = grnLineId, + QtyReceived = qtyBase, + QtyRemaining = qtyBase, + UnitCost = unitCost, + ReceiptDate = receiptDate + }; + await _layers.AddAsync(layer, ct); + return layer; + } + + public async Task> ConsumeAsync( + long itemId, long warehouseId, long? batchId, decimal qtyBase, CancellationToken ct = default) + { + var today = DateOnly.FromDateTime(DateTime.UtcNow); + + if (batchId is not null) + { + var expiry = await _db.Batches.AsNoTracking() + .Where(b => b.BatchId == batchId).Select(b => b.ExpiryDate).FirstOrDefaultAsync(ct); + if (expiry is not null && expiry < today) + throw new DomainException(ErrorCodes.ExpiredBatchBlocked, + $"Batch {batchId} expired on {expiry:yyyy-MM-dd} and cannot be issued.", 409); + } + + // Row-lock the issuable open layers oldest-first (SELECT … FOR UPDATE, NFR-02). + // Excludes on-hold (grn line) and expired-batch stock. No LINQ is composed on + // top of the raw SQL so the FOR UPDATE reaches the database intact. + // {batchId}::bigint casts give Npgsql an explicit type for the (possibly null) + // parameter — without it a null batch filter fails with 42P18. + var layers = await _db.StockLayers + .FromSqlInterpolated($""" + SELECT * FROM stock_layers sl + WHERE sl."ItemId" = {itemId} AND sl."WarehouseId" = {warehouseId} AND sl."QtyRemaining" > 0 + AND ({batchId}::bigint IS NULL OR sl."BatchId" = {batchId}::bigint) + AND NOT EXISTS (SELECT 1 FROM grn_lines gl WHERE gl."GrnLineId" = sl."GrnLineId" AND gl."HoldStatus" = 'OnHold') + AND NOT EXISTS (SELECT 1 FROM batches b WHERE b."BatchId" = sl."BatchId" AND b."ExpiryDate" < {today}) + ORDER BY sl."ReceiptDate", sl."LayerId" + FOR UPDATE + """) + .ToListAsync(ct); + + var available = layers.Sum(l => l.QtyRemaining); + if (available < qtyBase) + { + var heldExists = await _db.StockLayers.AsNoTracking().AnyAsync(l => + l.ItemId == itemId && l.WarehouseId == warehouseId && l.QtyRemaining > 0 + && (batchId == null || l.BatchId == batchId) + && l.GrnLine != null && l.GrnLine.HoldStatus == HoldStatus.OnHold, ct); + if (heldExists) + throw new DomainException(ErrorCodes.OnHoldNotIssuable, + $"Stock for item {itemId} at warehouse {warehouseId} is on inspection hold and cannot be issued.", 409); + + throw new DomainException(ErrorCodes.StockNegativeBlocked, + $"Available {available} < requested {qtyBase} for item {itemId} at warehouse {warehouseId}.", 409); + } + + var segments = new List(); + var remaining = qtyBase; + foreach (var layer in layers) + { + if (remaining <= 0) break; + var take = Math.Min(remaining, layer.QtyRemaining); + layer.QtyRemaining -= take; + remaining -= take; + segments.Add(new ConsumedSegment(layer.LayerId, take, layer.UnitCost)); + } + + return segments; + } + + public async Task PostLedgerAsync( + long itemId, long warehouseId, long? binId, long? batchId, long? serialId, long userId, + Direction direction, decimal qtyBase, decimal unitCost, decimal runningBalance, + string sourceDocType, long sourceDocId, DateTime createdAt, CancellationToken ct = default) + { + var entry = new StockLedger + { + ItemId = itemId, + WarehouseId = warehouseId, + BinId = binId, + BatchId = batchId, + SerialId = serialId, + UserId = userId, + Direction = direction, + QtyBase = qtyBase, + UnitCost = unitCost, + Value = Math.Round(qtyBase * unitCost, 4, MidpointRounding.AwayFromZero), + RunningBalance = runningBalance, + SourceDocType = sourceDocType, + SourceDocId = sourceDocId, + CreatedAt = createdAt + }; + await _ledger.AddAsync(entry, ct); + + // GL-ready journal entry per movement (FR-STK-13; data only, no posting). + // Inbound debits Inventory / credits Clearing; outbound reverses. + var (debit, credit) = direction == Direction.In + ? (InventoryAccount, ClearingAccount) + : (ClearingAccount, InventoryAccount); + await _journal.AddAsync(new JournalEntryStub + { + SourceDocType = sourceDocType, + SourceDocId = sourceDocId, + DebitAccount = debit, + CreditAccount = credit, + Amount = entry.Value + }, ct); + + return entry; + } + + public async Task GetOnHandAsync(long itemId, long warehouseId, CancellationToken ct = default) + => await _layers.Query().AsNoTracking() + .Where(l => l.ItemId == itemId && l.WarehouseId == warehouseId) + .SumAsync(l => (decimal?)l.QtyRemaining, ct) ?? 0m; + + public async Task GetValuationAsync(long itemId, long warehouseId, CancellationToken ct = default) + { + var open = await _layers.Query().AsNoTracking() + .Where(l => l.ItemId == itemId && l.WarehouseId == warehouseId && l.QtyRemaining > 0) + .OrderBy(l => l.ReceiptDate).ThenBy(l => l.LayerId) + .Select(l => new { l.LayerId, l.QtyRemaining, l.UnitCost, l.ReceiptDate }) + .ToListAsync(ct); + + var layers = open.Select(l => new StockValuationLayerDto( + l.LayerId, l.QtyRemaining, l.UnitCost, + Math.Round(l.QtyRemaining * l.UnitCost, 4, MidpointRounding.AwayFromZero), l.ReceiptDate)).ToList(); + + return new StockValuationDto( + itemId, warehouseId, layers, + layers.Sum(l => l.QtyRemaining), layers.Sum(l => l.Value), BaseCurrency, Method); + } +} diff --git a/Backend/ERPCore/Services/Stock/StockMutator.cs b/Backend/ERPCore/Services/Stock/StockMutator.cs new file mode 100644 index 0000000..729f72e --- /dev/null +++ b/Backend/ERPCore/Services/Stock/StockMutator.cs @@ -0,0 +1,77 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Infra.Auth; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services.Stock; + +/// +/// Applies signed stock deltas for a document and posts the ledger (see +/// ). Running balances are tracked in-memory per +/// (item, warehouse), seeded from current on-hand, so multiple lines for the same +/// item chain correctly within the transaction. +/// +public sealed class StockMutator : IStockMutator +{ + private readonly IFifoCostingService _fifo; + private readonly IRepository _layers; + private readonly ICurrentUser _currentUser; + + public StockMutator(IFifoCostingService fifo, IRepository layers, ICurrentUser currentUser) + { + _fifo = fifo; + _layers = layers; + _currentUser = currentUser; + } + + public async Task> ApplyAsync( + long warehouseId, string sourceDocType, long sourceDocId, DateTime now, + IReadOnlyList deltas, CancellationToken ct = default) + { + var actor = _currentUser.AuditUserId; + var balances = new Dictionary(); + var entries = new List(); + + foreach (var d in deltas) + { + if (d.QtyDelta == 0) continue; + + if (!balances.TryGetValue(d.ItemId, out var bal)) + bal = await _fifo.GetOnHandAsync(d.ItemId, warehouseId, ct); + + StockLedger entry; + if (d.QtyDelta < 0) + { + var qty = -d.QtyDelta; + var segments = await _fifo.ConsumeAsync(d.ItemId, warehouseId, d.BatchId, qty, ct); + var unitCost = segments.Sum(s => s.Qty * s.UnitCost) / qty; + bal -= qty; + entry = await _fifo.PostLedgerAsync( + d.ItemId, warehouseId, d.BinId, d.BatchId, null, actor, + Direction.Out, qty, unitCost, bal, sourceDocType, sourceDocId, now, ct); + } + else + { + var unitCost = await LastCostAsync(d.ItemId, warehouseId, ct); + await _fifo.CreateInboundLayerAsync(d.ItemId, warehouseId, d.BatchId, null, null, d.QtyDelta, unitCost, now, ct); + bal += d.QtyDelta; + entry = await _fifo.PostLedgerAsync( + d.ItemId, warehouseId, d.BinId, d.BatchId, null, actor, + Direction.In, d.QtyDelta, unitCost, bal, sourceDocType, sourceDocId, now, ct); + } + + balances[d.ItemId] = bal; + entries.Add(entry); + } + + return entries; + } + + private async Task LastCostAsync(long itemId, long warehouseId, CancellationToken ct) + => await _layers.Query().AsNoTracking() + .Where(l => l.ItemId == itemId && l.WarehouseId == warehouseId) + .OrderByDescending(l => l.ReceiptDate).ThenByDescending(l => l.LayerId) + .Select(l => (decimal?)l.UnitCost).FirstOrDefaultAsync(ct) ?? 0m; +} diff --git a/Backend/ERPCore/Services/Stock/StockService.cs b/Backend/ERPCore/Services/Stock/StockService.cs new file mode 100644 index 0000000..722a069 --- /dev/null +++ b/Backend/ERPCore/Services/Stock/StockService.cs @@ -0,0 +1,76 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Stock; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services.Stock; + +public sealed class StockService : IStockService +{ + private readonly IFifoCostingService _fifo; + private readonly IRepository _layers; + private readonly IRepository _ledger; + private readonly IRepository _transferLines; + + public StockService( + IFifoCostingService fifo, IRepository layers, + IRepository ledger, IRepository transferLines) + { + _fifo = fifo; + _layers = layers; + _ledger = ledger; + _transferLines = transferLines; + } + + public async Task GetOnHandAsync(long itemId, long warehouseId, CancellationToken ct = default) + { + var onHand = await _fifo.GetOnHandAsync(itemId, warehouseId, ct); + + // On-hold stock is on-hand but not issuable — sourced from GRN lines still OnHold. + var onHold = await _layers.Query().AsNoTracking() + .Where(l => l.ItemId == itemId && l.WarehouseId == warehouseId + && l.GrnLine != null && l.GrnLine.HoldStatus == HoldStatus.OnHold) + .SumAsync(l => (decimal?)l.QtyRemaining, ct) ?? 0m; + + // Outbound in-transit: dispatched from this warehouse, not yet received at the + // destination. Dispatch already consumed the source layers, so this stock has + // left onHand — it is reported for visibility (AR-05) but is NOT re-subtracted + // from available (that would double-count). reserved (sales) stays a stub. + var inTransit = await _transferLines.Query().AsNoTracking() + .Where(l => l.ItemId == itemId + && l.Transfer!.SrcWarehouseId == warehouseId + && l.Transfer.Status == TransferStatus.InTransit) + .SumAsync(l => (decimal?)(l.Qty - l.QtyReceived), ct) ?? 0m; + const decimal reserved = 0m; + var available = onHand - onHold - reserved; + + return new StockOnHandDto(itemId, warehouseId, onHand, available, onHold, inTransit, reserved, DateTime.UtcNow); + } + + public async Task> GetLedgerAsync( + long? itemId, long? warehouseId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default) + { + var q = _ledger.Query().AsNoTracking(); + if (itemId is not null) q = q.Where(l => l.ItemId == itemId); + if (warehouseId is not null) q = q.Where(l => l.WarehouseId == warehouseId); + if (from is not null) q = q.Where(l => l.CreatedAt >= from.Value.ToDateTime(TimeOnly.MinValue)); + if (to is not null) q = q.Where(l => l.CreatedAt < to.Value.AddDays(1).ToDateTime(TimeOnly.MinValue)); + + var total = await q.CountAsync(ct); + var rows = await q.OrderByDescending(l => l.LedgerId) + .Skip(query.Skip).Take(query.PageSize) + .Select(l => new StockLedgerRowDto( + l.LedgerId, l.ItemId, l.WarehouseId, l.BinId, l.BatchId, l.SerialId, + l.Direction, l.QtyBase, l.UnitCost, l.Value, l.RunningBalance, + l.SourceDocType, l.SourceDocId, l.UserId, l.CreatedAt)) + .ToListAsync(ct); + + return PagedResponse.Create(rows, query.Page, query.PageSize, total); + } + + public Task GetValuationAsync(long itemId, long warehouseId, CancellationToken ct = default) + => _fifo.GetValuationAsync(itemId, warehouseId, ct); +} diff --git a/Backend/ERPCore/Services/TransferService.cs b/Backend/ERPCore/Services/TransferService.cs new file mode 100644 index 0000000..4c765f5 --- /dev/null +++ b/Backend/ERPCore/Services/TransferService.cs @@ -0,0 +1,196 @@ +using ERPCore.Domain; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Stock; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Inter-warehouse transfer service (FR-STK-05/06). Dispatch consumes source FIFO +/// layers (row-locked; negative-stock blocked) and records the value-weighted cost +/// on the line; receive recreates the destination layer at that cost +/// (cost-preserving — no revaluation). Both run in a single UoW transaction. +/// +public sealed class TransferService : ITransferService +{ + private readonly IRepository _transfers; + private readonly IRepository _warehouses; + private readonly IRepository _items; + private readonly IFifoCostingService _fifo; + private readonly INumberSequenceService _numbers; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public TransferService( + IRepository transfers, IRepository warehouses, IRepository items, + IFifoCostingService fifo, INumberSequenceService numbers, ICurrentUser currentUser, IUnitOfWork uow) + { + _transfers = transfers; + _warehouses = warehouses; + _items = items; + _fifo = fifo; + _numbers = numbers; + _currentUser = currentUser; + _uow = uow; + } + + public async Task GetAsync(long transferId, CancellationToken ct = default) + { + var t = await _transfers.Query().AsNoTracking().Include(x => x.Lines) + .FirstOrDefaultAsync(x => x.TransferId == transferId, ct); + return t is null ? null : Map(t); + } + + public async Task CreateAsync(CreateTransferRequest request, CancellationToken ct = default) + { + if (request.SrcWarehouseId == request.DestWarehouseId) + throw new DomainException(ErrorCodes.Validation, "destWarehouseId must differ from srcWarehouseId.", 422); + await EnsureWarehouseAsync(request.SrcWarehouseId, ct); + await EnsureWarehouseAsync(request.DestWarehouseId, ct); + foreach (var line in request.Lines) + if (!await _items.Query().AnyAsync(i => i.ItemId == line.ItemId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Item {line.ItemId} does not exist.", 422); + + var actor = _currentUser.AuditUserId; + var transfer = await _uow.ExecuteInTransactionAsync(async token => + { + var docNo = await _numbers.NextAsync(DocumentTypes.Transfer, token); + var entity = new StockTransfer + { + DocNo = docNo, + SrcWarehouseId = request.SrcWarehouseId, + DestWarehouseId = request.DestWarehouseId, + Status = TransferStatus.Draft, + CreatedBy = actor, + CreatedAt = DateTime.UtcNow, + Lines = request.Lines.Select(l => new StockTransferLine + { + ItemId = l.ItemId, + SrcBinId = l.SrcBinId, + DestBinId = l.DestBinId, + BatchId = l.BatchId, + Qty = l.Qty + }).ToList() + }; + await _transfers.AddAsync(entity, token); + return entity; + }, ct); + + return Map(transfer); + } + + public async Task DispatchAsync(long transferId, CancellationToken ct = default) + { + var transfer = await _transfers.Query().Include(t => t.Lines) + .FirstOrDefaultAsync(t => t.TransferId == transferId, ct) + ?? throw new NotFoundException($"Transfer {transferId} was not found."); + if (transfer.Status != TransferStatus.Draft) + throw new ConflictException($"Transfer {transferId} is {transfer.Status}; only a Draft can be dispatched."); + + var actor = _currentUser.AuditUserId; + var now = DateTime.UtcNow; + var consumed = new List(); + var ledgerRefs = new List(); + var balances = new Dictionary(); + + await _uow.ExecuteInTransactionAsync(async token => + { + foreach (var line in transfer.Lines.OrderBy(l => l.TransferLineId)) + { + var segments = await _fifo.ConsumeAsync(line.ItemId, transfer.SrcWarehouseId, line.BatchId, line.Qty, token); + var value = segments.Sum(s => s.Qty * s.UnitCost); + line.UnitCost = value / line.Qty; // value-weighted cost, preserved to receive + + if (!balances.TryGetValue(line.ItemId, out var bal)) + bal = await _fifo.GetOnHandAsync(line.ItemId, transfer.SrcWarehouseId, token); + bal -= line.Qty; + balances[line.ItemId] = bal; + + var entry = await _fifo.PostLedgerAsync( + line.ItemId, transfer.SrcWarehouseId, line.SrcBinId, line.BatchId, null, actor, + Direction.Out, line.Qty, line.UnitCost.Value, bal, DocumentTypes.Transfer, transfer.TransferId, now, token); + ledgerRefs.Add(entry); + + consumed.AddRange(segments.Select(s => new ConsumedLayerDto(s.LayerId, s.Qty, s.UnitCost))); + } + + transfer.Status = TransferStatus.InTransit; + return 0; + }, ct); + + return new DispatchResultDto(transfer.TransferId, transfer.Status, consumed, ledgerRefs.Select(l => l.LedgerId).ToList()); + } + + public async Task ReceiveAsync(long transferId, ReceiveTransferRequest request, CancellationToken ct = default) + { + var transfer = await _transfers.Query().Include(t => t.Lines) + .FirstOrDefaultAsync(t => t.TransferId == transferId, ct) + ?? throw new NotFoundException($"Transfer {transferId} was not found."); + if (transfer.Status != TransferStatus.InTransit) + throw new ConflictException($"Transfer {transferId} is {transfer.Status}; only an InTransit transfer can be received."); + + var actor = _currentUser.AuditUserId; + var now = DateTime.UtcNow; + var createdLayers = new List(); + var ledgerRefs = new List(); + var balances = new Dictionary(); + + await _uow.ExecuteInTransactionAsync(async token => + { + foreach (var input in request.Lines) + { + var line = transfer.Lines.FirstOrDefault(l => l.TransferLineId == input.TransferLineId) + ?? throw new DomainException(ErrorCodes.Validation, $"Transfer line {input.TransferLineId} is not on transfer {transferId}.", 422); + + var outstanding = line.Qty - line.QtyReceived; + if (input.Qty > outstanding) + throw new DomainException(ErrorCodes.Validation, + $"Receiving {input.Qty} exceeds the outstanding in-transit {outstanding} on line {input.TransferLineId}.", 422); + + var unitCost = line.UnitCost ?? 0m; + var layer = await _fifo.CreateInboundLayerAsync( + line.ItemId, transfer.DestWarehouseId, line.BatchId, null, null, input.Qty, unitCost, now, token); + createdLayers.Add(layer); + + if (!balances.TryGetValue(line.ItemId, out var bal)) + bal = await _fifo.GetOnHandAsync(line.ItemId, transfer.DestWarehouseId, token); + bal += input.Qty; + balances[line.ItemId] = bal; + + var entry = await _fifo.PostLedgerAsync( + line.ItemId, transfer.DestWarehouseId, line.DestBinId, line.BatchId, null, actor, + Direction.In, input.Qty, unitCost, bal, DocumentTypes.Transfer, transfer.TransferId, now, token); + ledgerRefs.Add(entry); + + line.QtyReceived += input.Qty; + } + + if (transfer.Lines.All(l => l.QtyReceived >= l.Qty)) + transfer.Status = TransferStatus.Received; + return 0; + }, ct); + + // Map after commit so layer ids are populated. + var created = createdLayers + .Select(l => new TransferCreatedLayerDto(l.LayerId, l.WarehouseId, l.QtyReceived, l.UnitCost)) + .ToList(); + return new ReceiveResultDto(transfer.TransferId, transfer.Status, created, ledgerRefs.Select(l => l.LedgerId).ToList()); + } + + private async Task EnsureWarehouseAsync(long warehouseId, CancellationToken ct) + { + if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == warehouseId, ct)) + throw new DomainException(ErrorCodes.Validation, $"Warehouse {warehouseId} does not exist.", 422); + } + + private static TransferDto Map(StockTransfer t) => new( + t.TransferId, t.DocNo, t.SrcWarehouseId, t.DestWarehouseId, t.Status, + t.Lines.OrderBy(l => l.TransferLineId).Select(l => new TransferLineDto( + l.TransferLineId, l.ItemId, l.SrcBinId, l.DestBinId, l.BatchId, l.Qty, l.QtyReceived)).ToList()); +} diff --git a/Backend/PROGRESS.md b/Backend/PROGRESS.md index 66d7ab5..a1761e9 100644 --- a/Backend/PROGRESS.md +++ b/Backend/PROGRESS.md @@ -29,36 +29,40 @@ Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** - [~] Requisition (+ lines) + submit (`POST /requisitions`, `/{id}/submit`, list, get) - [~] RFQ + quotations + comparison (`POST /rfqs`, `/{id}/quotations` [one per vendor], `GET /{id}/comparison` matrix) - [~] Purchase Order: create (auto-approve, `approvalRequired` flag), edit-while-open (If-Match), approve (no-op), cancel -- [ ] Purchase Return (outbound movement, reason code) — **deferred**: needs GRN lines + stock ledger/FIFO (§3/§4). Build with those. +- [~] Purchase Return (outbound movement, reason code) — `POST /purchase-returns` auto-posts an outbound FIFO consume via shared `StockMutator`; mandatory Return-context reason (`REASON_CODE_REQUIRED`→400, wrong context→422), references the GRN line for traceability, over-return→`409 STOCK_NEGATIVE_BLOCKED`. Verified. (Cumulative return-vs-received cap still relies on the stock-availability guard.) > **Deviation (recorded):** `VendorQuotation` is modelled as header + `VendorQuotationLine` (per-item pricing) to satisfy the API contract (docs/11 §3.2); docs/10 Part C.2's scalar `VENDOR_QUOTATION(unit_price, lead_days)` with no item ref cannot represent it. Update the ER model doc to match. > **RFQ `vendorIds`** are validated for existence but not persisted (no RFQ↔vendor link in the model); quotations reference vendors directly. ## 3. Goods Receipt -- [ ] GRN create (against PO / direct), over-receipt tolerance -- [ ] GRN confirm → FIFO layer + ledger + PO `qtyReceived` (single UoW txn, Idempotency-Key) -- [ ] Inspection hold release / reject +> Implemented + **smoke test PASSED** 2026-07-13 (see §4 note for the shared stock verification). Same `[~]` reason as §1/§2: the §6 auth+audit gate. +- [~] GRN create (against PO / direct), over-receipt tolerance — `unitCost` **PO-derived server-side** (client `999` verified ignored → PO price used, 02-SECURITY C.3); direct receipt requires `vendorId` + entered cost (AR-04); over-receipt → `422 OVER_RECEIPT_TOLERANCE` (verified at open-qty boundary); batch created/reused per (item, batchNo). Serial capture deferred. +- [~] GRN confirm → FIFO layer + ledger + PO `qtyReceived` (single UoW txn) — verified: layers+ledger posted, running balance, PO → PartiallyReceived/FullyReceived, UOM→base conversion (10 Box-12 → 120 base @10). **Idempotent** re-confirm verified (no double-post). Note: `Idempotency-Key` accepted but idempotency is resource-state based (already-Confirmed replays existing result); a keyed idempotency store is deferred. +- [~] Inspection hold release / reject — Release fully verified (OnHold excluded from `available`, then released). Reject removes on-hand + posts a reversing ledger entry; formal link to a Purchase Return is deferred (§3.4). ## 4. Stock Core -- [ ] StockLayer + StockLedger entities/config (ledger append-only) -- [ ] `FifoCostingService` (consume oldest-first with row lock; valuation) -- [ ] Stock enquiry (onHand / available / onHold / inTransit) -- [ ] Ledger query · Valuation query +> Implemented + **live smoke test PASSED** 2026-07-13: receive→confirm creates FIFO layers + inbound ledger (qtyBase/unitCost/value/runningBalance correct), on-hand/valuation/ledger queries correct, OnHold excluded from `available`, UOM→base conversion applied. Same `[~]` gate (§6 auth+audit). +- [x] StockLayer + StockLedger entities/config — ledger **append-only at the app level** (never updated/deleted); DB-role `UPDATE`/`DELETE` revoke is deferred hardening (02-SECURITY B.3). Layers keyed per item **per warehouse**, base-UOM qty + unit cost; ledger polymorphic source (`sourceDocType`/`sourceDocId`), time-series indexes. +- [~] `FifoCostingService` — inbound layer + ledger posting + valuation **and oldest-first consume with row lock** (`SELECT … FOR UPDATE`, on-hold/expired exclusion, negative-stock block) all implemented + verified 2026-07-13 via §5. Blended cost on multi-layer consume verified (700@10 + 100@12 → 10.25). +- [~] Stock enquiry (onHand / available / onHold / inTransit) — onHand/available/onHold **and inTransit** now live + verified (inTransit = outstanding InTransit-transfer qty out of this warehouse). `reserved` stays a 0 stub until Sales. +- [x] Ledger query · Valuation query — `GET /stock/ledger` (item/warehouse/from/to + paging), `GET /stock/valuation` (open layers, totals, FIFO); both verified. ## 5. Stock Transactions -- [ ] Transfer: create → dispatch (consume, In-Transit) → receive (dest layer, cost-preserving) -- [ ] Adjustment (auto-post, mandatory reason code) -- [ ] Count (cycle/full → enter counts → variance → post) -- [ ] Reorder alerts (query) + suggest requisition - +> **All four §5 features implemented + live smoke test PASSED 2026-07-13** (Adjustment, Transfer, Count, Reorder alerts); Purchase Return (§3.4) also done this pass. Same `[~]` gate (§6 auth+audit). +- [~] Transfer: create → dispatch (consume source FIFO row-locked → In-Transit) → receive (dest layer, **cost-preserving**) — verified: dispatch reduces source onHand + reports inTransit; receive creates dest layer at inherited cost (300 @12 → dest value 3600); `destWarehouseId != srcWarehouseId`→422; dispatch short→`409 STOCK_NEGATIVE_BLOCKED`. Partial receive supported (`QtyReceived`). +- [~] Adjustment (auto-post, mandatory reason code) — **highest-risk feature (02-SECURITY C.5)**: `REASON_CODE_REQUIRED`→400, non-Adjustment reason→422, decrease FIFO-consumes (blended cost, negative→409), increase creates a layer at last cost. All verified. +- [~] Count (cycle/full → enter counts → variance → post) — create snapshots systemQty (immutable), enter sets counted+variance→Counted, post emits a variance `StockAdjustment` via shared `StockMutator` + closes the count. Verified: variance −15 (post→on-hand 485) and +10 increase; re-post→409. +- [~] Reorder alerts (query) + suggest requisition — `GET /stock/reorder-alerts` (available ≤ ROP, computed on read) + `POST …/{itemId}/requisition` (draft PR at suggested qty). Verified. +syte ## 6. Cross-cutting -> **Auth-enforcement gap (open):** JWT bearer *validation* is wired, but no token issuer exists yet and controllers are **not** `[Authorize]`-gated, so §1 endpoints are currently open. This is the AR-01/NFR-03 control surface — gate all v1 endpoints (fallback authorization policy) in the same change as `POST /auth/login`, then re-run the 02-SECURITY B.1 checklist and flip §1 items to `[x]`. -- [ ] Audit log on every mutation (who/when/old→new) +> **Status:** audit trail, doc numbering, reason codes, JournalEntryStub, negative-stock block all **done**. The **one** remaining item is authentication. **Auth-enforcement gap (open):** JWT bearer *validation* is wired, but no token issuer exists yet and controllers are **not** `[Authorize]`-gated, so endpoints are currently open. This is the AR-01/NFR-03 control surface — gate all v1 endpoints (fallback authorization policy) in the same change as `POST /auth/login`, then re-run the 02-SECURITY B.1 checklist and flip §1–§5 items to `[x]`. (The AR-01 **audit** compensating control is now in place.) +- [x] Audit log on every mutation (who/when/old→new) — `AuditLog` (jsonb `changeSet`), written by an `ErpDbContext.SaveChanges` override (`AuditScribe`): Create captures the field set, Update captures **only changed fields as {old,new}**, Delete captures the prior row; PK/RowVersion excluded; ledger/layer/seq/self/journal excluded. Actor from `ICurrentUser` (system=1 until auth). Read via `GET /audit-logs`. **Verified** (Item create+update old→new; StockAdjustment create). This is the **AR-01 compensating control** (02-SECURITY B.3) — app-level append-only; DB-role UPDATE/DELETE revoke still deferred. - [x] Document numbering sequences (per type, per year) — `NumberSequence` + `NumberSequenceService` (atomic `INSERT … ON CONFLICT … RETURNING` inside the doc's UoW txn; gap-controlled). Verified issuing + incrementing PR/RFQ/PO. - [~] Auth: simple in-app login → JWT (`POST /auth/login`) — foundation only: `User` table + seeded `system` user (id 1) exist and `ICurrentUser.AuditUserId` stamps docs; login endpoint + `[Authorize]` still pending. -- [ ] JournalEntryStub emitted per stock movement (data only) -- [ ] Negative-stock policy enforcement (default block) -- [ ] FEFO picking for perishables; block expired / on-hold issue +- [x] JournalEntryStub emitted per stock movement (data only) — `JournalEntryStub` written in `FifoCostingService.PostLedgerAsync` for every ledger entry (In → Dr Inventory `1300` / Cr Clearing `2100`; Out reverses; amount = movement value). Placeholder accounts until a chart of accounts exists. Read via `GET /journal-entries`. **Verified** (GRN In 700, ADJ Out 70). +- [x] Negative-stock policy enforcement (default block) — enforced in `FifoCostingService.ConsumeAsync` → `409 STOCK_NEGATIVE_BLOCKED` (verified). Per-item override still a config stub. +- [~] FEFO picking for perishables; block expired / on-hold issue — **issue-block done + verified** (`ONHOLD_NOT_ISSUABLE`, `EXPIRED_BATCH_BLOCKED`; on-hold/expired layers excluded from consume). FEFO *pick ordering* (oldest-expiry first) not yet built. +- [x] Reason codes (FR-X-04) — `ReasonCode` entity + `GET/POST /reason-codes`; standard set (docs/10 §B.8.3) seeded idempotently at startup (`DataSeeder`). Verified. ## Deferred (Phase 2+ — do NOT build now, hooks only) - [ ] Vendor invoice + three-way match @@ -88,3 +92,31 @@ Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** - **Verified:** build clean; app boots; full procurement smoke green (see §2 note) — requisition→submit, RFQ→quotation→comparison, PO create/get/edit/approve/cancel, numbering increment, error paths 409/412/422. - **Deferred/next:** Purchase Return (needs GRN+stock), then §3 GRN, §4 Stock Core (FIFO/ledger), §5 stock transactions. Auth/audit (§6) still the gate for flipping §1/§2 to `[x]`. - Housekeeping: an empty user-created migration `20260709124415_initial` sits between InitialCreate and AddProcurement (applied, harmless no-op; emits a cosmetic CS8981 lowercase-name warning). + +### 2026-07-13 — Stock Core (§4) + Goods Receipt (§3, minus purchase return) +- Entities: Batch, Serial, StockLayer (FIFO), StockLedger (append-only), Grn/GrnLine + configs; enums Direction, HoldStatus, GrnStatus. DbSets + migration `AddStockAndGrn` (6 tables) applied. +- Services: `FifoCostingService` (Services/Stock — inbound layer, ledger post, on-hand, valuation), `StockService` (enquiry/ledger/valuation), `GrnService` (create with PO-derived cost + over-receipt + batch resolve + UOM→base; confirm in `ExecuteInTransactionAsync`; release/reject). Controllers `/stock`, `/grns`. +- **Verified against Postgres:** PO→GRN receive (client cost ignored, PO price used) → confirm → 2 FIFO layers + 2 ledger rows (running balance) → PO FullyReceived; on-hand 5000 / valuation 62500 / ledger In; **idempotent re-confirm** (on-hand stayed 5000); over-receipt `422` at the open-qty boundary, exact-fill `201`; batch receive **OnHold** → excluded from `available` → **release** → available; **UOM conversion** 10×Box-12 → 120 base units @10 (value 1200). +- **Deferred (next):** §5 stock transactions — Transfer (dispatch/receive, in-transit, cost-preserving), Adjustment (auto-post + reason code), Count, Reorder alerts — which is where the FIFO **oldest-first consume + row lock** lands; then Purchase Return (§3.4) and GRN reject→return linkage. `ReasonCode` (§6) needed for adjustments/returns. Auth/audit (§6) remains the `[x]` gate. + +### 2026-07-13 (2) — Stock Transactions (§5: Adjustment, Transfer, Reorder) + FIFO consume + Reason codes +- **FIFO consume engine** in `FifoCostingService.ConsumeAsync`: oldest-first, row-locked via `SELECT … FOR UPDATE` (raw SQL, no LINQ composed on top so the lock reaches PG; `{batchId}::bigint` cast avoids a 42P18 null-param error), excludes on-hold + expired layers, throws `STOCK_NEGATIVE_BLOCKED`/`ONHOLD_NOT_ISSUABLE`/`EXPIRED_BATCH_BLOCKED`, returns consumed segments for cost-preserving moves. +- Entities: ReasonCode, StockAdjustment/Line, StockTransfer/Line (+ `UnitCost`/`QtyReceived` extension on the transfer line for cost-preservation) + configs; enums TransferStatus, AdjustmentStatus, ReasonContext. Migration `AddStockTransactions` (5 tables) applied. +- Services/controllers: `ReasonCodeService` (`/reason-codes`, startup seed via `DataSeeder`), `AdjustmentService` (`/stock-adjustments`), `TransferService` (`/stock-transfers` create/dispatch/receive), `ReorderService` (`/stock/reorder-alerts` + suggest-requisition). `StockService.GetOnHandAsync` now computes real inTransit. +- **Verified against Postgres:** adjustment decrease FIFO-consume with blended cost 10.25 across two layers, negative→409, `REASON_CODE_REQUIRED`→400, wrong-context reason→422, increase-at-last-cost; transfer create→dispatch (source onHand↓, inTransit↑, consumedLayers)→receive (dest layer cost-preserved @12, value 3600), dest==src→422, dispatch-short→409; reorder alerts list + draft-PR suggestion; reason codes seeded (5 Adjustment + 4 Return). +- **Deferred (next):** §5 **Count** (create snapshot → enter counts → post variance via the Adjustment engine), **Purchase Return** (§3.4, + GRN reject→return linkage), FEFO pick ordering, `JournalEntryStub`. Auth/audit (§6) remains the `[x]` gate. + +### 2026-07-13 (3) — Count (§5.6) + Purchase Return (§3.4) + shared StockMutator +- **`StockMutator`** (Services/Stock): shared signed-delta poster (negative → FIFO consume; positive → layer at last cost) + ledger, run inside the caller's txn. Adjustment/Count-post/Return all delegate to it → one code path for stock-affecting postings. +- Refactored `AdjustmentService` onto `StockMutator` and added an **intermediate `SaveChanges`** so the header id is flushed before ledger posting — fixes a latent bug where new-in-txn documents wrote `sourceDocId=0` (verified: `ADJ` ledger now `sourceDocId=5`). Also fixed `ledgerRefs:[0]` by mapping ledger ids **after** commit (Count + Return). +- Entities: StockCount/Line (+ CountType, CountStatus), PurchaseReturn/Line (+ ReturnStatus) + configs. Migration `AddCountsAndReturns` (4 tables) applied. +- Services/controllers: `CountService` (`/stock-counts` create/enter/post), `PurchaseReturnService` (`/purchase-returns`). +- **Verified against Postgres:** count snapshot 500 → counted 485 → post variance −15 (on-hand→485, adjustmentId+ledgerRefs), positive variance +10, re-post→409; return 100 outbound (on-hand→385), `REASON_CODE_REQUIRED`→400, non-Return reason→422, over-return→409 `STOCK_NEGATIVE_BLOCKED`. +- **§5 is now complete.** Remaining Phase-1 backend: §6 (auth/login + `[Authorize]`, audit trail, `JournalEntryStub`, negative-stock per-item override, FEFO pick ordering). Auth/audit is still the gate for flipping §1–§5 `[~]`→`[x]`. + +### 2026-07-13 (4) — §6 audit trail + JournalEntryStub (auth deferred by request) +- **Audit trail (FR-X-02):** `AuditLog` (jsonb `changeSet`) written by an `ErpDbContext.SaveChanges/Async` override via `AuditScribe` — captures before save (accurate old→new), writes rows after inserts get their keys. Create = field set, Update = only changed fields `{old,new}`, Delete = prior row; excludes PK/RowVersion and the ledger/layer/sequence/journal/self tables. Actor from `ICurrentUser` (system=1). `ErpDbContext` now takes `ICurrentUser` (design-time migration still works via DI). +- **JournalEntryStub (FR-STK-13):** emitted for every ledger entry in `FifoCostingService.PostLedgerAsync` (In → Dr `1300`/Cr `2100`; Out reverses; amount = value). Placeholder GL accounts. +- Read endpoints (auditor role, beyond documented §11): `GET /audit-logs` (entityType/entityId/userId/from/to), `GET /journal-entries` (sourceDocType/sourceDocId). `AuditService`. Migration `AddAuditAndJournal` (2 tables, jsonb) applied. +- **Verified against Postgres:** item Create logged full field set (userId 1); item Update logged only `Name` + `UpdatedAt` as `{old,new}`; GRN confirm → journal In Dr1300/Cr2100 amount 700; adjustment decrease → journal Out Dr2100/Cr1300 amount 70; StockAdjustment Create audited. +- **Only auth remains for Phase 1.** Everything else in §6 is done. Auth (`POST /auth/login` + global `[Authorize]`) is intentionally deferred per request; wiring it is what flips §1–§5 `[~]`→`[x]`. FEFO pick-ordering left as a documented deferral (would conflict with FIFO-costing integrity without a physical/cost layer split); negative-stock stays the resolved global block (open-decision #2). From 67150425e42577eab84ee186888558ae6980ff86 Mon Sep 17 00:00:00 2001 From: ImanThiyanga Date: Tue, 14 Jul 2026 16:30:45 +0530 Subject: [PATCH 2/3] Add migration to add auth_user_id column to users table - Introduced a new column 'auth_user_id' of type UUID to the 'users' table. - Updated existing user data to set 'auth_user_id' to null for UserId 1. - Created a unique index on 'auth_user_id' to enforce uniqueness. - Implemented rollback functionality to remove the column and index if needed. --- .../ERPCore/Controllers/ApiControllerBase.cs | 6 +- Backend/ERPCore/Domain/Entities/User.cs | 15 +- Backend/ERPCore/Infra/Auth/AuthHexClaims.cs | 17 + .../ERPCore/Infra/Auth/JwtAuthExtensions.cs | 49 +- .../Auth/ShadowUserClaimsTransformation.cs | 76 + .../Configurations/UserConfiguration.cs | 5 + .../20260714103443_AddAuthUserId.Designer.cs | 2229 +++++++++++++++++ .../20260714103443_AddAuthUserId.cs | 46 + .../Migrations/ErpDbContextModelSnapshot.cs | 7 + Backend/ERPCore/Program.cs | 7 +- Backend/ERPCore/appsettings.Development.json | 3 - Backend/ERPCore/appsettings.json | 11 +- Backend/PROGRESS.md | 65 +- docs/10-BACKEND-PHASE1.md | 17 +- docs/11-BACKEND-PHASE1.md | 22 +- 15 files changed, 2506 insertions(+), 69 deletions(-) create mode 100644 Backend/ERPCore/Infra/Auth/AuthHexClaims.cs create mode 100644 Backend/ERPCore/Infra/Auth/ShadowUserClaimsTransformation.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.Designer.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.cs diff --git a/Backend/ERPCore/Controllers/ApiControllerBase.cs b/Backend/ERPCore/Controllers/ApiControllerBase.cs index ad4d1e3..e00e8b2 100644 --- a/Backend/ERPCore/Controllers/ApiControllerBase.cs +++ b/Backend/ERPCore/Controllers/ApiControllerBase.cs @@ -1,5 +1,7 @@ using ERPCore.Common.Http; +using ERPCore.Infra.Auth; using ERPCore.System.Errors; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers; @@ -8,10 +10,12 @@ namespace ERPCore.Controllers; /// Base for the v1 API controllers. Centralises ETag / If-Match handling /// (docs/11-BACKEND-PHASE1.md §1.6) so concurrency behaviour is uniform. /// Each controller declares its own explicit lowercase [Route] to match -/// the API contract paths (docs/11 §1.1). +/// the API contract paths (docs/11 §1.1). Every v1 endpoint requires a valid +/// AuthHex token satisfying the ERP door policy (docs/10 A.4). /// [ApiController] [Produces("application/json")] +[Authorize(JwtAuthExtensions.ErpAccessPolicy)] public abstract class ApiControllerBase : ControllerBase { /// Parse a mandatory If-Match header, or 428 if absent/malformed. diff --git a/Backend/ERPCore/Domain/Entities/User.cs b/Backend/ERPCore/Domain/Entities/User.cs index 24b58db..2966e68 100644 --- a/Backend/ERPCore/Domain/Entities/User.cs +++ b/Backend/ERPCore/Domain/Entities/User.cs @@ -3,18 +3,23 @@ using ERPCore.Domain.Enums; namespace ERPCore.Domain.Entities; /// -/// Application user (FR-X-01). In Phase 1 authentication/RBAC are deferred; this -/// table exists so mutations can be stamped with an audit actor and documents can -/// carry a `createdBy`/`requestedBy` FK. A seeded system user (id 1) is the -/// fallback actor until `/auth/login` lands (§6). Model: docs/10 Part C.7. +/// Application user (FR-X-01) — a **local shadow/projection** of an AuthHex identity. +/// The local (long) is what every `createdBy`/`requestedBy`/ +/// audit/ledger FK references; maps it to the AuthHex +/// UserId (GUID) and is JIT-provisioned on first authenticated request +/// (docs/10 A.4/C.7). A seeded system user (id 1, null AuthUserId) is the +/// fallback actor for unauthenticated/system operations. Model: docs/10 Part C.7. /// public class User { - /// Seeded fallback actor used while auth is deferred. + /// Seeded fallback actor for unauthenticated/system operations. public const long SystemUserId = 1; public long UserId { get; set; } public string Username { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public EntityStatus Status { get; set; } = EntityStatus.Active; + + /// AuthHex identity (token UserId GUID); null for the seeded system user. + public Guid? AuthUserId { get; set; } } diff --git a/Backend/ERPCore/Infra/Auth/AuthHexClaims.cs b/Backend/ERPCore/Infra/Auth/AuthHexClaims.cs new file mode 100644 index 0000000..e35400a --- /dev/null +++ b/Backend/ERPCore/Infra/Auth/AuthHexClaims.cs @@ -0,0 +1,17 @@ +namespace ERPCore.Infra.Auth; + +/// +/// Claim type names emitted by the AuthHex IdP (see its JwtTokenHelper). +/// AuthHex uses no standard sub/nameid; identity is the custom +/// (GUID). These are read verbatim (JWT bearer is configured +/// with MapInboundClaims = false). +/// +public static class AuthHexClaims +{ + public const string UserId = "UserId"; + public const string UserTypeId = "UserTypeId"; + public const string UserTypeCode = "UserTypeCode"; + public const string RoleId = "RoleId"; + public const string RoleCode = "RoleCode"; + public const string Nic = "NIC"; +} diff --git a/Backend/ERPCore/Infra/Auth/JwtAuthExtensions.cs b/Backend/ERPCore/Infra/Auth/JwtAuthExtensions.cs index 10ca70f..ea879ca 100644 --- a/Backend/ERPCore/Infra/Auth/JwtAuthExtensions.cs +++ b/Backend/ERPCore/Infra/Auth/JwtAuthExtensions.cs @@ -1,25 +1,42 @@ -using System.Text; +using System.Security.Cryptography; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; namespace ERPCore.Infra.Auth; /// -/// JWT bearer wiring. Authentication only — RBAC/authorization policies are -/// deferred for Phase 1; the validated principal exists solely so that -/// can stamp the audit actor. +/// Auth wiring for ERPCore as a **resource server** for the external AuthHex IdP +/// (docs/10 A.4). Validates AuthHex's **RS256** tokens against AuthHex's RSA public +/// key (configured statically — no JWKS), issuer AuthHex, audience +/// AuthHexClient. A single door policy () admits +/// only ERP UserType/Role holders when those codes are configured; +/// per-endpoint RBAC stays deferred. Identity → audit actor is resolved by +/// + . /// public static class JwtAuthExtensions { + /// Authorization policy applied to every v1 controller (via ApiControllerBase). + public const string ErpAccessPolicy = "ErpAccess"; + public static IServiceCollection AddErpJwtAuth(this IServiceCollection services, IConfiguration config) { - var issuer = config["Jwt:Issuer"]; - var audience = config["Jwt:Audience"]; - var signingKey = config["Jwt:SigningKey"] ?? string.Empty; + var issuer = config["Auth:Issuer"]; + var audience = config["Auth:Audience"]; + var publicKeyXml = config["Auth:RsaPublicKeyXml"] + ?? throw new InvalidOperationException("Auth:RsaPublicKeyXml (AuthHex RSA public key) is not configured."); + var requiredUserType = config["Auth:RequiredUserTypeCode"]; + var requiredRole = config["Auth:RequiredRoleCode"]; + + // AuthHex publishes no JWKS; the RSA public key is configured statically. + var rsa = RSA.Create(); + rsa.FromXmlString(publicKeyXml); + var signingKey = new RsaSecurityKey(rsa); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { + // Keep AuthHex's claim names verbatim (UserId, UserTypeCode, RoleCode …). + options.MapInboundClaims = false; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, @@ -28,12 +45,26 @@ public static class JwtAuthExtensions ValidAudience = audience, ValidateLifetime = true, ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)), + IssuerSigningKey = signingKey, + ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 }, ClockSkew = TimeSpan.FromSeconds(30) }; }); - services.AddAuthorization(); + services.AddAuthorization(options => + { + options.AddPolicy(ErpAccessPolicy, policy => + { + policy.RequireAuthenticatedUser(); + // Door gate: only enforce a UserType/Role when configured (AuthHex is a + // shared IdP). Empty config = require a valid ERP token only. + if (!string.IsNullOrWhiteSpace(requiredUserType)) + policy.RequireClaim(AuthHexClaims.UserTypeCode, requiredUserType); + if (!string.IsNullOrWhiteSpace(requiredRole)) + policy.RequireClaim(AuthHexClaims.RoleCode, requiredRole); + }); + }); + return services; } } diff --git a/Backend/ERPCore/Infra/Auth/ShadowUserClaimsTransformation.cs b/Backend/ERPCore/Infra/Auth/ShadowUserClaimsTransformation.cs new file mode 100644 index 0000000..ebf7f21 --- /dev/null +++ b/Backend/ERPCore/Infra/Auth/ShadowUserClaimsTransformation.cs @@ -0,0 +1,76 @@ +using System.Security.Claims; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Infra.Persistence; +using Microsoft.AspNetCore.Authentication; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Infra.Auth; + +/// +/// Maps an authenticated AuthHex principal to ERPCore's local identity (docs/10 A.4/A.5). +/// AuthHex tokens carry the user as a custom UserId (GUID) claim and no +/// sub/nameid. This transformation JIT-provisions a local shadow +/// (keyed by auth_user_id) and injects the local +/// long id as , so +/// /AuditUserId resolve the real user unchanged. +/// Idempotent — may run several times per request. +/// +public sealed class ShadowUserClaimsTransformation : IClaimsTransformation +{ + private readonly ErpDbContext _db; + + public ShadowUserClaimsTransformation(ErpDbContext db) => _db = db; + + public async Task TransformAsync(ClaimsPrincipal principal) + { + if (principal.Identity is not ClaimsIdentity identity || !identity.IsAuthenticated) + return principal; + if (identity.HasClaim(c => c.Type == ClaimTypes.NameIdentifier)) + return principal; // already resolved this request + + var raw = principal.FindFirstValue(AuthHexClaims.UserId); + if (!Guid.TryParse(raw, out var authUserId)) + return principal; // no mappable identity → CurrentUser falls back to system + + var nic = principal.FindFirstValue(AuthHexClaims.Nic); + var localId = await ResolveOrProvisionAsync(authUserId, nic); + + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, localId.ToString())); + return principal; + } + + private async Task ResolveOrProvisionAsync(Guid authUserId, string? nic) + { + var existing = await _db.Users.AsNoTracking() + .Where(u => u.AuthUserId == authUserId) + .Select(u => u.UserId) + .FirstOrDefaultAsync(); + if (existing != 0) return existing; + + var label = string.IsNullOrWhiteSpace(nic) ? authUserId.ToString() : nic.Trim(); + var user = new User + { + AuthUserId = authUserId, + Username = label, + DisplayName = string.IsNullOrWhiteSpace(nic) ? "AuthHex User" : nic.Trim(), + Status = EntityStatus.Active + }; + + try + { + _db.Users.Add(user); + await _db.SaveChangesAsync(); + return user.UserId; + } + catch (DbUpdateException) + { + // Lost a race (unique auth_user_id) — the row now exists; re-read it. + _db.Entry(user).State = EntityState.Detached; + return await _db.Users.AsNoTracking() + .Where(u => u.AuthUserId == authUserId) + .Select(u => u.UserId) + .FirstAsync(); + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/UserConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/UserConfiguration.cs index 7a1c48b..eee8827 100644 --- a/Backend/ERPCore/Infra/Persistence/Configurations/UserConfiguration.cs +++ b/Backend/ERPCore/Infra/Persistence/Configurations/UserConfiguration.cs @@ -18,6 +18,11 @@ public sealed class UserConfiguration : IEntityTypeConfiguration builder.Property(u => u.Status) .HasConversion().HasMaxLength(20).IsRequired(); + // Maps the local shadow user to its AuthHex identity (unique; NULL for the + // system user — Postgres allows multiple NULLs in a unique index). + builder.Property(u => u.AuthUserId).HasColumnName("auth_user_id"); + builder.HasIndex(u => u.AuthUserId).IsUnique(); + // Seeded fallback audit actor while auth is deferred (§6). builder.HasData(new User { diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.Designer.cs new file mode 100644 index 0000000..7251936 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.Designer.cs @@ -0,0 +1,2229 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20260714103443_AddAuthUserId")] + partial class AddAuthUserId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EntityId") + .HasColumnType("bigint"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.HasKey("CategoryId"); + + b.HasIndex("ParentId"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("GrnId") + .HasColumnType("bigint"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoLineId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + + b.Property("BaseUomId") + .HasColumnType("bigint"); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultVendorId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxClass") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ItemId"); + + b.HasIndex("BaseUomId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("bigint") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("integer") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("DocType", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("PoId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("PoId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReturnId") + .HasColumnType("bigint"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("bigint"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("bigint"); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("CountId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CountId") + .HasColumnType("bigint"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("GrnLineId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("BinId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SourceDocId") + .HasColumnType("bigint"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("bigint"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DestWarehouseId") + .HasColumnType("bigint"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SrcWarehouseId") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("bigint"); + + b.Property("DestBinId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("bigint"); + + b.Property("SrcBinId") + .HasColumnType("bigint"); + + b.Property("TransferId") + .HasColumnType("bigint"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.Property("ConversionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + + b.Property("Factor") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("FromUomId") + .HasColumnType("bigint"); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("ToUomId") + .HasColumnType("bigint"); + + b.HasKey("ConversionId"); + + b.HasIndex("FromUomId"); + + b.HasIndex("ToUomId"); + + b.HasIndex("ItemId", "FromUomId", "ToUomId") + .IsUnique(); + + b.ToTable("uom_conversions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("uuid") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1L, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("character varying(3)") + .HasDefaultValue("LKR"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("VendorId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RfqId") + .HasColumnType("bigint"); + + b.Property("VendorId") + .HasColumnType("bigint"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("bigint"); + + b.Property("LeadDays") + .HasColumnType("integer"); + + b.Property("QuotationId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("WarehouseId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Count"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") + .WithMany() + .HasForeignKey("FromUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("UomConversions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") + .WithMany() + .HasForeignKey("ToUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + + b.Navigation("UomConversions"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.cs new file mode 100644 index 0000000..026db87 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + /// + public partial class AddAuthUserId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "auth_user_id", + table: "users", + type: "uuid", + nullable: true); + + migrationBuilder.UpdateData( + table: "users", + keyColumn: "UserId", + keyValue: 1L, + column: "auth_user_id", + value: null); + + migrationBuilder.CreateIndex( + name: "IX_users_auth_user_id", + table: "users", + column: "auth_user_id", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_users_auth_user_id", + table: "users"); + + migrationBuilder.DropColumn( + name: "auth_user_id", + table: "users"); + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs b/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs index 0d3e800..099275e 100644 --- a/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs +++ b/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs @@ -1301,6 +1301,10 @@ namespace ERPCore.Infra.Persistence.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + b.Property("AuthUserId") + .HasColumnType("uuid") + .HasColumnName("auth_user_id"); + b.Property("DisplayName") .IsRequired() .HasMaxLength(200) @@ -1318,6 +1322,9 @@ namespace ERPCore.Infra.Persistence.Migrations b.HasKey("UserId"); + b.HasIndex("AuthUserId") + .IsUnique(); + b.HasIndex("Username") .IsUnique(); diff --git a/Backend/ERPCore/Program.cs b/Backend/ERPCore/Program.cs index d03cea2..e4fcd86 100644 --- a/Backend/ERPCore/Program.cs +++ b/Backend/ERPCore/Program.cs @@ -8,6 +8,7 @@ using ERPCore.Services; using ERPCore.Services.Interfaces; using ERPCore.Services.Stock; using ERPCore.System.Errors; +using Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi; using Serilog; @@ -31,12 +32,14 @@ builder.Services.AddDbContext(o => builder.Services.AddProblemDetails(); builder.Services.AddExceptionHandler(); -// JWT bearer auth (RBAC deferred; identity used only for the audit stamp) +// Auth: validate external AuthHex RS256 tokens + ERP door policy (docs/10 A.4) builder.Services.AddErpJwtAuth(builder.Configuration); -// Current-user (audit actor) derived from token `sub` +// Current-user (audit actor). AuthHex has no sub/nameid → a claims transformation +// JIT-provisions a local shadow user and injects the local `long` id as `nameid`. builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Unit of work + generic repository base builder.Services.AddScoped(); diff --git a/Backend/ERPCore/appsettings.Development.json b/Backend/ERPCore/appsettings.Development.json index d8c99f7..3d13631 100644 --- a/Backend/ERPCore/appsettings.Development.json +++ b/Backend/ERPCore/appsettings.Development.json @@ -7,8 +7,5 @@ }, "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Database=ERPCore;Username=postgres;Password=root" - }, - "Jwt": { - "SigningKey": "dev-only-signing-key-please-change-me-0123456789" } } diff --git a/Backend/ERPCore/appsettings.json b/Backend/ERPCore/appsettings.json index 0b08225..d49cb2d 100644 --- a/Backend/ERPCore/appsettings.json +++ b/Backend/ERPCore/appsettings.json @@ -8,11 +8,12 @@ "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Database=ERPCore;Username=CHANGE_ME;Password=CHANGE_ME" }, - "Jwt": { - "Issuer": "ERPCore", - "Audience": "ERPCore.Clients", - "SigningKey": "CHANGE_ME_DEV_ONLY_32+_CHARS", - "AccessTokenMinutes": 120 + "Auth": { + "Issuer": "AuthHex", + "Audience": "AuthHexClient", + "RsaPublicKeyXml": "1LlNkMBQNdpXJiDal7XMxkG/3ad+YBsMCuY9JD/abHMzniFXtQlovjfbeaaHJ0v1kvSo9731CJ0YC1qhPU5rPQwZwxOWZ9BOBZlMDghONdjOH/HyCUbb5Z18ibqc0QenFSnEYz+jkVZiayj8DV/+VUe+eKzpQTlU6aWtHvlbwfuXaDu+QvFlpLJ7/m8na+0s2nYhLX8Wfi4C/2AoNaYhFkIwYhMMGoaSHuIoQ5R6181Rh0gKvYopRW+IpTD5RV8bXV3AM6zOcoisOifBYROHwA5ZZpoHXuTvHYmPWW8kL8PKme7BwBldPi8KrJUroRE+WXA87aAA5Wtt1oxePcXvhQ==AQAB", + "RequiredUserTypeCode": "", + "RequiredRoleCode": "" }, "AllowedHosts": "*" } diff --git a/Backend/PROGRESS.md b/Backend/PROGRESS.md index a1761e9..ba8a854 100644 --- a/Backend/PROGRESS.md +++ b/Backend/PROGRESS.md @@ -8,57 +8,57 @@ Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** - [x] Solution + Web API project (`net10.0`), packages restored (00-CORE §5.4) - [x] Folder structure per 00-CORE §5.3 - [x] `ErpDbContext` + Npgsql wired; `InitialCreate` migration **created and applied** (2026-07-10, 8 master-data tables). `/health` → `Healthy`. -- [x] Serilog, JWT, Swagger, HealthChecks, ProblemDetails in `Program.cs` (JWT bearer *validated*; endpoints not yet `[Authorize]`-gated — see §6 auth note) +- [x] Serilog, JWT, Swagger, HealthChecks, ProblemDetails in `Program.cs` — JWT bearer now validates **RS256** tokens from the external **AuthHex IdP** (issuer `AuthHex` / audience `AuthHexClient` / static RSA public key). v1 endpoints `[Authorize]`-gated via the `ErpAccess` door policy (§6); `/health`, `/api/meta`, Swagger stay anonymous. - [x] `IUnitOfWork` + `UnitOfWork` (transaction boundary) - [x] Generic repository base + interfaces -- [x] `ICurrentUser` (audit stamp from token `sub`) +- [x] `ICurrentUser` (audit stamp from token identity claim `nameid`/`sub`) — with AuthHex the actor comes from the `UserId` GUID → local shadow user (`nameid` injected by the §6 provisioning step) - [x] ProblemDetails middleware + domain exception → `code` mapping (System/Errors; full §7 catalog added to `ErrorCodes`) ## 1. Master Data -> Code complete for all items below (2026-07-09). **Live smoke test PASSED against Postgres (2026-07-10):** create/get/list/update/status/reorder/uom-conversions across all 5 controllers; ETag round-trip 200 / stale→412 / missing→428; SKU_DUPLICATE→400; bad reference→422; missing-field→400 ValidationProblemDetails; category `?tree=true` nesting; `pageSize=9999` clamped to 200; deactivate via PATCH status→204. Still `[~]` (not `[x]`) for **one** reason: the **security gate** (00-CORE §8) — the foundational auth control (02-SECURITY B.1) and the audit trail (B.3, the AR-01 compensating control) land in §6. Flip to `[x]` once §6 auth+audit are wired. +> Code complete for all items below (2026-07-09). **Live smoke test PASSED against Postgres (2026-07-10):** create/get/list/update/status/reorder/uom-conversions across all 5 controllers; ETag round-trip 200 / stale→412 / missing→428; SKU_DUPLICATE→400; bad reference→422; missing-field→400 ValidationProblemDetails; category `?tree=true` nesting; `pageSize=9999` clamped to 200; deactivate via PATCH status→204. **Flipped `[x]` on 2026-07-14** — the §6 security gate (00-CORE §8: auth control 02-SECURITY B.1 + audit trail B.3) is now met (AuthHex RS256 validation + `[Authorize]` door policy + shadow-user provisioning, and the audit trail). The dated smoke-test notes in §2–§5 that reference a pending "§6 gate" are historical. - [x] Item: entity + config + enums (ItemType, TrackingMode; EntityStatus added) — `xmin`/RowVersion concurrency token (Npgsql), unique SKU -- [~] Item: repository (generic) + service + controller (CRUD, narrow DTOs, ETag/If-Match→412, SKU_DUPLICATE, reference validation) — server-controlled fields excluded (02-SECURITY C.1) -- [~] UOM + UOM conversions (`GET/POST /uoms`, `PUT /items/{id}/uom-conversions` full-replace upsert) -- [~] Category (hierarchy, `GET /categories?tree=true` nested build, parent-exists validation) -- [~] Vendor (CRUD, ETag/If-Match, unique code, deactivate via `PATCH /vendors/{id}/status`) -- [~] Warehouse + Bin (`/warehouses`, nested `/warehouses/{id}/bins`, bin code unique per warehouse) -- [~] Item reorder settings (`PUT /items/{id}/reorder` full-replace upsert, warehouse-exists validation) +- [x] Item: repository (generic) + service + controller (CRUD, narrow DTOs, ETag/If-Match→412, SKU_DUPLICATE, reference validation) — server-controlled fields excluded (02-SECURITY C.1) +- [x] UOM + UOM conversions (`GET/POST /uoms`, `PUT /items/{id}/uom-conversions` full-replace upsert) +- [x] Category (hierarchy, `GET /categories?tree=true` nested build, parent-exists validation) +- [x] Vendor (CRUD, ETag/If-Match, unique code, deactivate via `PATCH /vendors/{id}/status`) +- [x] Warehouse + Bin (`/warehouses`, nested `/warehouses/{id}/bins`, bin code unique per warehouse) +- [x] Item reorder settings (`PUT /items/{id}/reorder` full-replace upsert, warehouse-exists validation) ## 2. Procurement > Requisition/RFQ/PO implemented 2026-07-10. **Live smoke test PASSED** (docNo `PR/RFQ/PO-2026-#####` gap-controlled + incrementing, requestedBy/createdBy = seeded system user, RFQ comparison matrix, duplicate quotation→409, PO auto-approve + server-computed totals matching the spec example `112100/20178/132278`, edit-while-open with recomputed totals + ETag 200/412, PO_NOT_EDITABLE→409 on cancelled, cancel→200, bad reference→422). Same `[~]` reason as §1: the §6 security gate (auth + audit) is not yet wired. -- [~] Requisition (+ lines) + submit (`POST /requisitions`, `/{id}/submit`, list, get) -- [~] RFQ + quotations + comparison (`POST /rfqs`, `/{id}/quotations` [one per vendor], `GET /{id}/comparison` matrix) -- [~] Purchase Order: create (auto-approve, `approvalRequired` flag), edit-while-open (If-Match), approve (no-op), cancel -- [~] Purchase Return (outbound movement, reason code) — `POST /purchase-returns` auto-posts an outbound FIFO consume via shared `StockMutator`; mandatory Return-context reason (`REASON_CODE_REQUIRED`→400, wrong context→422), references the GRN line for traceability, over-return→`409 STOCK_NEGATIVE_BLOCKED`. Verified. (Cumulative return-vs-received cap still relies on the stock-availability guard.) +- [x] Requisition (+ lines) + submit (`POST /requisitions`, `/{id}/submit`, list, get) +- [x] RFQ + quotations + comparison (`POST /rfqs`, `/{id}/quotations` [one per vendor], `GET /{id}/comparison` matrix) +- [x] Purchase Order: create (auto-approve, `approvalRequired` flag), edit-while-open (If-Match), approve (no-op), cancel +- [x] Purchase Return (outbound movement, reason code) — `POST /purchase-returns` auto-posts an outbound FIFO consume via shared `StockMutator`; mandatory Return-context reason (`REASON_CODE_REQUIRED`→400, wrong context→422), references the GRN line for traceability, over-return→`409 STOCK_NEGATIVE_BLOCKED`. Verified. (Cumulative return-vs-received cap still relies on the stock-availability guard.) > **Deviation (recorded):** `VendorQuotation` is modelled as header + `VendorQuotationLine` (per-item pricing) to satisfy the API contract (docs/11 §3.2); docs/10 Part C.2's scalar `VENDOR_QUOTATION(unit_price, lead_days)` with no item ref cannot represent it. Update the ER model doc to match. > **RFQ `vendorIds`** are validated for existence but not persisted (no RFQ↔vendor link in the model); quotations reference vendors directly. ## 3. Goods Receipt > Implemented + **smoke test PASSED** 2026-07-13 (see §4 note for the shared stock verification). Same `[~]` reason as §1/§2: the §6 auth+audit gate. -- [~] GRN create (against PO / direct), over-receipt tolerance — `unitCost` **PO-derived server-side** (client `999` verified ignored → PO price used, 02-SECURITY C.3); direct receipt requires `vendorId` + entered cost (AR-04); over-receipt → `422 OVER_RECEIPT_TOLERANCE` (verified at open-qty boundary); batch created/reused per (item, batchNo). Serial capture deferred. -- [~] GRN confirm → FIFO layer + ledger + PO `qtyReceived` (single UoW txn) — verified: layers+ledger posted, running balance, PO → PartiallyReceived/FullyReceived, UOM→base conversion (10 Box-12 → 120 base @10). **Idempotent** re-confirm verified (no double-post). Note: `Idempotency-Key` accepted but idempotency is resource-state based (already-Confirmed replays existing result); a keyed idempotency store is deferred. -- [~] Inspection hold release / reject — Release fully verified (OnHold excluded from `available`, then released). Reject removes on-hand + posts a reversing ledger entry; formal link to a Purchase Return is deferred (§3.4). +- [x] GRN create (against PO / direct), over-receipt tolerance — `unitCost` **PO-derived server-side** (client `999` verified ignored → PO price used, 02-SECURITY C.3); direct receipt requires `vendorId` + entered cost (AR-04); over-receipt → `422 OVER_RECEIPT_TOLERANCE` (verified at open-qty boundary); batch created/reused per (item, batchNo). Serial capture deferred. +- [x] GRN confirm → FIFO layer + ledger + PO `qtyReceived` (single UoW txn) — verified: layers+ledger posted, running balance, PO → PartiallyReceived/FullyReceived, UOM→base conversion (10 Box-12 → 120 base @10). **Idempotent** re-confirm verified (no double-post). Note: `Idempotency-Key` accepted but idempotency is resource-state based (already-Confirmed replays existing result); a keyed idempotency store is deferred. +- [x] Inspection hold release / reject — Release fully verified (OnHold excluded from `available`, then released). Reject removes on-hand + posts a reversing ledger entry; formal link to a Purchase Return is deferred (§3.4). ## 4. Stock Core > Implemented + **live smoke test PASSED** 2026-07-13: receive→confirm creates FIFO layers + inbound ledger (qtyBase/unitCost/value/runningBalance correct), on-hand/valuation/ledger queries correct, OnHold excluded from `available`, UOM→base conversion applied. Same `[~]` gate (§6 auth+audit). - [x] StockLayer + StockLedger entities/config — ledger **append-only at the app level** (never updated/deleted); DB-role `UPDATE`/`DELETE` revoke is deferred hardening (02-SECURITY B.3). Layers keyed per item **per warehouse**, base-UOM qty + unit cost; ledger polymorphic source (`sourceDocType`/`sourceDocId`), time-series indexes. -- [~] `FifoCostingService` — inbound layer + ledger posting + valuation **and oldest-first consume with row lock** (`SELECT … FOR UPDATE`, on-hold/expired exclusion, negative-stock block) all implemented + verified 2026-07-13 via §5. Blended cost on multi-layer consume verified (700@10 + 100@12 → 10.25). -- [~] Stock enquiry (onHand / available / onHold / inTransit) — onHand/available/onHold **and inTransit** now live + verified (inTransit = outstanding InTransit-transfer qty out of this warehouse). `reserved` stays a 0 stub until Sales. +- [x] `FifoCostingService` — inbound layer + ledger posting + valuation **and oldest-first consume with row lock** (`SELECT … FOR UPDATE`, on-hold/expired exclusion, negative-stock block) all implemented + verified 2026-07-13 via §5. Blended cost on multi-layer consume verified (700@10 + 100@12 → 10.25). +- [x] Stock enquiry (onHand / available / onHold / inTransit) — onHand/available/onHold **and inTransit** now live + verified (inTransit = outstanding InTransit-transfer qty out of this warehouse). `reserved` stays a 0 stub until Sales. - [x] Ledger query · Valuation query — `GET /stock/ledger` (item/warehouse/from/to + paging), `GET /stock/valuation` (open layers, totals, FIFO); both verified. ## 5. Stock Transactions > **All four §5 features implemented + live smoke test PASSED 2026-07-13** (Adjustment, Transfer, Count, Reorder alerts); Purchase Return (§3.4) also done this pass. Same `[~]` gate (§6 auth+audit). -- [~] Transfer: create → dispatch (consume source FIFO row-locked → In-Transit) → receive (dest layer, **cost-preserving**) — verified: dispatch reduces source onHand + reports inTransit; receive creates dest layer at inherited cost (300 @12 → dest value 3600); `destWarehouseId != srcWarehouseId`→422; dispatch short→`409 STOCK_NEGATIVE_BLOCKED`. Partial receive supported (`QtyReceived`). -- [~] Adjustment (auto-post, mandatory reason code) — **highest-risk feature (02-SECURITY C.5)**: `REASON_CODE_REQUIRED`→400, non-Adjustment reason→422, decrease FIFO-consumes (blended cost, negative→409), increase creates a layer at last cost. All verified. -- [~] Count (cycle/full → enter counts → variance → post) — create snapshots systemQty (immutable), enter sets counted+variance→Counted, post emits a variance `StockAdjustment` via shared `StockMutator` + closes the count. Verified: variance −15 (post→on-hand 485) and +10 increase; re-post→409. -- [~] Reorder alerts (query) + suggest requisition — `GET /stock/reorder-alerts` (available ≤ ROP, computed on read) + `POST …/{itemId}/requisition` (draft PR at suggested qty). Verified. -syte +- [x] Transfer: create → dispatch (consume source FIFO row-locked → In-Transit) → receive (dest layer, **cost-preserving**) — verified: dispatch reduces source onHand + reports inTransit; receive creates dest layer at inherited cost (300 @12 → dest value 3600); `destWarehouseId != srcWarehouseId`→422; dispatch short→`409 STOCK_NEGATIVE_BLOCKED`. Partial receive supported (`QtyReceived`). +- [x] Adjustment (auto-post, mandatory reason code) — **highest-risk feature (02-SECURITY C.5)**: `REASON_CODE_REQUIRED`→400, non-Adjustment reason→422, decrease FIFO-consumes (blended cost, negative→409), increase creates a layer at last cost. All verified. +- [x] Count (cycle/full → enter counts → variance → post) — create snapshots systemQty (immutable), enter sets counted+variance→Counted, post emits a variance `StockAdjustment` via shared `StockMutator` + closes the count. Verified: variance −15 (post→on-hand 485) and +10 increase; re-post→409. +- [x] Reorder alerts (query) + suggest requisition — `GET /stock/reorder-alerts` (available ≤ ROP, computed on read) + `POST …/{itemId}/requisition` (draft PR at suggested qty). Verified. + ## 6. Cross-cutting -> **Status:** audit trail, doc numbering, reason codes, JournalEntryStub, negative-stock block all **done**. The **one** remaining item is authentication. **Auth-enforcement gap (open):** JWT bearer *validation* is wired, but no token issuer exists yet and controllers are **not** `[Authorize]`-gated, so endpoints are currently open. This is the AR-01/NFR-03 control surface — gate all v1 endpoints (fallback authorization policy) in the same change as `POST /auth/login`, then re-run the 02-SECURITY B.1 checklist and flip §1–§5 items to `[x]`. (The AR-01 **audit** compensating control is now in place.) +> **Status: COMPLETE.** Audit trail, doc numbering, reason codes, JournalEntryStub, negative-stock block, and now **authentication** (external AuthHex IdP integration) are all done + verified. The §6 security gate (NFR-03 auth + AR-01 audit) is met — **§1–§5 flipped `[~]`→`[x]`** (2026-07-14). FEFO pick-ordering is the only intentional deferral. - [x] Audit log on every mutation (who/when/old→new) — `AuditLog` (jsonb `changeSet`), written by an `ErpDbContext.SaveChanges` override (`AuditScribe`): Create captures the field set, Update captures **only changed fields as {old,new}**, Delete captures the prior row; PK/RowVersion excluded; ledger/layer/seq/self/journal excluded. Actor from `ICurrentUser` (system=1 until auth). Read via `GET /audit-logs`. **Verified** (Item create+update old→new; StockAdjustment create). This is the **AR-01 compensating control** (02-SECURITY B.3) — app-level append-only; DB-role UPDATE/DELETE revoke still deferred. - [x] Document numbering sequences (per type, per year) — `NumberSequence` + `NumberSequenceService` (atomic `INSERT … ON CONFLICT … RETURNING` inside the doc's UoW txn; gap-controlled). Verified issuing + incrementing PR/RFQ/PO. -- [~] Auth: simple in-app login → JWT (`POST /auth/login`) — foundation only: `User` table + seeded `system` user (id 1) exist and `ICurrentUser.AuditUserId` stamps docs; login endpoint + `[Authorize]` still pending. +- [x] Auth: **external AuthHex IdP integration** (2026-07-14) — ERPCore is a resource server. `JwtAuthExtensions` validates **RS256** against AuthHex's RSA **public** key (config `Auth:RsaPublicKeyXml` → `RsaSecurityKey`; `MapInboundClaims=false`), issuer `AuthHex`, audience `AuthHexClient` (no JWKS → static key). `[Authorize(ErpAccess)]` on `ApiControllerBase` gates every v1 endpoint; the `ErpAccess` policy `RequireAuthenticatedUser` + optional `RequireClaim(UserTypeCode/RoleCode)` from `Auth:RequiredUserTypeCode`/`RequiredRoleCode` (empty ⇒ any valid ERP token — AuthHex is ERP-dedicated). **Shadow-user JIT provisioning:** `ShadowUserClaimsTransformation` (`IClaimsTransformation`) maps the token's `UserId` **GUID** → a local `users` row (`auth_user_id` unique; Username/DisplayName = `NIC`), idempotent, and injects the local `long` id as `nameid` so `ICurrentUser.AuditUserId` resolves the real actor. Migration `AddAuthUserId`. **Verified:** no token→401; `/health`,`/api/meta`,Swagger anonymous; valid token→200; shadow user provisioned (User 2, Username=NIC, AuthUserId=GUID); item Create **audited as the shadow user (id 2, not system)**; re-request reuses the same user; door gate → **403** on UserType mismatch, **200** on match. - [x] JournalEntryStub emitted per stock movement (data only) — `JournalEntryStub` written in `FifoCostingService.PostLedgerAsync` for every ledger entry (In → Dr Inventory `1300` / Cr Clearing `2100`; Out reverses; amount = movement value). Placeholder accounts until a chart of accounts exists. Read via `GET /journal-entries`. **Verified** (GRN In 700, ADJ Out 70). - [x] Negative-stock policy enforcement (default block) — enforced in `FifoCostingService.ConsumeAsync` → `409 STOCK_NEGATIVE_BLOCKED` (verified). Per-item override still a config stub. - [~] FEFO picking for perishables; block expired / on-hold issue — **issue-block done + verified** (`ONHOLD_NOT_ISSUABLE`, `EXPIRED_BATCH_BLOCKED`; on-hold/expired layers excluded from consume). FEFO *pick ordering* (oldest-expiry first) not yet built. @@ -119,4 +119,17 @@ syte - **JournalEntryStub (FR-STK-13):** emitted for every ledger entry in `FifoCostingService.PostLedgerAsync` (In → Dr `1300`/Cr `2100`; Out reverses; amount = value). Placeholder GL accounts. - Read endpoints (auditor role, beyond documented §11): `GET /audit-logs` (entityType/entityId/userId/from/to), `GET /journal-entries` (sourceDocType/sourceDocId). `AuditService`. Migration `AddAuditAndJournal` (2 tables, jsonb) applied. - **Verified against Postgres:** item Create logged full field set (userId 1); item Update logged only `Name` + `UpdatedAt` as `{old,new}`; GRN confirm → journal In Dr1300/Cr2100 amount 700; adjustment decrease → journal Out Dr2100/Cr1300 amount 70; StockAdjustment Create audited. -- **Only auth remains for Phase 1.** Everything else in §6 is done. Auth (`POST /auth/login` + global `[Authorize]`) is intentionally deferred per request; wiring it is what flips §1–§5 `[~]`→`[x]`. FEFO pick-ordering left as a documented deferral (would conflict with FIFO-costing integrity without a physical/cost layer split); negative-stock stays the resolved global block (open-decision #2). +- **Only auth remains for Phase 1.** Everything else in §6 is done. Auth (~~`POST /auth/login` + global `[Authorize]`~~ — **superseded 2026-07-14**, now external **AuthHex** IdP integration; see the next entry) is intentionally deferred per request; wiring it is what flips §1–§5 `[~]`→`[x]`. FEFO pick-ordering left as a documented deferral (would conflict with FIFO-costing integrity without a physical/cost layer split); negative-stock stays the resolved global block (open-decision #2). + +### 2026-07-14 — Auth architecture change: external AuthHex IdP (docs-only pass) +- **Plan changed:** auth is no longer a local `POST /auth/login` inside ERPCore. A **separate AuthHex IdP** (runs on `:5011`, source at `c:\Users\WAS\Documents\Developments\ERP_Auth_Service\`) owns login/registration/recovery; ERPCore becomes a **resource server** that only validates AuthHex tokens. Updated `docs/10-BACKEND-PHASE1.md` (header, A.4 auth/audit-actor, A.5 DI, B.2.3, FR-X-01, NFR-03, C.7 `USER`, C.9, B.8.4 decision #10) and this file. **No code changed this pass.** +- **Decisions (confirmed):** (1) identity = **shadow-user JIT provisioning** — add `auth_user_id` GUID (unique) to `users`, keep all `long` FKs; (2) authorization = **door-gate to an ERP `UserType`/`Role`**, per-endpoint RBAC still deferred; (3) scope = **docs only** now, code integration is a follow-up. +- **Confirmed AuthHex facts:** RS256 (RSA 2048; ERPCore needs the static **public** key — no JWKS), issuer `AuthHex`, audience `AuthHexClient`, lifetime 1000 min prod / 60 min dev; claims `UserId`(GUID)/`UserTypeCode`/`RoleCode`/`NIC`/`jti`/`iat` (no `sub`/`nameid`); BCrypt password hashing; login `POST /api/loginUser {identifier,password}`. +- **Open blockers (resolve before the code phase):** exact ERP `UserTypeCode`/`RoleCode` for the door gate (must exist in AuthHex); RSA public-key distribution + rotation process (no JWKS); shadow-user `Username`/`DisplayName` source (token has no name); secrets hygiene in AuthHex config (private key/SMTP/DB in plaintext); `docs/11 §2.0` still documents `/auth/login` (now AuthHex-owned) — recommend a follow-up annotation. + +### 2026-07-14 (2) — Auth code integration: AuthHex resource server (§6 COMPLETE → §1–§5 flipped `[x]`) +- **RS256 validation:** `JwtAuthExtensions` rewritten — `RsaSecurityKey` from `Auth:RsaPublicKeyXml` (AuthHex public key), `ValidIssuer=AuthHex`, `ValidAudience=AuthHexClient`, `ValidAlgorithms=[RS256]`, `MapInboundClaims=false` (keeps `UserId`/`UserTypeCode`/`RoleCode` verbatim). `appsettings.json` `Jwt`→`Auth` (public key + issuer/audience + `RequiredUserTypeCode`/`RequiredRoleCode`); removed the HS256 dev signing key. +- **Door policy** `ErpAccess`: `RequireAuthenticatedUser` + optional `RequireClaim(UserTypeCode/RoleCode)` when configured (AuthHex is ERP-dedicated → empty default = any valid token). `[Authorize(ErpAccess)]` on `ApiControllerBase`; `MetaController`/health/Swagger stay anonymous. +- **Shadow-user provisioning:** `ShadowUserClaimsTransformation` (`IClaimsTransformation`, scoped) maps token `UserId` GUID → local `users` row (`auth_user_id` unique, Username/DisplayName=`NIC`), idempotent w/ race-safe re-read, injects local `long` id as `nameid`. `User.AuthUserId` (Guid?) + `AuthHexClaims` consts + migration `AddAuthUserId`. +- **Verified (minted AuthHex-shaped RS256 token, signed with AuthHex's real private key):** no token→401; `/health`,`/api/meta`,Swagger→200 anon; valid token→200; POST item→201 **audited as shadow user id 2** (Username=NIC, AuthUserId=GUID), not system; repeat request reuses user (1 provision); door gate `RequiredUserTypeCode=WAREHOUSE` → ERP-type token **403**, WAREHOUSE-type token **200**. Build clean; migration applied. +- **§6 COMPLETE.** Only intentional deferral left in Phase 1: FEFO pick-ordering (§6, `[~]`). Follow-ups: set the real ERP `Auth:RequiredUserTypeCode`/`RoleCode` for production; secure the RSA key rotation process. diff --git a/docs/10-BACKEND-PHASE1.md b/docs/10-BACKEND-PHASE1.md index 8a1ce63..e7dd784 100644 --- a/docs/10-BACKEND-PHASE1.md +++ b/docs/10-BACKEND-PHASE1.md @@ -3,6 +3,7 @@ > **Authoritative for:** backend architecture, business rules, and the data model (the 38-entity schema). > **Navigation:** you arrived here from `00-CORE.md`. API request/response contracts are in `11-BACKEND-PHASE1.md`. Frontend rules are in `20-FRONTEND.md`. Record work in `Backend/PROGRESS.md`. > **Scope basis:** SRS v1.1. Costing = FIFO · Multi-warehouse · Single-tenant · RBAC deferred (user identity stamped) · approvals auto/config-gated · vendor invoice + 3-way match deferred to Accounting. +> **Authentication:** delegated to the **external AuthHex identity provider** (separate service). ERPCore is a **resource server** that only *validates* AuthHex's RS256 JWTs — it does not issue tokens or own a login endpoint. See A.4 (Authentication / Audit actor). RBAC (per-endpoint) still deferred. --- @@ -48,7 +49,8 @@ HTTP ─► Controller ─► Service ─► Repository ─► UnitOfWork / ErpD ## A.4 Cross-cutting - **Errors:** RFC 7807 `ProblemDetails` (framework default). Domain exceptions in `System/Errors` carry a stable `code`; a middleware maps them to `ProblemDetails`. Catalog in `11-BACKEND-PHASE1.md §7`. -- **Audit actor:** an `ICurrentUser` abstraction (`Infra/Auth`) resolves the acting user from the JWT `sub`. Services stamp mutations with it. **Never** trust a `createdBy` from the request body. +- **Authentication:** ERPCore is a **resource server**. It validates JWTs issued by the **external AuthHex IdP** — algorithm **RS256** (asymmetric RSA), issuer `AuthHex`, audience `AuthHexClient`. AuthHex exposes **no JWKS/OIDC discovery**, so ERPCore is configured with AuthHex's **RSA public key statically** (rotation is a manual config update). Tokens live ~1000 min (prod) / 60 min (dev). A single **door authorization policy** requires an ERP `UserTypeCode`/`RoleCode` claim (AuthHex is a shared IdP, so a valid token alone is not enough); **per-endpoint RBAC stays deferred**. +- **Audit actor:** the token carries no `sub`/`nameid`; identity is AuthHex's custom **`UserId` (GUID)** claim. An `ICurrentUser` abstraction (`Infra/Auth`) resolves the acting user from a **local shadow user** — the GUID is mapped (JIT-provisioned) to a local `long` `users.user_id` that all FKs reference (see C.7). Services stamp mutations with it. **Never** trust a `createdBy` from the request body. - **Concurrency:** mutable resources carry a `RowVersion` (`[Timestamp] byte[]`), surfaced as `ETag`; `PUT`/`PATCH` require `If-Match` → `412` on mismatch. - **Numbering:** document numbers come from `NumberSequence` (per doc type, per year), issued inside the same transaction as the document. @@ -56,6 +58,7 @@ HTTP ─► Controller ─► Service ─► Repository ─► UnitOfWork / ErpD - `ErpDbContext`: scoped (default). - `IUnitOfWork`, repositories, services, `ICurrentUser`, `FifoCostingService`: **scoped**. - Register in `Program.cs` (or an `AddApplication()` extension) after `AddDbContext`. +- **Auth wiring:** JWT bearer validation is built from AuthHex's **RSA public key** (config XML → `RsaSecurityKey`) with `ValidIssuer=AuthHex`, `ValidAudience=AuthHexClient`. A scoped **`IClaimsTransformation`** provisions/looks up the local shadow user (by `auth_user_id` = token `UserId` GUID) and injects the resolved local `long` id as a `ClaimTypes.NameIdentifier` (`nameid`) claim, so `ICurrentUser`/`AuditUserId` resolve a real user unchanged (falling back to the seeded system user only when unauthenticated). --- @@ -96,7 +99,7 @@ Foundation of a modular ERP. **Single-tenant**, **multi-warehouse**. All later m Maintain master data; raise/approve procurement through PO and return; receive goods with inspection hold; track stock movements in a costed FIFO ledger across warehouses; perform counts/transfers/adjustments with audit; track batch/expiry/serial and locate by bin; raise reorder alerts. ### B.2.3 User classes -> **Phase-1 note:** role-based permissions are **deferred** (FR-X-01). Phase 1 runs a **single operational user context** — any user may perform any action, but each action is stamped with the authenticated user's identity for audit. The roles below are the functional blueprint for future RBAC, **not** enforced boundaries. +> **Phase-1 note:** role-based permissions are **deferred** (FR-X-01). Identity is supplied by the **external AuthHex IdP** (see A.4); a single door policy admits only ERP `UserType`/`Role` holders, but beyond that any admitted user may perform any action, with each action stamped with the authenticated user's identity for audit. The roles below are the functional blueprint for future per-endpoint RBAC, **not** enforced boundaries. Storekeeper/Warehouse operator (receive, count, transfer, pick) · Procurement officer (requisitions, POs, vendors) · Approver/Manager (authorizes once approvals enabled) · Inventory controller (valuation, adjustments, reorder policy) · Auditor (read-only) · System administrator (users, numbering, config). @@ -180,7 +183,7 @@ One base currency; invoicing/3-way match in Accounting (GRN carries data); users ### B.3.6 Cross-cutting (FR-X) | ID | Requirement | Pri | |---|---|---| -| FR-X-01 | **[Phase 1: user identity only]** Authenticate users and **stamp every transaction with the acting user's identity** for audit. Full RBAC (role→permission matrix, from which approvals derive) is **deferred**; reserve role/permission structures for no-migration enablement. | M | +| FR-X-01 | **[Phase 1: external IdP, user identity only]** Authentication is **delegated to the external AuthHex IdP** (ERPCore validates its RS256 tokens; no local login). ERPCore **provisions a local shadow user** (`auth_user_id` GUID → local `long`) and **stamps every transaction with the acting user's identity** for audit. AuthHex now also supplies `RoleCode`/`UserTypeCode` claims, used only for the door gate; full per-endpoint RBAC (role→permission matrix) remains **deferred** — reserve role/permission structures for no-migration enablement. | M | | FR-X-02 | Maintain an **immutable audit trail** for every create/update/delete and stock movement (who/when/old→new/reason). | M | | FR-X-03 | Generate **document numbers** from configurable sequences (per type, per year), unique and gap-controlled. | M | | FR-X-04 | Maintain configurable **reason-code** lists (adjustments, returns, count variances). | M | @@ -197,7 +200,7 @@ UI: responsive; count/pick screens handheld-friendly; status badges; mandatory-f |---|---|---| | NFR-01 | Performance | Single item/warehouse enquiry + valuation < 2s under normal load; ledger posting transactional, < 1s per line. | | NFR-02 | Integrity | FIFO layer consumption atomic and concurrency-safe; no double-consumption of remaining qty. | -| NFR-03 | Security | Users authenticated; passwords hashed; every action attributed to a user and logged. (Role-based enforcement deferred, FR-X-01.) | +| NFR-03 | Security | Users authenticated via the external AuthHex IdP; **password hashing (BCrypt) is AuthHex's responsibility** — ERPCore validates tokens only. Every action attributed to a user and logged. (Role-based enforcement deferred, FR-X-01.) | | NFR-04 | Auditability | Audit trail immutable, retained per policy; ledger append-only. | | NFR-05 | Reliability | No stock transaction partially commits; full rollback on failure. | | NFR-06 | Scalability | Growth in items/warehouses/ledger without redesign; ledger indexed for time-series queries. | @@ -239,6 +242,7 @@ Adjustment: Damage, Theft/Loss, Count Variance, Expiry Write-off, System Correct | 7 | PO amendments | **Resolved:** Option B, edit-while-open | | 8 | Costing method | **Resolved:** FIFO | | 9 | Tenancy | **Resolved:** single-tenant | +| 10 | Authentication | **Resolved:** external **AuthHex** IdP (RS256; ERPCore validates only), **shadow-user** provisioning (`auth_user_id` GUID → local `long`), door-gated by ERP `UserType`/`Role`; per-endpoint RBAC deferred. *Open sub-item:* exact ERP `UserTypeCode`/`RoleCode` + RSA-key rotation process. | --- @@ -318,7 +322,7 @@ STOCK_COUNT_LINE(count_line_id PK, count_id FK→STOCK_COUNT, item_id FK→ITEM, ## C.7 Cross-cutting ``` -USER(user_id PK, username, display_name, status) +USER(user_id PK, username, display_name, status, auth_user_id [GUID, unique] → AuthHex identity) -- local shadow/projection of AuthHex users; user_id (long) is what all FKs reference REASON_CODE(reason_code_id PK, code, description, context) NUMBER_SEQUENCE(sequence_id PK, doc_type, year, last_number) AUDIT_LOG(audit_id PK, user_id FK→USER, entity_type, entity_id, action, change_set, created_at) @@ -338,7 +342,8 @@ ROLE_PERMISSION(role_id FK→ROLE, permission_id FK→PERMISSION) - **Polymorphic source.** `STOCK_LEDGER.source_doc_type/source_doc_id` (and `AUDIT_LOG`, `JOURNAL_ENTRY_STUB`) reference the originating document without a hard FK per type — new transaction types (Sales, Manufacturing) write to the ledger without a schema change. - **In-transit + cost-preserving transfer.** `STOCK_TRANSFER` holds `src`/`dest` warehouse; dispatch consumes source layers into in-transit, receive creates the destination layer at the **inherited** source cost. - **FEFO ≠ FIFO.** FIFO governs *costing*; FEFO governs *physical picking* of perishables via `BATCH.expiry_date`. -- **Reserved RBAC.** Role/Permission/UserRole/RolePermission exist for schema-completeness only; only `USER` is live (audit stamp). +- **External IdP + shadow user.** Authentication is delegated to **AuthHex** (RS256, issuer `AuthHex`/audience `AuthHexClient`, static public key). `USER` is a **local shadow** of AuthHex identities: `auth_user_id` (GUID from the token's `UserId` claim) is JIT-mapped to the local `long` `user_id` that every `created_by`/`requested_by`/`AUDIT_LOG.user_id`/`STOCK_LEDGER.user_id` FK references — no FK type change. A door policy admits only ERP `UserType`/`Role` holders. +- **Reserved RBAC.** Role/Permission/UserRole/RolePermission exist for schema-completeness only; only `USER` is live (audit stamp). AuthHex's `RoleCode`/`UserTypeCode` claims drive the door gate today; per-endpoint RBAC is future work. - **Reorder alerts are a query**, not an entity — computed from `ITEM_REORDER` vs available. Add a table only if alert history is required. ## C.10 Entity → implementation mapping diff --git a/docs/11-BACKEND-PHASE1.md b/docs/11-BACKEND-PHASE1.md index 240ed7d..845f6d6 100644 --- a/docs/11-BACKEND-PHASE1.md +++ b/docs/11-BACKEND-PHASE1.md @@ -18,10 +18,9 @@ Path-based versioning. Breaking changes bump the major version. ``` Authorization: Bearer ``` -- Every endpoint requires a valid **Bearer JWT**; unauthenticated → `401`. -- **RBAC is NOT enforced in Phase 1** (FR-X-01): any authenticated user may call any endpoint. -- The token subject (`sub`) is the **audit actor** on every mutation. Clients never send `createdBy`; the server derives it. -- Tokens are issued by a simple in-app login (`/auth/login`, §2.0). +- Every endpoint requires a valid **Bearer JWT**; unauthenticated → `401`. Tokens are issued by the **external AuthHex IdP** (not ERPCore) — **RS256**, issuer `AuthHex`, audience `AuthHexClient`. ERPCore validates them against AuthHex's static RSA public key (no JWKS) and admits only holders of the configured ERP `UserType`/`Role` (door policy) → otherwise `403`. +- **Per-endpoint RBAC is NOT enforced in Phase 1** (FR-X-01): any ERP-admitted user may call any endpoint. +- The **audit actor** is AuthHex's custom **`UserId` (GUID)** claim, mapped to a local shadow user (`long`). Clients never send `createdBy`; the server derives it (docs/10 A.4). ### 1.3 Content type & encoding `application/json`, UTF-8, **camelCase**. Timestamps ISO 8601 UTC (`2026-07-07T09:30:00Z`); dates `YYYY-MM-DD`. Base currency **LKR** in Phase 1. @@ -60,16 +59,15 @@ Domain errors add a stable `code` (catalog §7): ## 2. Master Data -### 2.0 Auth -#### `POST /auth/login` +### 2.0 Auth — **external (AuthHex IdP); not an ERPCore endpoint** +> **Superseded (2026-07-14).** ERPCore no longer exposes `/auth/login`. Login, registration and recovery are owned by the +> separate **AuthHex** service (e.g. `POST /api/loginUser` with `{ identifier, password }`), which returns an **RS256** JWT +> (issuer `AuthHex`, audience `AuthHexClient`; claims `UserId` (GUID), `UserTypeCode`, `RoleCode`, `NIC`, …). ERPCore only +> **validates** that Bearer token and provisions a local shadow user (docs/10 A.4). The old shape is retained here for history: ```json -{ "username": "storekeeper01", "password": "••••••••" } +POST {AuthHex}/api/loginUser → { "identifier": "…", "password": "••••••••" } +// 200 → an RS256 access token (Bearer). Bad credentials → 401. Token → ERPCore Authorization: Bearer . ``` -**200 OK** -```json -{ "accessToken": "eyJhbGciOi...", "tokenType": "Bearer", "expiresInMinutes": 120, "userId": 17, "displayName": "Nimal Perera" } -``` -`401` on bad credentials. ### 2.1 Items #### `GET /items` From ed2ee87c68fb0d9dbb991fc4b522a1364709e239 Mon Sep 17 00:00:00 2001 From: ImanThiyanga Date: Wed, 15 Jul 2026 15:35:54 +0530 Subject: [PATCH 3/3] Refactor code structure for int in alll ids instesd defult using long --- .../Controllers/AuditLogsController.cs | 2 +- Backend/ERPCore/Controllers/GrnsController.cs | 12 +- .../ERPCore/Controllers/ItemsController.cs | 22 +- .../Controllers/JournalEntriesController.cs | 2 +- .../Controllers/PurchaseOrdersController.cs | 18 +- .../Controllers/RequisitionsController.cs | 8 +- Backend/ERPCore/Controllers/RfqsController.cs | 12 +- .../ERPCore/Controllers/StockController.cs | 12 +- .../Controllers/StockCountsController.cs | 12 +- .../Controllers/StockTransfersController.cs | 12 +- .../ERPCore/Controllers/VendorsController.cs | 12 +- .../Controllers/WarehousesController.cs | 12 +- Backend/ERPCore/Domain/Entities/AuditLog.cs | 6 +- Backend/ERPCore/Domain/Entities/Batch.cs | 4 +- Backend/ERPCore/Domain/Entities/Bin.cs | 4 +- Backend/ERPCore/Domain/Entities/Category.cs | 4 +- Backend/ERPCore/Domain/Entities/Grn.cs | 10 +- Backend/ERPCore/Domain/Entities/GrnLine.cs | 14 +- Backend/ERPCore/Domain/Entities/Item.cs | 8 +- .../ERPCore/Domain/Entities/ItemReorder.cs | 6 +- .../Domain/Entities/JournalEntryStub.cs | 4 +- .../ERPCore/Domain/Entities/NumberSequence.cs | 4 +- Backend/ERPCore/Domain/Entities/PoLine.cs | 10 +- .../ERPCore/Domain/Entities/PurchaseOrder.cs | 8 +- .../ERPCore/Domain/Entities/PurchaseReturn.cs | 10 +- .../Domain/Entities/PurchaseReturnLine.cs | 8 +- Backend/ERPCore/Domain/Entities/ReasonCode.cs | 2 +- .../ERPCore/Domain/Entities/Requisition.cs | 4 +- .../Domain/Entities/RequisitionLine.cs | 6 +- Backend/ERPCore/Domain/Entities/Rfq.cs | 4 +- Backend/ERPCore/Domain/Entities/RfqLine.cs | 6 +- Backend/ERPCore/Domain/Entities/Serial.cs | 4 +- .../Domain/Entities/StockAdjustment.cs | 8 +- .../Domain/Entities/StockAdjustmentLine.cs | 12 +- Backend/ERPCore/Domain/Entities/StockCount.cs | 6 +- .../ERPCore/Domain/Entities/StockCountLine.cs | 8 +- Backend/ERPCore/Domain/Entities/StockLayer.cs | 12 +- .../ERPCore/Domain/Entities/StockLedger.cs | 16 +- .../ERPCore/Domain/Entities/StockTransfer.cs | 8 +- .../Domain/Entities/StockTransferLine.cs | 14 +- Backend/ERPCore/Domain/Entities/Uom.cs | 2 +- .../ERPCore/Domain/Entities/UomConversion.cs | 8 +- Backend/ERPCore/Domain/Entities/User.cs | 6 +- Backend/ERPCore/Domain/Entities/Vendor.cs | 2 +- .../Domain/Entities/VendorQuotation.cs | 6 +- .../Domain/Entities/VendorQuotationLine.cs | 6 +- Backend/ERPCore/Domain/Entities/Warehouse.cs | 2 +- Backend/ERPCore/Dtos/Audit/AuditDtos.cs | 4 +- .../ERPCore/Dtos/Categories/CategoryDtos.cs | 6 +- Backend/ERPCore/Dtos/Grn/GrnDtos.cs | 30 +- Backend/ERPCore/Dtos/Items/ItemDtos.cs | 32 +- .../Dtos/Procurement/PurchaseOrderDtos.cs | 22 +- .../Dtos/Procurement/PurchaseReturnDtos.cs | 16 +- .../Dtos/Procurement/RequisitionDtos.cs | 8 +- Backend/ERPCore/Dtos/Procurement/RfqDtos.cs | 24 +- .../ERPCore/Dtos/Reference/ReasonCodeDtos.cs | 2 +- Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs | 16 +- Backend/ERPCore/Dtos/Stock/CountDtos.cs | 12 +- Backend/ERPCore/Dtos/Stock/ReorderDtos.cs | 2 +- Backend/ERPCore/Dtos/Stock/StockDtos.cs | 10 +- Backend/ERPCore/Dtos/Stock/TransferDtos.cs | 26 +- Backend/ERPCore/Dtos/Uoms/UomDtos.cs | 2 +- Backend/ERPCore/Dtos/Vendors/VendorDtos.cs | 2 +- .../ERPCore/Dtos/Warehouses/WarehouseDtos.cs | 4 +- Backend/ERPCore/Infra/Auth/CurrentUser.cs | 2 +- Backend/ERPCore/Infra/Auth/ICurrentUser.cs | 2 +- .../Auth/ShadowUserClaimsTransformation.cs | 4 +- .../Infra/Persistence/Auditing/AuditScribe.cs | 8 +- .../20260709095653_InitialCreate.Designer.cs | 445 ---- .../20260709095653_InitialCreate.cs | 325 --- .../20260709124415_initial.Designer.cs | 445 ---- .../Migrations/20260709124415_initial.cs | 22 - .../20260710090753_AddProcurement.Designer.cs | 987 -------- .../20260710090753_AddProcurement.cs | 448 ---- .../20260713054124_AddStockAndGrn.Designer.cs | 1480 ----------- .../20260713054124_AddStockAndGrn.cs | 434 ---- ...713061141_AddStockTransactions.Designer.cs | 1847 -------------- .../20260713061141_AddStockTransactions.cs | 337 --- ...0713105512_AddCountsAndReturns.Designer.cs | 2134 ---------------- .../20260713105512_AddCountsAndReturns.cs | 253 -- ...60713121825_AddAuditAndJournal.Designer.cs | 2222 ----------------- .../20260713121825_AddAuditAndJournal.cs | 87 - .../20260714103443_AddAuthUserId.cs | 46 - ... 20260715093552_InitialCreate.Designer.cs} | 552 ++-- .../20260715093552_InitialCreate.cs | 1791 +++++++++++++ .../Migrations/ErpDbContextModelSnapshot.cs | 548 ++-- Backend/ERPCore/Program.cs | 2 +- .../ERPCore/Properties/launchSettings.json | 6 +- Backend/ERPCore/Services/AuditService.cs | 4 +- Backend/ERPCore/Services/CategoryService.cs | 2 +- Backend/ERPCore/Services/CountService.cs | 8 +- Backend/ERPCore/Services/GrnService.cs | 20 +- .../Services/Interfaces/IAuditService.cs | 4 +- .../Services/Interfaces/ICountService.cs | 6 +- .../Interfaces/IFifoCostingService.cs | 14 +- .../Services/Interfaces/IGrnService.cs | 6 +- .../Services/Interfaces/IItemService.cs | 12 +- .../Interfaces/IPurchaseOrderService.cs | 10 +- .../Services/Interfaces/IReorderService.cs | 4 +- .../Interfaces/IRequisitionService.cs | 4 +- .../Services/Interfaces/IRfqService.cs | 6 +- .../Services/Interfaces/IStockMutator.cs | 4 +- .../Services/Interfaces/IStockService.cs | 6 +- .../Services/Interfaces/ITransferService.cs | 6 +- .../Services/Interfaces/IVendorService.cs | 6 +- .../Services/Interfaces/IWarehouseService.cs | 6 +- Backend/ERPCore/Services/ItemService.cs | 14 +- .../ERPCore/Services/PurchaseOrderService.cs | 14 +- Backend/ERPCore/Services/ReorderService.cs | 4 +- .../ERPCore/Services/RequisitionService.cs | 6 +- Backend/ERPCore/Services/RfqService.cs | 10 +- .../Services/Stock/FifoCostingService.cs | 12 +- .../ERPCore/Services/Stock/StockMutator.cs | 6 +- .../ERPCore/Services/Stock/StockService.cs | 6 +- Backend/ERPCore/Services/TransferService.cs | 12 +- Backend/ERPCore/Services/VendorService.cs | 6 +- Backend/ERPCore/Services/WarehouseService.cs | 8 +- Backend/PROGRESS.md | 6 +- docs/10-BACKEND-PHASE1.md | 12 +- docs/11-BACKEND-PHASE1.md | 2 +- 120 files changed, 2775 insertions(+), 12494 deletions(-) delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260709095653_InitialCreate.Designer.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260709095653_InitialCreate.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260709124415_initial.Designer.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260709124415_initial.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260710090753_AddProcurement.Designer.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260710090753_AddProcurement.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.Designer.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.Designer.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.Designer.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.Designer.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.cs delete mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.cs rename Backend/ERPCore/Infra/Persistence/Migrations/{20260714103443_AddAuthUserId.Designer.cs => 20260715093552_InitialCreate.Designer.cs} (83%) create mode 100644 Backend/ERPCore/Infra/Persistence/Migrations/20260715093552_InitialCreate.cs diff --git a/Backend/ERPCore/Controllers/AuditLogsController.cs b/Backend/ERPCore/Controllers/AuditLogsController.cs index a3f9750..6f66cf4 100644 --- a/Backend/ERPCore/Controllers/AuditLogsController.cs +++ b/Backend/ERPCore/Controllers/AuditLogsController.cs @@ -20,7 +20,7 @@ public sealed class AuditLogsController : ApiControllerBase [HttpGet] [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] public async Task>> List( - [FromQuery] string? entityType, [FromQuery] long? entityId, [FromQuery] long? userId, + [FromQuery] string? entityType, [FromQuery] int? entityId, [FromQuery] int? userId, [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, [FromQuery] PageQuery query, CancellationToken ct) => Ok(await _audit.ListLogsAsync(entityType, entityId, userId, from, to, query, ct)); } diff --git a/Backend/ERPCore/Controllers/GrnsController.cs b/Backend/ERPCore/Controllers/GrnsController.cs index 0903a5a..20ba722 100644 --- a/Backend/ERPCore/Controllers/GrnsController.cs +++ b/Backend/ERPCore/Controllers/GrnsController.cs @@ -12,10 +12,10 @@ public sealed class GrnsController : ApiControllerBase public GrnsController(IGrnService grns) => _grns = grns; - [HttpGet("{grnId:long}")] + [HttpGet("{grnId:int}")] [ProducesResponseType(typeof(GrnDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long grnId, CancellationToken ct) + public async Task> GetById(int grnId, CancellationToken ct) { var dto = await _grns.GetAsync(grnId, ct); return dto is null ? NotFound() : Ok(dto); @@ -33,20 +33,20 @@ public sealed class GrnsController : ApiControllerBase } /// Confirm: create FIFO layers + inbound ledger + update PO receipts (single UoW txn). - [HttpPost("{grnId:long}/confirm")] + [HttpPost("{grnId:int}/confirm")] [ProducesResponseType(typeof(GrnConfirmResultDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Confirm( - long grnId, [FromHeader(Name = "Idempotency-Key")] string? idempotencyKey, CancellationToken ct) + int grnId, [FromHeader(Name = "Idempotency-Key")] string? idempotencyKey, CancellationToken ct) => Ok(await _grns.ConfirmAsync(grnId, idempotencyKey, ct)); /// Release or reject an inspection-hold line (FR-GRN-05). - [HttpPost("{grnId:long}/lines/{grnLineId:long}/release")] + [HttpPost("{grnId:int}/lines/{grnLineId:int}/release")] [ProducesResponseType(typeof(ReleaseLineResultDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Release( - long grnId, long grnLineId, [FromBody] ReleaseLineRequest request, CancellationToken ct) + int grnId, int grnLineId, [FromBody] ReleaseLineRequest request, CancellationToken ct) => Ok(await _grns.ReleaseLineAsync(grnId, grnLineId, request.Action, ct)); } diff --git a/Backend/ERPCore/Controllers/ItemsController.cs b/Backend/ERPCore/Controllers/ItemsController.cs index eab6c79..c98493a 100644 --- a/Backend/ERPCore/Controllers/ItemsController.cs +++ b/Backend/ERPCore/Controllers/ItemsController.cs @@ -20,16 +20,16 @@ public sealed class ItemsController : ApiControllerBase public async Task>> List( [FromQuery] PageQuery query, [FromQuery] EntityStatus? status, - [FromQuery] long? categoryId, + [FromQuery] int? categoryId, [FromQuery] TrackingMode? trackingMode, CancellationToken ct) => Ok(await _items.ListAsync(query, status, categoryId, trackingMode, ct)); /// Get a single item; returns an ETag for optimistic concurrency. - [HttpGet("{itemId:long}")] + [HttpGet("{itemId:int}")] [ProducesResponseType(typeof(ItemDetailDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long itemId, CancellationToken ct) + public async Task> GetById(int itemId, CancellationToken ct) { var result = await _items.GetAsync(itemId, ct); if (result is null) return NotFound(); @@ -51,11 +51,11 @@ public sealed class ItemsController : ApiControllerBase } /// Full update; requires If-Match (412 on stale ETag). - [HttpPut("{itemId:long}")] + [HttpPut("{itemId:int}")] [ProducesResponseType(typeof(ItemDetailDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] - public async Task> Update(long itemId, [FromBody] UpdateItemRequest request, CancellationToken ct) + public async Task> Update(int itemId, [FromBody] UpdateItemRequest request, CancellationToken ct) { var expected = RequireIfMatch(); var result = await _items.UpdateAsync(itemId, request, expected, ct); @@ -64,26 +64,26 @@ public sealed class ItemsController : ApiControllerBase } /// Activate / deactivate the item (FR-MD-08 — deactivate, not delete). - [HttpPatch("{itemId:long}/status")] + [HttpPatch("{itemId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task SetStatus(long itemId, [FromBody] UpdateItemStatusRequest request, CancellationToken ct) + public async Task SetStatus(int itemId, [FromBody] UpdateItemStatusRequest request, CancellationToken ct) { await _items.SetStatusAsync(itemId, request.Status, ct); return NoContent(); } /// Replace the item's per-warehouse reorder settings (FR-MD-05). - [HttpPut("{itemId:long}/reorder")] + [HttpPut("{itemId:int}/reorder")] [ProducesResponseType(typeof(ItemReorderSettingsDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> UpdateReorder(long itemId, [FromBody] UpdateReorderRequest request, CancellationToken ct) + public async Task> UpdateReorder(int itemId, [FromBody] UpdateReorderRequest request, CancellationToken ct) => Ok(await _items.UpdateReorderAsync(itemId, request, ct)); /// Replace the item's UOM conversions (FR-MD-02). - [HttpPut("{itemId:long}/uom-conversions")] + [HttpPut("{itemId:int}/uom-conversions")] [ProducesResponseType(typeof(ItemUomConversionsDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> UpdateUomConversions(long itemId, [FromBody] UpdateUomConversionsRequest request, CancellationToken ct) + public async Task> UpdateUomConversions(int itemId, [FromBody] UpdateUomConversionsRequest request, CancellationToken ct) => Ok(await _items.UpdateUomConversionsAsync(itemId, request, ct)); } diff --git a/Backend/ERPCore/Controllers/JournalEntriesController.cs b/Backend/ERPCore/Controllers/JournalEntriesController.cs index a2d500d..54789c9 100644 --- a/Backend/ERPCore/Controllers/JournalEntriesController.cs +++ b/Backend/ERPCore/Controllers/JournalEntriesController.cs @@ -19,6 +19,6 @@ public sealed class JournalEntriesController : ApiControllerBase [HttpGet] [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] public async Task>> List( - [FromQuery] string? sourceDocType, [FromQuery] long? sourceDocId, [FromQuery] PageQuery query, CancellationToken ct) + [FromQuery] string? sourceDocType, [FromQuery] int? sourceDocId, [FromQuery] PageQuery query, CancellationToken ct) => Ok(await _audit.ListJournalAsync(sourceDocType, sourceDocId, query, ct)); } diff --git a/Backend/ERPCore/Controllers/PurchaseOrdersController.cs b/Backend/ERPCore/Controllers/PurchaseOrdersController.cs index 03c9524..a1bf9b9 100644 --- a/Backend/ERPCore/Controllers/PurchaseOrdersController.cs +++ b/Backend/ERPCore/Controllers/PurchaseOrdersController.cs @@ -17,13 +17,13 @@ public sealed class PurchaseOrdersController : ApiControllerBase [HttpGet] [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] public async Task>> List( - [FromQuery] PageQuery query, [FromQuery] PurchaseOrderStatus? status, [FromQuery] long? vendorId, CancellationToken ct) + [FromQuery] PageQuery query, [FromQuery] PurchaseOrderStatus? status, [FromQuery] int? vendorId, CancellationToken ct) => Ok(await _pos.ListAsync(query, status, vendorId, ct)); - [HttpGet("{poId:long}")] + [HttpGet("{poId:int}")] [ProducesResponseType(typeof(PurchaseOrderDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long poId, CancellationToken ct) + public async Task> GetById(int poId, CancellationToken ct) { var result = await _pos.GetAsync(poId, ct); if (result is null) return NotFound(); @@ -44,12 +44,12 @@ public sealed class PurchaseOrdersController : ApiControllerBase } /// Edit while open (FR-PROC-05); requires If-Match. 409 PO_NOT_EDITABLE if closed. - [HttpPut("{poId:long}")] + [HttpPut("{poId:int}")] [ProducesResponseType(typeof(PurchaseOrderDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] - public async Task> Update(long poId, [FromBody] UpdatePurchaseOrderRequest request, CancellationToken ct) + public async Task> Update(int poId, [FromBody] UpdatePurchaseOrderRequest request, CancellationToken ct) { var expected = RequireIfMatch(); var result = await _pos.UpdateAsync(poId, request, expected, ct); @@ -58,17 +58,17 @@ public sealed class PurchaseOrdersController : ApiControllerBase } /// Approve — no-op in Phase 1 (POs auto-approve); transitions PendingApproval→Approved when enabled. - [HttpPost("{poId:long}/approve")] + [HttpPost("{poId:int}/approve")] [ProducesResponseType(typeof(PurchaseOrderDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> Approve(long poId, CancellationToken ct) + public async Task> Approve(int poId, CancellationToken ct) => Ok(await _pos.ApproveAsync(poId, ct)); /// Cancel — 409 if any goods have been received against the PO. - [HttpPost("{poId:long}/cancel")] + [HttpPost("{poId:int}/cancel")] [ProducesResponseType(typeof(PurchaseOrderDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task> Cancel(long poId, [FromBody] CancelPurchaseOrderRequest request, CancellationToken ct) + public async Task> Cancel(int poId, [FromBody] CancelPurchaseOrderRequest request, CancellationToken ct) => Ok(await _pos.CancelAsync(poId, request.Reason, ct)); } diff --git a/Backend/ERPCore/Controllers/RequisitionsController.cs b/Backend/ERPCore/Controllers/RequisitionsController.cs index 10d4c08..ab849b9 100644 --- a/Backend/ERPCore/Controllers/RequisitionsController.cs +++ b/Backend/ERPCore/Controllers/RequisitionsController.cs @@ -18,10 +18,10 @@ public sealed class RequisitionsController : ApiControllerBase public async Task>> List([FromQuery] PageQuery query, CancellationToken ct) => Ok(await _requisitions.ListAsync(query, ct)); - [HttpGet("{requisitionId:long}")] + [HttpGet("{requisitionId:int}")] [ProducesResponseType(typeof(RequisitionDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long requisitionId, CancellationToken ct) + public async Task> GetById(int requisitionId, CancellationToken ct) { var dto = await _requisitions.GetAsync(requisitionId, ct); return dto is null ? NotFound() : Ok(dto); @@ -36,9 +36,9 @@ public sealed class RequisitionsController : ApiControllerBase return Created($"/api/v1/requisitions/{dto.RequisitionId}", dto); } - [HttpPost("{requisitionId:long}/submit")] + [HttpPost("{requisitionId:int}/submit")] [ProducesResponseType(typeof(RequisitionDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> Submit(long requisitionId, CancellationToken ct) + public async Task> Submit(int requisitionId, CancellationToken ct) => Ok(await _requisitions.SubmitAsync(requisitionId, ct)); } diff --git a/Backend/ERPCore/Controllers/RfqsController.cs b/Backend/ERPCore/Controllers/RfqsController.cs index 736ddbc..7cc0e1f 100644 --- a/Backend/ERPCore/Controllers/RfqsController.cs +++ b/Backend/ERPCore/Controllers/RfqsController.cs @@ -12,10 +12,10 @@ public sealed class RfqsController : ApiControllerBase public RfqsController(IRfqService rfqs) => _rfqs = rfqs; - [HttpGet("{rfqId:long}")] + [HttpGet("{rfqId:int}")] [ProducesResponseType(typeof(RfqDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long rfqId, CancellationToken ct) + public async Task> GetById(int rfqId, CancellationToken ct) { var dto = await _rfqs.GetAsync(rfqId, ct); return dto is null ? NotFound() : Ok(dto); @@ -30,20 +30,20 @@ public sealed class RfqsController : ApiControllerBase return Created($"/api/v1/rfqs/{dto.RfqId}", dto); } - [HttpPost("{rfqId:long}/quotations")] + [HttpPost("{rfqId:int}/quotations")] [ProducesResponseType(typeof(VendorQuotationDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] - public async Task> AddQuotation(long rfqId, [FromBody] CreateQuotationRequest request, CancellationToken ct) + public async Task> AddQuotation(int rfqId, [FromBody] CreateQuotationRequest request, CancellationToken ct) { var dto = await _rfqs.AddQuotationAsync(rfqId, request, ct); return Created($"/api/v1/rfqs/{rfqId}/quotations/{dto.QuotationId}", dto); } - [HttpGet("{rfqId:long}/comparison")] + [HttpGet("{rfqId:int}/comparison")] [ProducesResponseType(typeof(RfqComparisonDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> Comparison(long rfqId, CancellationToken ct) + public async Task> Comparison(int rfqId, CancellationToken ct) => Ok(await _rfqs.GetComparisonAsync(rfqId, ct)); } diff --git a/Backend/ERPCore/Controllers/StockController.cs b/Backend/ERPCore/Controllers/StockController.cs index 3124bd3..652942a 100644 --- a/Backend/ERPCore/Controllers/StockController.cs +++ b/Backend/ERPCore/Controllers/StockController.cs @@ -21,34 +21,34 @@ public sealed class StockController : ApiControllerBase [HttpGet("on-hand")] [ProducesResponseType(typeof(StockOnHandDto), StatusCodes.Status200OK)] - public async Task> OnHand([FromQuery] long itemId, [FromQuery] long warehouseId, CancellationToken ct) + public async Task> OnHand([FromQuery] int itemId, [FromQuery] int warehouseId, CancellationToken ct) => Ok(await _stock.GetOnHandAsync(itemId, warehouseId, ct)); [HttpGet("ledger")] [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] public async Task>> Ledger( - [FromQuery] long? itemId, [FromQuery] long? warehouseId, + [FromQuery] int? itemId, [FromQuery] int? warehouseId, [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, [FromQuery] PageQuery query, CancellationToken ct) => Ok(await _stock.GetLedgerAsync(itemId, warehouseId, from, to, query, ct)); [HttpGet("valuation")] [ProducesResponseType(typeof(StockValuationDto), StatusCodes.Status200OK)] - public async Task> Valuation([FromQuery] long itemId, [FromQuery] long warehouseId, CancellationToken ct) + public async Task> Valuation([FromQuery] int itemId, [FromQuery] int warehouseId, CancellationToken ct) => Ok(await _stock.GetValuationAsync(itemId, warehouseId, ct)); /// Items at/below their reorder point (FR-STK-10), computed on read. [HttpGet("reorder-alerts")] [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] public async Task>> ReorderAlerts( - [FromQuery] long? warehouseId, [FromQuery] PageQuery query, CancellationToken ct) + [FromQuery] int? warehouseId, [FromQuery] PageQuery query, CancellationToken ct) => Ok(await _reorder.GetAlertsAsync(warehouseId, query, ct)); /// Create a draft requisition for an item's suggested reorder quantity. - [HttpPost("reorder-alerts/{itemId:long}/requisition")] + [HttpPost("reorder-alerts/{itemId:int}/requisition")] [ProducesResponseType(typeof(RequisitionDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] public async Task> SuggestRequisition( - long itemId, [FromQuery] long warehouseId, CancellationToken ct) + int itemId, [FromQuery] int warehouseId, CancellationToken ct) { var dto = await _reorder.CreateSuggestedRequisitionAsync(itemId, warehouseId, ct); return Created($"/api/v1/requisitions/{dto.RequisitionId}", dto); diff --git a/Backend/ERPCore/Controllers/StockCountsController.cs b/Backend/ERPCore/Controllers/StockCountsController.cs index e1c7bed..008ca79 100644 --- a/Backend/ERPCore/Controllers/StockCountsController.cs +++ b/Backend/ERPCore/Controllers/StockCountsController.cs @@ -12,10 +12,10 @@ public sealed class StockCountsController : ApiControllerBase public StockCountsController(ICountService counts) => _counts = counts; - [HttpGet("{countId:long}")] + [HttpGet("{countId:int}")] [ProducesResponseType(typeof(CountDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long countId, CancellationToken ct) + public async Task> GetById(int countId, CancellationToken ct) { var dto = await _counts.GetAsync(countId, ct); return dto is null ? NotFound() : Ok(dto); @@ -32,18 +32,18 @@ public sealed class StockCountsController : ApiControllerBase } /// Enter counted quantities; variance = counted − system. - [HttpPut("{countId:long}/counts")] + [HttpPut("{countId:int}/counts")] [ProducesResponseType(typeof(CountDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task> EnterCounts(long countId, [FromBody] EnterCountsRequest request, CancellationToken ct) + public async Task> EnterCounts(int countId, [FromBody] EnterCountsRequest request, CancellationToken ct) => Ok(await _counts.EnterCountsAsync(countId, request, ct)); /// Post: emit a variance adjustment and close the count. - [HttpPost("{countId:long}/post")] + [HttpPost("{countId:int}/post")] [ProducesResponseType(typeof(CountPostResultDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task> Post(long countId, CancellationToken ct) + public async Task> Post(int countId, CancellationToken ct) => Ok(await _counts.PostAsync(countId, ct)); } diff --git a/Backend/ERPCore/Controllers/StockTransfersController.cs b/Backend/ERPCore/Controllers/StockTransfersController.cs index 9d70733..fd5f52b 100644 --- a/Backend/ERPCore/Controllers/StockTransfersController.cs +++ b/Backend/ERPCore/Controllers/StockTransfersController.cs @@ -12,10 +12,10 @@ public sealed class StockTransfersController : ApiControllerBase public StockTransfersController(ITransferService transfers) => _transfers = transfers; - [HttpGet("{transferId:long}")] + [HttpGet("{transferId:int}")] [ProducesResponseType(typeof(TransferDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long transferId, CancellationToken ct) + public async Task> GetById(int transferId, CancellationToken ct) { var dto = await _transfers.GetAsync(transferId, ct); return dto is null ? NotFound() : Ok(dto); @@ -31,19 +31,19 @@ public sealed class StockTransfersController : ApiControllerBase } /// Dispatch: consume source FIFO layers into in-transit. 409 STOCK_NEGATIVE_BLOCKED if short. - [HttpPost("{transferId:long}/dispatch")] + [HttpPost("{transferId:int}/dispatch")] [ProducesResponseType(typeof(DispatchResultDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task> Dispatch(long transferId, CancellationToken ct) + public async Task> Dispatch(int transferId, CancellationToken ct) => Ok(await _transfers.DispatchAsync(transferId, ct)); /// Receive: create the destination layer at the inherited cost (cost-preserving). - [HttpPost("{transferId:long}/receive")] + [HttpPost("{transferId:int}/receive")] [ProducesResponseType(typeof(ReceiveResultDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] - public async Task> Receive(long transferId, [FromBody] ReceiveTransferRequest request, CancellationToken ct) + public async Task> Receive(int transferId, [FromBody] ReceiveTransferRequest request, CancellationToken ct) => Ok(await _transfers.ReceiveAsync(transferId, request, ct)); } diff --git a/Backend/ERPCore/Controllers/VendorsController.cs b/Backend/ERPCore/Controllers/VendorsController.cs index 40b17b3..ee1021d 100644 --- a/Backend/ERPCore/Controllers/VendorsController.cs +++ b/Backend/ERPCore/Controllers/VendorsController.cs @@ -20,10 +20,10 @@ public sealed class VendorsController : ApiControllerBase [FromQuery] PageQuery query, [FromQuery] EntityStatus? status, CancellationToken ct) => Ok(await _vendors.ListAsync(query, status, ct)); - [HttpGet("{vendorId:long}")] + [HttpGet("{vendorId:int}")] [ProducesResponseType(typeof(VendorDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long vendorId, CancellationToken ct) + public async Task> GetById(int vendorId, CancellationToken ct) { var result = await _vendors.GetAsync(vendorId, ct); if (result is null) return NotFound(); @@ -42,11 +42,11 @@ public sealed class VendorsController : ApiControllerBase return Created($"/api/v1/vendors/{result.Value.VendorId}", result.Value); } - [HttpPut("{vendorId:long}")] + [HttpPut("{vendorId:int}")] [ProducesResponseType(typeof(VendorDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] - public async Task> Update(long vendorId, [FromBody] UpdateVendorRequest request, CancellationToken ct) + public async Task> Update(int vendorId, [FromBody] UpdateVendorRequest request, CancellationToken ct) { var expected = RequireIfMatch(); var result = await _vendors.UpdateAsync(vendorId, request, expected, ct); @@ -54,10 +54,10 @@ public sealed class VendorsController : ApiControllerBase return Ok(result.Value); } - [HttpPatch("{vendorId:long}/status")] + [HttpPatch("{vendorId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task SetStatus(long vendorId, [FromBody] UpdateVendorStatusRequest request, CancellationToken ct) + public async Task SetStatus(int vendorId, [FromBody] UpdateVendorStatusRequest request, CancellationToken ct) { await _vendors.SetStatusAsync(vendorId, request.Status, ct); return NoContent(); diff --git a/Backend/ERPCore/Controllers/WarehousesController.cs b/Backend/ERPCore/Controllers/WarehousesController.cs index 4c2eb5c..ff3304b 100644 --- a/Backend/ERPCore/Controllers/WarehousesController.cs +++ b/Backend/ERPCore/Controllers/WarehousesController.cs @@ -18,10 +18,10 @@ public sealed class WarehousesController : ApiControllerBase public async Task>> List([FromQuery] PageQuery query, CancellationToken ct) => Ok(await _warehouses.ListAsync(query, ct)); - [HttpGet("{warehouseId:long}")] + [HttpGet("{warehouseId:int}")] [ProducesResponseType(typeof(WarehouseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetById(long warehouseId, CancellationToken ct) + public async Task> GetById(int warehouseId, CancellationToken ct) { var dto = await _warehouses.GetAsync(warehouseId, ct); return dto is null ? NotFound() : Ok(dto); @@ -36,17 +36,17 @@ public sealed class WarehousesController : ApiControllerBase return Created($"/api/v1/warehouses/{dto.WarehouseId}", dto); } - [HttpGet("{warehouseId:long}/bins")] + [HttpGet("{warehouseId:int}/bins")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> ListBins(long warehouseId, CancellationToken ct) + public async Task>> ListBins(int warehouseId, CancellationToken ct) => Ok(await _warehouses.ListBinsAsync(warehouseId, ct)); - [HttpPost("{warehouseId:long}/bins")] + [HttpPost("{warehouseId:int}/bins")] [ProducesResponseType(typeof(BinDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task> CreateBin(long warehouseId, [FromBody] CreateBinRequest request, CancellationToken ct) + public async Task> CreateBin(int warehouseId, [FromBody] CreateBinRequest request, CancellationToken ct) { var dto = await _warehouses.CreateBinAsync(warehouseId, request, ct); return Created($"/api/v1/warehouses/{warehouseId}/bins/{dto.BinId}", dto); diff --git a/Backend/ERPCore/Domain/Entities/AuditLog.cs b/Backend/ERPCore/Domain/Entities/AuditLog.cs index 833a68c..c139a8c 100644 --- a/Backend/ERPCore/Domain/Entities/AuditLog.cs +++ b/Backend/ERPCore/Domain/Entities/AuditLog.cs @@ -12,10 +12,10 @@ namespace ERPCore.Domain.Entities; /// public class AuditLog { - public long AuditId { get; set; } - public long UserId { get; set; } + public int AuditId { get; set; } + public int UserId { get; set; } public string EntityType { get; set; } = string.Empty; - public long EntityId { get; set; } + public int EntityId { get; set; } public AuditAction Action { get; set; } /// JSON change set: field→value (create/delete) or field→{old,new} (update). public string ChangeSet { get; set; } = "{}"; diff --git a/Backend/ERPCore/Domain/Entities/Batch.cs b/Backend/ERPCore/Domain/Entities/Batch.cs index cc51ae4..03ba570 100644 --- a/Backend/ERPCore/Domain/Entities/Batch.cs +++ b/Backend/ERPCore/Domain/Entities/Batch.cs @@ -6,9 +6,9 @@ namespace ERPCore.Domain.Entities; /// public class Batch { - public long BatchId { get; set; } + public int BatchId { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } public string BatchNo { get; set; } = string.Empty; diff --git a/Backend/ERPCore/Domain/Entities/Bin.cs b/Backend/ERPCore/Domain/Entities/Bin.cs index 331fce1..1d252e3 100644 --- a/Backend/ERPCore/Domain/Entities/Bin.cs +++ b/Backend/ERPCore/Domain/Entities/Bin.cs @@ -6,9 +6,9 @@ namespace ERPCore.Domain.Entities; /// public class Bin { - public long BinId { get; set; } + public int BinId { get; set; } - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public Warehouse? Warehouse { get; set; } public string Code { get; set; } = string.Empty; diff --git a/Backend/ERPCore/Domain/Entities/Category.cs b/Backend/ERPCore/Domain/Entities/Category.cs index 5285373..6c34b87 100644 --- a/Backend/ERPCore/Domain/Entities/Category.cs +++ b/Backend/ERPCore/Domain/Entities/Category.cs @@ -6,10 +6,10 @@ namespace ERPCore.Domain.Entities; /// public class Category { - public long CategoryId { get; set; } + public int CategoryId { get; set; } public string Name { get; set; } = string.Empty; - public long? ParentId { get; set; } + public int? ParentId { get; set; } public Category? Parent { get; set; } public ICollection Children { get; set; } = new List(); } diff --git a/Backend/ERPCore/Domain/Entities/Grn.cs b/Backend/ERPCore/Domain/Entities/Grn.cs index 9fc2516..5642f1b 100644 --- a/Backend/ERPCore/Domain/Entities/Grn.cs +++ b/Backend/ERPCore/Domain/Entities/Grn.cs @@ -10,21 +10,21 @@ namespace ERPCore.Domain.Entities; /// public class Grn { - public long GrnId { get; set; } + public int GrnId { get; set; } public string DocNo { get; set; } = string.Empty; - public long? PoId { get; set; } + public int? PoId { get; set; } public PurchaseOrder? PurchaseOrder { get; set; } - public long VendorId { get; set; } + public int VendorId { get; set; } public Vendor? Vendor { get; set; } - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public Warehouse? Warehouse { get; set; } public GrnStatus Status { get; set; } = GrnStatus.Draft; - public long CreatedBy { get; set; } + public int CreatedBy { get; set; } public User? Creator { get; set; } public DateTime CreatedAt { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/GrnLine.cs b/Backend/ERPCore/Domain/Entities/GrnLine.cs index 28dce48..5805127 100644 --- a/Backend/ERPCore/Domain/Entities/GrnLine.cs +++ b/Backend/ERPCore/Domain/Entities/GrnLine.cs @@ -10,24 +10,24 @@ namespace ERPCore.Domain.Entities; /// public class GrnLine { - public long GrnLineId { get; set; } + public int GrnLineId { get; set; } - public long GrnId { get; set; } + public int GrnId { get; set; } public Grn? Grn { get; set; } - public long? PoLineId { get; set; } + public int? PoLineId { get; set; } public PoLine? PoLine { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } - public long UomId { get; set; } + public int UomId { get; set; } public Uom? Uom { get; set; } - public long? BinId { get; set; } + public int? BinId { get; set; } public Bin? Bin { get; set; } - public long? BatchId { get; set; } + public int? BatchId { get; set; } public Batch? Batch { get; set; } public decimal Qty { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/Item.cs b/Backend/ERPCore/Domain/Entities/Item.cs index 4083f57..f308dd3 100644 --- a/Backend/ERPCore/Domain/Entities/Item.cs +++ b/Backend/ERPCore/Domain/Entities/Item.cs @@ -9,18 +9,18 @@ namespace ERPCore.Domain.Entities; /// public class Item { - public long ItemId { get; set; } + public int ItemId { get; set; } public string Sku { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string? Description { get; set; } - public long CategoryId { get; set; } + public int CategoryId { get; set; } public Category? Category { get; set; } - public long BaseUomId { get; set; } + public int BaseUomId { get; set; } public Uom? BaseUom { get; set; } - public long? DefaultVendorId { get; set; } + public int? DefaultVendorId { get; set; } public Vendor? DefaultVendor { get; set; } public ItemType ItemType { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/ItemReorder.cs b/Backend/ERPCore/Domain/Entities/ItemReorder.cs index 8a3ecfe..65350c1 100644 --- a/Backend/ERPCore/Domain/Entities/ItemReorder.cs +++ b/Backend/ERPCore/Domain/Entities/ItemReorder.cs @@ -7,12 +7,12 @@ namespace ERPCore.Domain.Entities; /// public class ItemReorder { - public long ReorderId { get; set; } + public int ReorderId { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public Warehouse? Warehouse { get; set; } public decimal ReorderPoint { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/JournalEntryStub.cs b/Backend/ERPCore/Domain/Entities/JournalEntryStub.cs index 7f458c7..b9dc74f 100644 --- a/Backend/ERPCore/Domain/Entities/JournalEntryStub.cs +++ b/Backend/ERPCore/Domain/Entities/JournalEntryStub.cs @@ -9,9 +9,9 @@ namespace ERPCore.Domain.Entities; /// public class JournalEntryStub { - public long JournalId { get; set; } + public int JournalId { get; set; } public string SourceDocType { get; set; } = string.Empty; - public long SourceDocId { get; set; } + public int SourceDocId { get; set; } public string DebitAccount { get; set; } = string.Empty; public string CreditAccount { get; set; } = string.Empty; public decimal Amount { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/NumberSequence.cs b/Backend/ERPCore/Domain/Entities/NumberSequence.cs index 6db8de6..c4f7ffc 100644 --- a/Backend/ERPCore/Domain/Entities/NumberSequence.cs +++ b/Backend/ERPCore/Domain/Entities/NumberSequence.cs @@ -8,8 +8,8 @@ namespace ERPCore.Domain.Entities; /// public class NumberSequence { - public long SequenceId { get; set; } + public int SequenceId { get; set; } public string DocType { get; set; } = string.Empty; public int Year { get; set; } - public long LastNumber { get; set; } + public int LastNumber { get; set; } } diff --git a/Backend/ERPCore/Domain/Entities/PoLine.cs b/Backend/ERPCore/Domain/Entities/PoLine.cs index b564499..1f38169 100644 --- a/Backend/ERPCore/Domain/Entities/PoLine.cs +++ b/Backend/ERPCore/Domain/Entities/PoLine.cs @@ -7,18 +7,18 @@ namespace ERPCore.Domain.Entities; /// public class PoLine { - public long PoLineId { get; set; } + public int PoLineId { get; set; } - public long PoId { get; set; } + public int PoId { get; set; } public PurchaseOrder? PurchaseOrder { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } - public long UomId { get; set; } + public int UomId { get; set; } public Uom? Uom { get; set; } - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public Warehouse? Warehouse { get; set; } public decimal Qty { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/PurchaseOrder.cs b/Backend/ERPCore/Domain/Entities/PurchaseOrder.cs index 257f67d..a94c0a1 100644 --- a/Backend/ERPCore/Domain/Entities/PurchaseOrder.cs +++ b/Backend/ERPCore/Domain/Entities/PurchaseOrder.cs @@ -11,19 +11,19 @@ namespace ERPCore.Domain.Entities; /// public class PurchaseOrder { - public long PoId { get; set; } + public int PoId { get; set; } public string DocNo { get; set; } = string.Empty; - public long VendorId { get; set; } + public int VendorId { get; set; } public Vendor? Vendor { get; set; } - public long? RequisitionId { get; set; } + public int? RequisitionId { get; set; } public Requisition? Requisition { get; set; } public PurchaseOrderStatus Status { get; set; } = PurchaseOrderStatus.Draft; public bool ApprovalRequired { get; set; } - public long CreatedBy { get; set; } + public int CreatedBy { get; set; } public User? Creator { get; set; } public DateTime CreatedAt { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/PurchaseReturn.cs b/Backend/ERPCore/Domain/Entities/PurchaseReturn.cs index 1acd501..a663950 100644 --- a/Backend/ERPCore/Domain/Entities/PurchaseReturn.cs +++ b/Backend/ERPCore/Domain/Entities/PurchaseReturn.cs @@ -9,21 +9,21 @@ namespace ERPCore.Domain.Entities; /// public class PurchaseReturn { - public long ReturnId { get; set; } + public int ReturnId { get; set; } public string DocNo { get; set; } = string.Empty; - public long VendorId { get; set; } + public int VendorId { get; set; } public Vendor? Vendor { get; set; } - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public Warehouse? Warehouse { get; set; } - public long ReasonCodeId { get; set; } + public int ReasonCodeId { get; set; } public ReasonCode? ReasonCode { get; set; } public ReturnStatus Status { get; set; } = ReturnStatus.Posted; - public long CreatedBy { get; set; } + public int CreatedBy { get; set; } public User? Creator { get; set; } public DateTime CreatedAt { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/PurchaseReturnLine.cs b/Backend/ERPCore/Domain/Entities/PurchaseReturnLine.cs index f42c0e0..9fa5f98 100644 --- a/Backend/ERPCore/Domain/Entities/PurchaseReturnLine.cs +++ b/Backend/ERPCore/Domain/Entities/PurchaseReturnLine.cs @@ -6,15 +6,15 @@ namespace ERPCore.Domain.Entities; /// public class PurchaseReturnLine { - public long ReturnLineId { get; set; } + public int ReturnLineId { get; set; } - public long ReturnId { get; set; } + public int ReturnId { get; set; } public PurchaseReturn? Return { get; set; } - public long? GrnLineId { get; set; } + public int? GrnLineId { get; set; } public GrnLine? GrnLine { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } public decimal Qty { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/ReasonCode.cs b/Backend/ERPCore/Domain/Entities/ReasonCode.cs index 662bb49..7bb34e0 100644 --- a/Backend/ERPCore/Domain/Entities/ReasonCode.cs +++ b/Backend/ERPCore/Domain/Entities/ReasonCode.cs @@ -8,7 +8,7 @@ namespace ERPCore.Domain.Entities; /// public class ReasonCode { - public long ReasonCodeId { get; set; } + public int ReasonCodeId { get; set; } public string Code { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public ReasonContext Context { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/Requisition.cs b/Backend/ERPCore/Domain/Entities/Requisition.cs index e6cfda6..29103ca 100644 --- a/Backend/ERPCore/Domain/Entities/Requisition.cs +++ b/Backend/ERPCore/Domain/Entities/Requisition.cs @@ -8,10 +8,10 @@ namespace ERPCore.Domain.Entities; /// public class Requisition { - public long RequisitionId { get; set; } + public int RequisitionId { get; set; } public string DocNo { get; set; } = string.Empty; - public long RequestedBy { get; set; } + public int RequestedBy { get; set; } public User? Requester { get; set; } public RequisitionStatus Status { get; set; } = RequisitionStatus.Draft; diff --git a/Backend/ERPCore/Domain/Entities/RequisitionLine.cs b/Backend/ERPCore/Domain/Entities/RequisitionLine.cs index 13afc08..ff1bd80 100644 --- a/Backend/ERPCore/Domain/Entities/RequisitionLine.cs +++ b/Backend/ERPCore/Domain/Entities/RequisitionLine.cs @@ -3,12 +3,12 @@ namespace ERPCore.Domain.Entities; /// Requisition line (FR-PROC-01). Model: docs/10 Part C.2. public class RequisitionLine { - public long ReqLineId { get; set; } + public int ReqLineId { get; set; } - public long RequisitionId { get; set; } + public int RequisitionId { get; set; } public Requisition? Requisition { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } public decimal Qty { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/Rfq.cs b/Backend/ERPCore/Domain/Entities/Rfq.cs index e7177fd..5a4e5c1 100644 --- a/Backend/ERPCore/Domain/Entities/Rfq.cs +++ b/Backend/ERPCore/Domain/Entities/Rfq.cs @@ -8,10 +8,10 @@ namespace ERPCore.Domain.Entities; /// public class Rfq { - public long RfqId { get; set; } + public int RfqId { get; set; } public string DocNo { get; set; } = string.Empty; - public long RequisitionId { get; set; } + public int RequisitionId { get; set; } public Requisition? Requisition { get; set; } public RfqStatus Status { get; set; } = RfqStatus.Open; diff --git a/Backend/ERPCore/Domain/Entities/RfqLine.cs b/Backend/ERPCore/Domain/Entities/RfqLine.cs index a91191b..9b73ca5 100644 --- a/Backend/ERPCore/Domain/Entities/RfqLine.cs +++ b/Backend/ERPCore/Domain/Entities/RfqLine.cs @@ -3,12 +3,12 @@ namespace ERPCore.Domain.Entities; /// RFQ line — an item + quantity being quoted (FR-PROC-02). Model: docs/10 Part C.2. public class RfqLine { - public long RfqLineId { get; set; } + public int RfqLineId { get; set; } - public long RfqId { get; set; } + public int RfqId { get; set; } public Rfq? Rfq { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } public decimal Qty { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/Serial.cs b/Backend/ERPCore/Domain/Entities/Serial.cs index 2fdd5a4..a4d1876 100644 --- a/Backend/ERPCore/Domain/Entities/Serial.cs +++ b/Backend/ERPCore/Domain/Entities/Serial.cs @@ -6,9 +6,9 @@ namespace ERPCore.Domain.Entities; /// public class Serial { - public long SerialId { get; set; } + public int SerialId { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } public string SerialNo { get; set; } = string.Empty; diff --git a/Backend/ERPCore/Domain/Entities/StockAdjustment.cs b/Backend/ERPCore/Domain/Entities/StockAdjustment.cs index 3b47f1a..be583d4 100644 --- a/Backend/ERPCore/Domain/Entities/StockAdjustment.cs +++ b/Backend/ERPCore/Domain/Entities/StockAdjustment.cs @@ -10,18 +10,18 @@ namespace ERPCore.Domain.Entities; /// public class StockAdjustment { - public long AdjustmentId { get; set; } + public int AdjustmentId { get; set; } public string DocNo { get; set; } = string.Empty; - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public Warehouse? Warehouse { get; set; } - public long ReasonCodeId { get; set; } + public int ReasonCodeId { get; set; } public ReasonCode? ReasonCode { get; set; } public AdjustmentStatus Status { get; set; } = AdjustmentStatus.Posted; - public long CreatedBy { get; set; } + public int CreatedBy { get; set; } public User? Creator { get; set; } public DateTime CreatedAt { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/StockAdjustmentLine.cs b/Backend/ERPCore/Domain/Entities/StockAdjustmentLine.cs index dbdae9b..8bbc1ad 100644 --- a/Backend/ERPCore/Domain/Entities/StockAdjustmentLine.cs +++ b/Backend/ERPCore/Domain/Entities/StockAdjustmentLine.cs @@ -7,17 +7,17 @@ namespace ERPCore.Domain.Entities; /// public class StockAdjustmentLine { - public long AdjLineId { get; set; } + public int AdjLineId { get; set; } - public long AdjustmentId { get; set; } + public int AdjustmentId { get; set; } public StockAdjustment? Adjustment { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } - public long? BinId { get; set; } - public long? BatchId { get; set; } - public long? SerialId { get; set; } + public int? BinId { get; set; } + public int? BatchId { get; set; } + public int? SerialId { get; set; } public decimal QtyDelta { get; set; } } diff --git a/Backend/ERPCore/Domain/Entities/StockCount.cs b/Backend/ERPCore/Domain/Entities/StockCount.cs index 5d652d8..2a2a118 100644 --- a/Backend/ERPCore/Domain/Entities/StockCount.cs +++ b/Backend/ERPCore/Domain/Entities/StockCount.cs @@ -10,16 +10,16 @@ namespace ERPCore.Domain.Entities; /// public class StockCount { - public long CountId { get; set; } + public int CountId { get; set; } public string DocNo { get; set; } = string.Empty; - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public Warehouse? Warehouse { get; set; } public CountType CountType { get; set; } public CountStatus Status { get; set; } = CountStatus.Draft; - public long CreatedBy { get; set; } + public int CreatedBy { get; set; } public User? Creator { get; set; } public DateTime CreatedAt { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/StockCountLine.cs b/Backend/ERPCore/Domain/Entities/StockCountLine.cs index 2a8ef40..debcfb7 100644 --- a/Backend/ERPCore/Domain/Entities/StockCountLine.cs +++ b/Backend/ERPCore/Domain/Entities/StockCountLine.cs @@ -6,15 +6,15 @@ namespace ERPCore.Domain.Entities; /// public class StockCountLine { - public long CountLineId { get; set; } + public int CountLineId { get; set; } - public long CountId { get; set; } + public int CountId { get; set; } public StockCount? Count { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } - public long? BinId { get; set; } + public int? BinId { get; set; } public decimal SystemQty { get; set; } public decimal? CountedQty { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/StockLayer.cs b/Backend/ERPCore/Domain/Entities/StockLayer.cs index de686c1..b4d1100 100644 --- a/Backend/ERPCore/Domain/Entities/StockLayer.cs +++ b/Backend/ERPCore/Domain/Entities/StockLayer.cs @@ -8,22 +8,22 @@ namespace ERPCore.Domain.Entities; /// public class StockLayer { - public long LayerId { get; set; } + public int LayerId { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public Warehouse? Warehouse { get; set; } - public long? BatchId { get; set; } + public int? BatchId { get; set; } public Batch? Batch { get; set; } - public long? SerialId { get; set; } + public int? SerialId { get; set; } public Serial? Serial { get; set; } /// Originating GRN line — carries the inspection hold status for this stock. - public long? GrnLineId { get; set; } + public int? GrnLineId { get; set; } public GrnLine? GrnLine { get; set; } public decimal QtyReceived { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/StockLedger.cs b/Backend/ERPCore/Domain/Entities/StockLedger.cs index cbffbf7..88d3997 100644 --- a/Backend/ERPCore/Domain/Entities/StockLedger.cs +++ b/Backend/ERPCore/Domain/Entities/StockLedger.cs @@ -11,14 +11,14 @@ namespace ERPCore.Domain.Entities; /// public class StockLedger { - public long LedgerId { get; set; } + public int LedgerId { get; set; } - public long ItemId { get; set; } - public long WarehouseId { get; set; } - public long? BinId { get; set; } - public long? BatchId { get; set; } - public long? SerialId { get; set; } - public long UserId { get; set; } + public int ItemId { get; set; } + public int WarehouseId { get; set; } + public int? BinId { get; set; } + public int? BatchId { get; set; } + public int? SerialId { get; set; } + public int UserId { get; set; } public Direction Direction { get; set; } public decimal QtyBase { get; set; } @@ -27,6 +27,6 @@ public class StockLedger public decimal RunningBalance { get; set; } public string SourceDocType { get; set; } = string.Empty; - public long SourceDocId { get; set; } + public int SourceDocId { get; set; } public DateTime CreatedAt { get; set; } } diff --git a/Backend/ERPCore/Domain/Entities/StockTransfer.cs b/Backend/ERPCore/Domain/Entities/StockTransfer.cs index 7bfc244..0a17c89 100644 --- a/Backend/ERPCore/Domain/Entities/StockTransfer.cs +++ b/Backend/ERPCore/Domain/Entities/StockTransfer.cs @@ -10,18 +10,18 @@ namespace ERPCore.Domain.Entities; /// public class StockTransfer { - public long TransferId { get; set; } + public int TransferId { get; set; } public string DocNo { get; set; } = string.Empty; - public long SrcWarehouseId { get; set; } + public int SrcWarehouseId { get; set; } public Warehouse? SrcWarehouse { get; set; } - public long DestWarehouseId { get; set; } + public int DestWarehouseId { get; set; } public Warehouse? DestWarehouse { get; set; } public TransferStatus Status { get; set; } = TransferStatus.Draft; - public long CreatedBy { get; set; } + public int CreatedBy { get; set; } public User? Creator { get; set; } public DateTime CreatedAt { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/StockTransferLine.cs b/Backend/ERPCore/Domain/Entities/StockTransferLine.cs index 3959676..240d815 100644 --- a/Backend/ERPCore/Domain/Entities/StockTransferLine.cs +++ b/Backend/ERPCore/Domain/Entities/StockTransferLine.cs @@ -12,18 +12,18 @@ namespace ERPCore.Domain.Entities; /// public class StockTransferLine { - public long TransferLineId { get; set; } + public int TransferLineId { get; set; } - public long TransferId { get; set; } + public int TransferId { get; set; } public StockTransfer? Transfer { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } - public long? SrcBinId { get; set; } - public long? DestBinId { get; set; } - public long? BatchId { get; set; } - public long? SerialId { get; set; } + public int? SrcBinId { get; set; } + public int? DestBinId { get; set; } + public int? BatchId { get; set; } + public int? SerialId { get; set; } public decimal Qty { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/Uom.cs b/Backend/ERPCore/Domain/Entities/Uom.cs index 2200e55..485b16a 100644 --- a/Backend/ERPCore/Domain/Entities/Uom.cs +++ b/Backend/ERPCore/Domain/Entities/Uom.cs @@ -6,6 +6,6 @@ namespace ERPCore.Domain.Entities; /// public class Uom { - public long UomId { get; set; } + public int UomId { get; set; } public string Name { get; set; } = string.Empty; } diff --git a/Backend/ERPCore/Domain/Entities/UomConversion.cs b/Backend/ERPCore/Domain/Entities/UomConversion.cs index fa76ef4..f44a99d 100644 --- a/Backend/ERPCore/Domain/Entities/UomConversion.cs +++ b/Backend/ERPCore/Domain/Entities/UomConversion.cs @@ -7,15 +7,15 @@ namespace ERPCore.Domain.Entities; /// public class UomConversion { - public long ConversionId { get; set; } + public int ConversionId { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } - public long FromUomId { get; set; } + public int FromUomId { get; set; } public Uom? FromUom { get; set; } - public long ToUomId { get; set; } + public int ToUomId { get; set; } public Uom? ToUom { get; set; } public decimal Factor { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/User.cs b/Backend/ERPCore/Domain/Entities/User.cs index 2966e68..ddd0048 100644 --- a/Backend/ERPCore/Domain/Entities/User.cs +++ b/Backend/ERPCore/Domain/Entities/User.cs @@ -4,7 +4,7 @@ namespace ERPCore.Domain.Entities; /// /// Application user (FR-X-01) — a **local shadow/projection** of an AuthHex identity. -/// The local (long) is what every `createdBy`/`requestedBy`/ +/// The local (int) is what every `createdBy`/`requestedBy`/ /// audit/ledger FK references; maps it to the AuthHex /// UserId (GUID) and is JIT-provisioned on first authenticated request /// (docs/10 A.4/C.7). A seeded system user (id 1, null AuthUserId) is the @@ -13,9 +13,9 @@ namespace ERPCore.Domain.Entities; public class User { /// Seeded fallback actor for unauthenticated/system operations. - public const long SystemUserId = 1; + public const int SystemUserId = 1; - public long UserId { get; set; } + public int UserId { get; set; } public string Username { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public EntityStatus Status { get; set; } = EntityStatus.Active; diff --git a/Backend/ERPCore/Domain/Entities/Vendor.cs b/Backend/ERPCore/Domain/Entities/Vendor.cs index 22d1e09..05a85d3 100644 --- a/Backend/ERPCore/Domain/Entities/Vendor.cs +++ b/Backend/ERPCore/Domain/Entities/Vendor.cs @@ -9,7 +9,7 @@ namespace ERPCore.Domain.Entities; /// public class Vendor { - public long VendorId { get; set; } + public int VendorId { get; set; } public string Code { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string? Terms { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/VendorQuotation.cs b/Backend/ERPCore/Domain/Entities/VendorQuotation.cs index 5eab1f4..f8ba021 100644 --- a/Backend/ERPCore/Domain/Entities/VendorQuotation.cs +++ b/Backend/ERPCore/Domain/Entities/VendorQuotation.cs @@ -12,12 +12,12 @@ namespace ERPCore.Domain.Entities; /// public class VendorQuotation { - public long QuotationId { get; set; } + public int QuotationId { get; set; } - public long RfqId { get; set; } + public int RfqId { get; set; } public Rfq? Rfq { get; set; } - public long VendorId { get; set; } + public int VendorId { get; set; } public Vendor? Vendor { get; set; } public DateTime CreatedAt { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/VendorQuotationLine.cs b/Backend/ERPCore/Domain/Entities/VendorQuotationLine.cs index 6880524..20e6a1f 100644 --- a/Backend/ERPCore/Domain/Entities/VendorQuotationLine.cs +++ b/Backend/ERPCore/Domain/Entities/VendorQuotationLine.cs @@ -3,12 +3,12 @@ namespace ERPCore.Domain.Entities; /// Per-item quoted price and lead time within a (docs/11 §3.2). public class VendorQuotationLine { - public long QuotationLineId { get; set; } + public int QuotationLineId { get; set; } - public long QuotationId { get; set; } + public int QuotationId { get; set; } public VendorQuotation? Quotation { get; set; } - public long ItemId { get; set; } + public int ItemId { get; set; } public Item? Item { get; set; } public decimal UnitPrice { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/Warehouse.cs b/Backend/ERPCore/Domain/Entities/Warehouse.cs index fe7a402..bbcd8bf 100644 --- a/Backend/ERPCore/Domain/Entities/Warehouse.cs +++ b/Backend/ERPCore/Domain/Entities/Warehouse.cs @@ -6,7 +6,7 @@ namespace ERPCore.Domain.Entities; /// public class Warehouse { - public long WarehouseId { get; set; } + public int WarehouseId { get; set; } public string Code { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; diff --git a/Backend/ERPCore/Dtos/Audit/AuditDtos.cs b/Backend/ERPCore/Dtos/Audit/AuditDtos.cs index 4870725..1691243 100644 --- a/Backend/ERPCore/Dtos/Audit/AuditDtos.cs +++ b/Backend/ERPCore/Dtos/Audit/AuditDtos.cs @@ -5,8 +5,8 @@ namespace ERPCore.Dtos.Audit; /// An audit-trail entry (FR-X-02). ChangeSet is the stored JSON, inlined. public sealed record AuditLogDto( - long AuditId, long UserId, string EntityType, long EntityId, AuditAction Action, JsonElement ChangeSet, DateTime CreatedAt); + int AuditId, int UserId, string EntityType, int EntityId, AuditAction Action, JsonElement ChangeSet, DateTime CreatedAt); /// A GL-ready journal stub emitted per stock movement (FR-STK-13). public sealed record JournalEntryStubDto( - long JournalId, string SourceDocType, long SourceDocId, string DebitAccount, string CreditAccount, decimal Amount); + int JournalId, string SourceDocType, int SourceDocId, string DebitAccount, string CreditAccount, decimal Amount); diff --git a/Backend/ERPCore/Dtos/Categories/CategoryDtos.cs b/Backend/ERPCore/Dtos/Categories/CategoryDtos.cs index 856e23b..744026e 100644 --- a/Backend/ERPCore/Dtos/Categories/CategoryDtos.cs +++ b/Backend/ERPCore/Dtos/Categories/CategoryDtos.cs @@ -3,13 +3,13 @@ using System.ComponentModel.DataAnnotations; namespace ERPCore.Dtos.Categories; /// Flat category resource (docs/11-BACKEND-PHASE1.md §2.3). -public sealed record CategoryDto(long CategoryId, string Name, long? ParentId); +public sealed record CategoryDto(int CategoryId, string Name, int? ParentId); /// Nested category node for GET /categories?tree=true. -public sealed record CategoryTreeDto(long CategoryId, string Name, long? ParentId, IReadOnlyList Children); +public sealed record CategoryTreeDto(int CategoryId, string Name, int? ParentId, IReadOnlyList Children); public sealed class CreateCategoryRequest { [Required, StringLength(200)] public string Name { get; set; } = string.Empty; - public long? ParentId { get; set; } + public int? ParentId { get; set; } } diff --git a/Backend/ERPCore/Dtos/Grn/GrnDtos.cs b/Backend/ERPCore/Dtos/Grn/GrnDtos.cs index 72582ed..bdcff2b 100644 --- a/Backend/ERPCore/Dtos/Grn/GrnDtos.cs +++ b/Backend/ERPCore/Dtos/Grn/GrnDtos.cs @@ -6,22 +6,22 @@ namespace ERPCore.Dtos.Grn; // Responses (docs/11 §4) -------------------------------------------------------- public sealed record GrnLineDto( - long GrnLineId, long? PoLineId, long ItemId, long UomId, long? BinId, - decimal Qty, decimal UnitCost, decimal ReceivedValue, HoldStatus HoldStatus, long? BatchId); + int GrnLineId, int? PoLineId, int ItemId, int UomId, int? BinId, + decimal Qty, decimal UnitCost, decimal ReceivedValue, HoldStatus HoldStatus, int? BatchId); public sealed record GrnDto( - long GrnId, string DocNo, long? PoId, long VendorId, long WarehouseId, GrnStatus Status, - long CreatedBy, DateTime CreatedAt, DateTime? PostedAt, IReadOnlyList Lines); + int GrnId, string DocNo, int? PoId, int VendorId, int WarehouseId, GrnStatus Status, + int CreatedBy, DateTime CreatedAt, DateTime? PostedAt, IReadOnlyList Lines); public sealed record CreatedLayerDto( - long LayerId, long ItemId, long WarehouseId, long? BatchId, + int LayerId, int ItemId, int WarehouseId, int? BatchId, decimal QtyReceived, decimal QtyRemaining, decimal UnitCost, DateTime ReceiptDate); public sealed record GrnConfirmResultDto( - long GrnId, GrnStatus Status, DateTime PostedAt, - IReadOnlyList CreatedLayers, IReadOnlyList LedgerRefs, PurchaseOrderStatus? PoStatus); + int GrnId, GrnStatus Status, DateTime PostedAt, + IReadOnlyList CreatedLayers, IReadOnlyList LedgerRefs, PurchaseOrderStatus? PoStatus); -public sealed record ReleaseLineResultDto(long GrnLineId, HoldStatus HoldStatus); +public sealed record ReleaseLineResultDto(int GrnLineId, HoldStatus HoldStatus); // Requests ---------------------------------------------------------------------- @@ -34,10 +34,10 @@ public sealed class BatchInput public sealed class CreateGrnLineInput { /// Set for a PO-based receipt; cost is then derived from the PO line (02-SECURITY C.3). - public long? PoLineId { get; set; } - [Required] public long ItemId { get; set; } - [Required] public long UomId { get; set; } - public long? BinId { get; set; } + public int? PoLineId { get; set; } + [Required] public int ItemId { get; set; } + [Required] public int UomId { get; set; } + public int? BinId { get; set; } [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } /// Used only for direct (no-PO) receipts; ignored when is set. [Range(0, double.MaxValue)] public decimal UnitCost { get; set; } @@ -48,10 +48,10 @@ public sealed class CreateGrnLineInput public sealed class CreateGrnRequest { /// PO to receive against; null for a direct/emergency receipt (FR-GRN-02). - public long? PoId { get; set; } + public int? PoId { get; set; } /// Required for a direct receipt (no PO); ignored otherwise (vendor derives from the PO). - public long? VendorId { get; set; } - [Required] public long WarehouseId { get; set; } + public int? VendorId { get; set; } + [Required] public int WarehouseId { get; set; } [Required, MinLength(1)] public List Lines { get; set; } = new(); } diff --git a/Backend/ERPCore/Dtos/Items/ItemDtos.cs b/Backend/ERPCore/Dtos/Items/ItemDtos.cs index c372cad..7bb11e7 100644 --- a/Backend/ERPCore/Dtos/Items/ItemDtos.cs +++ b/Backend/ERPCore/Dtos/Items/ItemDtos.cs @@ -7,25 +7,25 @@ namespace ERPCore.Dtos.Items; /// Row shape for GET /items. public sealed record ItemListItemDto( - long ItemId, string Sku, string Name, long CategoryId, long BaseUomId, - long? DefaultVendorId, ItemType ItemType, TrackingMode TrackingMode, + int ItemId, string Sku, string Name, int CategoryId, int BaseUomId, + int? DefaultVendorId, ItemType ItemType, TrackingMode TrackingMode, string? TaxClass, EntityStatus Status); /// A single per-warehouse reorder policy row. -public sealed record ItemReorderDto(long WarehouseId, decimal ReorderPoint, decimal ReorderQty); +public sealed record ItemReorderDto(int WarehouseId, decimal ReorderPoint, decimal ReorderQty); /// Full item resource for GET /items/{id} and create/update responses. public sealed record ItemDetailDto( - long ItemId, string Sku, string Name, string? Description, long CategoryId, - long BaseUomId, long? DefaultVendorId, ItemType ItemType, TrackingMode TrackingMode, + int ItemId, string Sku, string Name, string? Description, int CategoryId, + int BaseUomId, int? DefaultVendorId, ItemType ItemType, TrackingMode TrackingMode, string? TaxClass, EntityStatus Status, IReadOnlyList Reorder, DateTime CreatedAt, DateTime? UpdatedAt); /// UOM conversion row (docs/11 §2.2). -public sealed record UomConversionDto(long ConversionId, long FromUom, long ToUom, decimal Factor); +public sealed record UomConversionDto(int ConversionId, int FromUom, int ToUom, decimal Factor); /// Response body for PUT /items/{id}/uom-conversions. -public sealed record ItemUomConversionsDto(long ItemId, long BaseUomId, IReadOnlyList Conversions); +public sealed record ItemUomConversionsDto(int ItemId, int BaseUomId, IReadOnlyList Conversions); /// Response body for PUT /items/{id}/reorder. public sealed record ItemReorderSettingsDto(IReadOnlyList Settings); @@ -38,9 +38,9 @@ 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] public int CategoryId { get; set; } + [Required] public int BaseUomId { get; set; } + public int? 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; } @@ -51,9 +51,9 @@ 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] public int CategoryId { get; set; } + [Required] public int BaseUomId { get; set; } + public int? 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; } @@ -66,7 +66,7 @@ public sealed class UpdateItemStatusRequest public sealed class ReorderSettingInput { - [Required] public long WarehouseId { get; set; } + [Required] public int WarehouseId { get; set; } [Range(0, double.MaxValue)] public decimal ReorderPoint { get; set; } [Range(0, double.MaxValue)] public decimal ReorderQty { get; set; } } @@ -78,8 +78,8 @@ public sealed class UpdateReorderRequest public sealed class UomConversionInput { - [Required] public long FromUom { get; set; } - [Required] public long ToUom { get; set; } + [Required] public int FromUom { get; set; } + [Required] public int ToUom { get; set; } [Range(0.000001, double.MaxValue)] public decimal Factor { get; set; } } diff --git a/Backend/ERPCore/Dtos/Procurement/PurchaseOrderDtos.cs b/Backend/ERPCore/Dtos/Procurement/PurchaseOrderDtos.cs index 2c372c8..c70e9a7 100644 --- a/Backend/ERPCore/Dtos/Procurement/PurchaseOrderDtos.cs +++ b/Backend/ERPCore/Dtos/Procurement/PurchaseOrderDtos.cs @@ -6,27 +6,27 @@ namespace ERPCore.Dtos.Procurement; // Responses (docs/11 §3.3) ------------------------------------------------------ public sealed record PoLineDto( - long PoLineId, long ItemId, long UomId, long WarehouseId, + int PoLineId, int ItemId, int UomId, int WarehouseId, decimal Qty, decimal UnitPrice, decimal Tax, decimal QtyReceived); public sealed record PoTotalsDto(decimal SubTotal, decimal Tax, decimal GrandTotal, string Currency); public sealed record PurchaseOrderDto( - long PoId, string DocNo, long VendorId, long? RequisitionId, PurchaseOrderStatus Status, - bool ApprovalRequired, long CreatedBy, DateTime CreatedAt, DateTime? UpdatedAt, + int PoId, string DocNo, int VendorId, int? RequisitionId, PurchaseOrderStatus Status, + bool ApprovalRequired, int CreatedBy, DateTime CreatedAt, DateTime? UpdatedAt, PoTotalsDto Totals, IReadOnlyList Lines); public sealed record PurchaseOrderSummaryDto( - long PoId, string DocNo, long VendorId, PurchaseOrderStatus Status, + int PoId, string DocNo, int VendorId, PurchaseOrderStatus Status, bool ApprovalRequired, DateTime CreatedAt, PoTotalsDto Totals); // Requests — server sets docNo, status, createdBy, timestamps, qtyReceived, totals public sealed class CreatePoLineInput { - [Required] public long ItemId { get; set; } - [Required] public long UomId { get; set; } - [Required] public long WarehouseId { get; set; } + [Required] public int ItemId { get; set; } + [Required] public int UomId { get; set; } + [Required] public int WarehouseId { get; set; } [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } [Range(0, double.MaxValue)] public decimal UnitPrice { get; set; } [Range(0, 1)] public decimal Tax { get; set; } @@ -34,15 +34,15 @@ public sealed class CreatePoLineInput public sealed class CreatePurchaseOrderRequest { - [Required] public long VendorId { get; set; } - public long? RequisitionId { get; set; } + [Required] public int VendorId { get; set; } + public int? RequisitionId { get; set; } [Required, MinLength(1)] public List Lines { get; set; } = new(); } public sealed class UpdatePurchaseOrderRequest { - [Required] public long VendorId { get; set; } - public long? RequisitionId { get; set; } + [Required] public int VendorId { get; set; } + public int? RequisitionId { get; set; } [Required, MinLength(1)] public List Lines { get; set; } = new(); } diff --git a/Backend/ERPCore/Dtos/Procurement/PurchaseReturnDtos.cs b/Backend/ERPCore/Dtos/Procurement/PurchaseReturnDtos.cs index 85328ed..f9deb75 100644 --- a/Backend/ERPCore/Dtos/Procurement/PurchaseReturnDtos.cs +++ b/Backend/ERPCore/Dtos/Procurement/PurchaseReturnDtos.cs @@ -5,27 +5,27 @@ namespace ERPCore.Dtos.Procurement; // Responses (docs/11 §3.4) ------------------------------------------------------ -public sealed record PurchaseReturnLineDto(long ReturnLineId, long? GrnLineId, long ItemId, decimal Qty); +public sealed record PurchaseReturnLineDto(int ReturnLineId, int? GrnLineId, int ItemId, decimal Qty); public sealed record PurchaseReturnDto( - long ReturnId, string DocNo, long VendorId, long WarehouseId, long ReasonCodeId, ReturnStatus Status, - long CreatedBy, IReadOnlyList Lines, IReadOnlyList LedgerRefs); + int ReturnId, string DocNo, int VendorId, int WarehouseId, int ReasonCodeId, ReturnStatus Status, + int CreatedBy, IReadOnlyList Lines, IReadOnlyList LedgerRefs); // Requests ---------------------------------------------------------------------- public sealed class CreatePurchaseReturnLineInput { /// Original GRN line, for traceability against the receipt. - public long? GrnLineId { get; set; } - [Required] public long ItemId { get; set; } + public int? GrnLineId { get; set; } + [Required] public int ItemId { get; set; } [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } } public sealed class CreatePurchaseReturnRequest { - [Required] public long VendorId { get; set; } - [Required] public long WarehouseId { get; set; } + [Required] public int VendorId { get; set; } + [Required] public int WarehouseId { get; set; } /// Nullable so an omitted value is a distinct REASON_CODE_REQUIRED error. - public long? ReasonCodeId { get; set; } + public int? ReasonCodeId { get; set; } [Required, MinLength(1)] public List Lines { get; set; } = new(); } diff --git a/Backend/ERPCore/Dtos/Procurement/RequisitionDtos.cs b/Backend/ERPCore/Dtos/Procurement/RequisitionDtos.cs index d9af44f..63b5e0a 100644 --- a/Backend/ERPCore/Dtos/Procurement/RequisitionDtos.cs +++ b/Backend/ERPCore/Dtos/Procurement/RequisitionDtos.cs @@ -5,20 +5,20 @@ namespace ERPCore.Dtos.Procurement; // Responses (docs/11 §3.1) ------------------------------------------------------ -public sealed record RequisitionLineDto(long ReqLineId, long ItemId, decimal Qty, DateOnly? RequiredBy); +public sealed record RequisitionLineDto(int ReqLineId, int ItemId, decimal Qty, DateOnly? RequiredBy); public sealed record RequisitionDto( - long RequisitionId, string DocNo, RequisitionStatus Status, long RequestedBy, + int RequisitionId, string DocNo, RequisitionStatus Status, int RequestedBy, DateTime CreatedAt, IReadOnlyList Lines); public sealed record RequisitionSummaryDto( - long RequisitionId, string DocNo, RequisitionStatus Status, long RequestedBy, DateTime CreatedAt); + int RequisitionId, string DocNo, RequisitionStatus Status, int RequestedBy, DateTime CreatedAt); // Requests — server sets docNo, status, requestedBy (audit actor), timestamps ---- public sealed class CreateRequisitionLineInput { - [Required] public long ItemId { get; set; } + [Required] public int ItemId { get; set; } [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } public DateOnly? RequiredBy { get; set; } } diff --git a/Backend/ERPCore/Dtos/Procurement/RfqDtos.cs b/Backend/ERPCore/Dtos/Procurement/RfqDtos.cs index 8109941..d54ab90 100644 --- a/Backend/ERPCore/Dtos/Procurement/RfqDtos.cs +++ b/Backend/ERPCore/Dtos/Procurement/RfqDtos.cs @@ -5,46 +5,46 @@ namespace ERPCore.Dtos.Procurement; // Responses (docs/11 §3.2) ------------------------------------------------------ -public sealed record RfqLineDto(long RfqLineId, long ItemId, decimal Qty); +public sealed record RfqLineDto(int RfqLineId, int ItemId, decimal Qty); public sealed record RfqDto( - long RfqId, string DocNo, long RequisitionId, RfqStatus Status, IReadOnlyList Lines); + int RfqId, string DocNo, int RequisitionId, RfqStatus Status, IReadOnlyList Lines); -public sealed record QuotationLineDto(long ItemId, decimal UnitPrice, int LeadDays); +public sealed record QuotationLineDto(int ItemId, decimal UnitPrice, int LeadDays); public sealed record VendorQuotationDto( - long QuotationId, long RfqId, long VendorId, IReadOnlyList Lines); + int QuotationId, int RfqId, int VendorId, IReadOnlyList Lines); /// Per-item, per-vendor price matrix for GET /rfqs/{id}/comparison. -public sealed record RfqComparisonCellDto(long VendorId, long QuotationId, decimal UnitPrice, int LeadDays); -public sealed record RfqComparisonRowDto(long ItemId, decimal Qty, IReadOnlyList Quotes); -public sealed record RfqComparisonDto(long RfqId, IReadOnlyList VendorIds, IReadOnlyList Rows); +public sealed record RfqComparisonCellDto(int VendorId, int QuotationId, decimal UnitPrice, int LeadDays); +public sealed record RfqComparisonRowDto(int ItemId, decimal Qty, IReadOnlyList Quotes); +public sealed record RfqComparisonDto(int RfqId, IReadOnlyList VendorIds, IReadOnlyList Rows); // Requests ---------------------------------------------------------------------- public sealed class CreateRfqLineInput { - [Required] public long ItemId { get; set; } + [Required] public int ItemId { get; set; } [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } } public sealed class CreateRfqRequest { - [Required] public long RequisitionId { get; set; } + [Required] public int RequisitionId { get; set; } /// Vendors the RFQ is issued to (validated for existence; quotations reference them). - public List VendorIds { get; set; } = new(); + public List VendorIds { get; set; } = new(); [Required, MinLength(1)] public List Lines { get; set; } = new(); } public sealed class CreateQuotationLineInput { - [Required] public long ItemId { get; set; } + [Required] public int ItemId { get; set; } [Range(0, double.MaxValue)] public decimal UnitPrice { get; set; } [Range(0, int.MaxValue)] public int LeadDays { get; set; } } public sealed class CreateQuotationRequest { - [Required] public long VendorId { get; set; } + [Required] public int VendorId { get; set; } [Required, MinLength(1)] public List Lines { get; set; } = new(); } diff --git a/Backend/ERPCore/Dtos/Reference/ReasonCodeDtos.cs b/Backend/ERPCore/Dtos/Reference/ReasonCodeDtos.cs index 60080f2..8287289 100644 --- a/Backend/ERPCore/Dtos/Reference/ReasonCodeDtos.cs +++ b/Backend/ERPCore/Dtos/Reference/ReasonCodeDtos.cs @@ -4,7 +4,7 @@ using ERPCore.Domain.Enums; namespace ERPCore.Dtos.Reference; /// Reason code (docs/11 §6). -public sealed record ReasonCodeDto(long ReasonCodeId, string Code, string Description, ReasonContext Context); +public sealed record ReasonCodeDto(int ReasonCodeId, string Code, string Description, ReasonContext Context); public sealed class CreateReasonCodeRequest { diff --git a/Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs b/Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs index 57d41a3..a8e9696 100644 --- a/Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs +++ b/Backend/ERPCore/Dtos/Stock/AdjustmentDtos.cs @@ -5,27 +5,27 @@ namespace ERPCore.Dtos.Stock; // Responses (docs/11 §5.5) ------------------------------------------------------ -public sealed record AdjustmentLineDto(long AdjLineId, long ItemId, long? BinId, long? BatchId, decimal QtyDelta); +public sealed record AdjustmentLineDto(int AdjLineId, int ItemId, int? BinId, int? BatchId, decimal QtyDelta); public sealed record AdjustmentDto( - long AdjustmentId, string DocNo, long WarehouseId, long ReasonCodeId, AdjustmentStatus Status, - long CreatedBy, DateTime CreatedAt, IReadOnlyList Lines, IReadOnlyList LedgerRefs); + int AdjustmentId, string DocNo, int WarehouseId, int ReasonCodeId, AdjustmentStatus Status, + int CreatedBy, DateTime CreatedAt, IReadOnlyList Lines, IReadOnlyList LedgerRefs); // Requests ---------------------------------------------------------------------- public sealed class CreateAdjustmentLineInput { - [Required] public long ItemId { get; set; } - public long? BinId { get; set; } - public long? BatchId { get; set; } + [Required] public int ItemId { get; set; } + public int? BinId { get; set; } + public int? BatchId { get; set; } /// Signed base-UOM delta: negative consumes FIFO layers, positive adds stock. public decimal QtyDelta { get; set; } } public sealed class CreateAdjustmentRequest { - [Required] public long WarehouseId { get; set; } + [Required] public int WarehouseId { get; set; } /// Nullable so an omitted value is a distinct REASON_CODE_REQUIRED error, not 0. - public long? ReasonCodeId { get; set; } + public int? ReasonCodeId { get; set; } [Required, MinLength(1)] public List Lines { get; set; } = new(); } diff --git a/Backend/ERPCore/Dtos/Stock/CountDtos.cs b/Backend/ERPCore/Dtos/Stock/CountDtos.cs index 79a4b73..44a67ec 100644 --- a/Backend/ERPCore/Dtos/Stock/CountDtos.cs +++ b/Backend/ERPCore/Dtos/Stock/CountDtos.cs @@ -5,25 +5,25 @@ namespace ERPCore.Dtos.Stock; // Responses (docs/11 §5.6) ------------------------------------------------------ -public sealed record CountLineDto(long CountLineId, long ItemId, long? BinId, decimal SystemQty, decimal? CountedQty, decimal? Variance); +public sealed record CountLineDto(int CountLineId, int ItemId, int? BinId, decimal SystemQty, decimal? CountedQty, decimal? Variance); public sealed record CountDto( - long CountId, string DocNo, long WarehouseId, CountType CountType, CountStatus Status, IReadOnlyList Lines); + int CountId, string DocNo, int WarehouseId, CountType CountType, CountStatus Status, IReadOnlyList Lines); -public sealed record CountPostResultDto(long CountId, CountStatus Status, long? AdjustmentId, IReadOnlyList LedgerRefs); +public sealed record CountPostResultDto(int CountId, CountStatus Status, int? AdjustmentId, IReadOnlyList LedgerRefs); // Requests ---------------------------------------------------------------------- public sealed class CreateCountRequest { - [Required] public long WarehouseId { get; set; } + [Required] public int WarehouseId { get; set; } [Required, EnumDataType(typeof(CountType))] public CountType CountType { get; set; } - [Required, MinLength(1)] public List ItemIds { get; set; } = new(); + [Required, MinLength(1)] public List ItemIds { get; set; } = new(); } public sealed class EnterCountLineInput { - [Required] public long CountLineId { get; set; } + [Required] public int CountLineId { get; set; } [Range(0, double.MaxValue)] public decimal CountedQty { get; set; } } diff --git a/Backend/ERPCore/Dtos/Stock/ReorderDtos.cs b/Backend/ERPCore/Dtos/Stock/ReorderDtos.cs index 48d1b7c..d325850 100644 --- a/Backend/ERPCore/Dtos/Stock/ReorderDtos.cs +++ b/Backend/ERPCore/Dtos/Stock/ReorderDtos.cs @@ -2,5 +2,5 @@ namespace ERPCore.Dtos.Stock; /// An item at/below its reorder point (docs/11 §5.7; FR-STK-10). Computed on read. public sealed record ReorderAlertDto( - long ItemId, long WarehouseId, decimal Available, + int ItemId, int WarehouseId, decimal Available, decimal ReorderPoint, decimal ReorderQty, decimal SuggestedRequisitionQty); diff --git a/Backend/ERPCore/Dtos/Stock/StockDtos.cs b/Backend/ERPCore/Dtos/Stock/StockDtos.cs index a1663e8..9a1b3b2 100644 --- a/Backend/ERPCore/Dtos/Stock/StockDtos.cs +++ b/Backend/ERPCore/Dtos/Stock/StockDtos.cs @@ -4,19 +4,19 @@ namespace ERPCore.Dtos.Stock; /// Stock enquiry (docs/11 §5.1). available = onHand − onHold − reserved − inTransit(out). public sealed record StockOnHandDto( - long ItemId, long WarehouseId, decimal OnHand, decimal Available, + int ItemId, int WarehouseId, decimal OnHand, decimal Available, decimal OnHold, decimal InTransit, decimal Reserved, DateTime AsOf); /// A stock-ledger row (docs/11 §5.2). public sealed record StockLedgerRowDto( - long LedgerId, long ItemId, long WarehouseId, long? BinId, long? BatchId, long? SerialId, + int LedgerId, int ItemId, int WarehouseId, int? BinId, int? BatchId, int? SerialId, Direction Direction, decimal QtyBase, decimal UnitCost, decimal Value, decimal RunningBalance, - string SourceDocType, long SourceDocId, long UserId, DateTime CreatedAt); + string SourceDocType, int SourceDocId, int UserId, DateTime CreatedAt); /// An open FIFO layer in a valuation (docs/11 §5.3). -public sealed record StockValuationLayerDto(long LayerId, decimal QtyRemaining, decimal UnitCost, decimal Value, DateTime ReceiptDate); +public sealed record StockValuationLayerDto(int LayerId, decimal QtyRemaining, decimal UnitCost, decimal Value, DateTime ReceiptDate); /// Valuation of on-hand stock from open FIFO layers (docs/11 §5.3). public sealed record StockValuationDto( - long ItemId, long WarehouseId, IReadOnlyList Layers, + int ItemId, int WarehouseId, IReadOnlyList Layers, decimal TotalQty, decimal TotalValue, string Currency, string CostingMethod); diff --git a/Backend/ERPCore/Dtos/Stock/TransferDtos.cs b/Backend/ERPCore/Dtos/Stock/TransferDtos.cs index 3974852..dd91237 100644 --- a/Backend/ERPCore/Dtos/Stock/TransferDtos.cs +++ b/Backend/ERPCore/Dtos/Stock/TransferDtos.cs @@ -6,43 +6,43 @@ namespace ERPCore.Dtos.Stock; // Responses (docs/11 §5.4) ------------------------------------------------------ public sealed record TransferLineDto( - long TransferLineId, long ItemId, long? SrcBinId, long? DestBinId, long? BatchId, decimal Qty, decimal QtyReceived); + int TransferLineId, int ItemId, int? SrcBinId, int? DestBinId, int? BatchId, decimal Qty, decimal QtyReceived); public sealed record TransferDto( - long TransferId, string DocNo, long SrcWarehouseId, long DestWarehouseId, + int TransferId, string DocNo, int SrcWarehouseId, int DestWarehouseId, TransferStatus Status, IReadOnlyList Lines); -public sealed record ConsumedLayerDto(long LayerId, decimal QtyConsumed, decimal UnitCost); +public sealed record ConsumedLayerDto(int LayerId, decimal QtyConsumed, decimal UnitCost); public sealed record DispatchResultDto( - long TransferId, TransferStatus Status, IReadOnlyList ConsumedLayers, IReadOnlyList LedgerRefs); + int TransferId, TransferStatus Status, IReadOnlyList ConsumedLayers, IReadOnlyList LedgerRefs); -public sealed record TransferCreatedLayerDto(long LayerId, long WarehouseId, decimal QtyReceived, decimal UnitCost); +public sealed record TransferCreatedLayerDto(int LayerId, int WarehouseId, decimal QtyReceived, decimal UnitCost); public sealed record ReceiveResultDto( - long TransferId, TransferStatus Status, IReadOnlyList CreatedLayers, IReadOnlyList LedgerRefs); + int TransferId, TransferStatus Status, IReadOnlyList CreatedLayers, IReadOnlyList LedgerRefs); // Requests ---------------------------------------------------------------------- public sealed class CreateTransferLineInput { - [Required] public long ItemId { get; set; } - public long? SrcBinId { get; set; } - public long? DestBinId { get; set; } - public long? BatchId { get; set; } + [Required] public int ItemId { get; set; } + public int? SrcBinId { get; set; } + public int? DestBinId { get; set; } + public int? BatchId { get; set; } [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } } public sealed class CreateTransferRequest { - [Required] public long SrcWarehouseId { get; set; } - [Required] public long DestWarehouseId { get; set; } + [Required] public int SrcWarehouseId { get; set; } + [Required] public int DestWarehouseId { get; set; } [Required, MinLength(1)] public List Lines { get; set; } = new(); } public sealed class ReceiveTransferLineInput { - [Required] public long TransferLineId { get; set; } + [Required] public int TransferLineId { get; set; } [Range(0.0001, double.MaxValue)] public decimal Qty { get; set; } } diff --git a/Backend/ERPCore/Dtos/Uoms/UomDtos.cs b/Backend/ERPCore/Dtos/Uoms/UomDtos.cs index 90f5259..655fd06 100644 --- a/Backend/ERPCore/Dtos/Uoms/UomDtos.cs +++ b/Backend/ERPCore/Dtos/Uoms/UomDtos.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; namespace ERPCore.Dtos.Uoms; /// UOM resource (docs/11-BACKEND-PHASE1.md §2.2). -public sealed record UomDto(long UomId, string Name); +public sealed record UomDto(int UomId, string Name); public sealed class CreateUomRequest { diff --git a/Backend/ERPCore/Dtos/Vendors/VendorDtos.cs b/Backend/ERPCore/Dtos/Vendors/VendorDtos.cs index 731099c..36c0145 100644 --- a/Backend/ERPCore/Dtos/Vendors/VendorDtos.cs +++ b/Backend/ERPCore/Dtos/Vendors/VendorDtos.cs @@ -5,7 +5,7 @@ namespace ERPCore.Dtos.Vendors; /// Vendor resource (docs/11-BACKEND-PHASE1.md §2.4). public sealed record VendorDto( - long VendorId, string Code, string Name, string? Terms, string? TaxReg, + int VendorId, string Code, string Name, string? Terms, string? TaxReg, string Currency, EntityStatus Status, DateTime CreatedAt, DateTime? UpdatedAt); public sealed class CreateVendorRequest diff --git a/Backend/ERPCore/Dtos/Warehouses/WarehouseDtos.cs b/Backend/ERPCore/Dtos/Warehouses/WarehouseDtos.cs index ad91678..37dea5c 100644 --- a/Backend/ERPCore/Dtos/Warehouses/WarehouseDtos.cs +++ b/Backend/ERPCore/Dtos/Warehouses/WarehouseDtos.cs @@ -3,10 +3,10 @@ using System.ComponentModel.DataAnnotations; namespace ERPCore.Dtos.Warehouses; /// Warehouse resource (docs/11-BACKEND-PHASE1.md §2.5). -public sealed record WarehouseDto(long WarehouseId, string Code, string Name); +public sealed record WarehouseDto(int WarehouseId, string Code, string Name); /// Bin/location resource (docs/11 §2.5). -public sealed record BinDto(long BinId, long WarehouseId, string Code, string? BinType); +public sealed record BinDto(int BinId, int WarehouseId, string Code, string? BinType); public sealed class CreateWarehouseRequest { diff --git a/Backend/ERPCore/Infra/Auth/CurrentUser.cs b/Backend/ERPCore/Infra/Auth/CurrentUser.cs index 06ff7ba..9e0a0b1 100644 --- a/Backend/ERPCore/Infra/Auth/CurrentUser.cs +++ b/Backend/ERPCore/Infra/Auth/CurrentUser.cs @@ -30,5 +30,5 @@ public sealed class CurrentUser : ICurrentUser } } - public long AuditUserId => long.TryParse(UserId, out var id) ? id : User.SystemUserId; + public int AuditUserId => int.TryParse(UserId, out var id) ? id : User.SystemUserId; } diff --git a/Backend/ERPCore/Infra/Auth/ICurrentUser.cs b/Backend/ERPCore/Infra/Auth/ICurrentUser.cs index be7317f..1c103a2 100644 --- a/Backend/ERPCore/Infra/Auth/ICurrentUser.cs +++ b/Backend/ERPCore/Infra/Auth/ICurrentUser.cs @@ -15,7 +15,7 @@ public interface ICurrentUser /// Resolves the token sub to a user id; falls back to the seeded system /// user () while auth is deferred (§6). /// - long AuditUserId { get; } + int AuditUserId { get; } /// True when the request carries an authenticated principal. bool IsAuthenticated { get; } diff --git a/Backend/ERPCore/Infra/Auth/ShadowUserClaimsTransformation.cs b/Backend/ERPCore/Infra/Auth/ShadowUserClaimsTransformation.cs index ebf7f21..e163895 100644 --- a/Backend/ERPCore/Infra/Auth/ShadowUserClaimsTransformation.cs +++ b/Backend/ERPCore/Infra/Auth/ShadowUserClaimsTransformation.cs @@ -12,7 +12,7 @@ namespace ERPCore.Infra.Auth; /// AuthHex tokens carry the user as a custom UserId (GUID) claim and no /// sub/nameid. This transformation JIT-provisions a local shadow /// (keyed by auth_user_id) and injects the local -/// long id as , so +/// int id as , so /// /AuditUserId resolve the real user unchanged. /// Idempotent — may run several times per request. /// @@ -40,7 +40,7 @@ public sealed class ShadowUserClaimsTransformation : IClaimsTransformation return principal; } - private async Task ResolveOrProvisionAsync(Guid authUserId, string? nic) + private async Task ResolveOrProvisionAsync(Guid authUserId, string? nic) { var existing = await _db.Users.AsNoTracking() .Where(u => u.AuthUserId == authUserId) diff --git a/Backend/ERPCore/Infra/Persistence/Auditing/AuditScribe.cs b/Backend/ERPCore/Infra/Persistence/Auditing/AuditScribe.cs index a65e413..8f231b0 100644 --- a/Backend/ERPCore/Infra/Persistence/Auditing/AuditScribe.cs +++ b/Backend/ERPCore/Infra/Persistence/Auditing/AuditScribe.cs @@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.ChangeTracking; namespace ERPCore.Infra.Persistence.Auditing; /// A mutation captured before save, awaiting its (possibly generated) key. -public sealed record PendingAudit(EntityEntry Entry, string EntityType, AuditAction Action, string ChangeSet, long CapturedId, bool IsAdded); +public sealed record PendingAudit(EntityEntry Entry, string EntityType, AuditAction Action, string ChangeSet, int CapturedId, bool IsAdded); /// /// Builds audit-trail rows from the EF change tracker (FR-X-02). High-volume / @@ -54,7 +54,7 @@ public static class AuditScribe return pending; } - public static AuditLog ToLog(PendingAudit p, long userId, DateTime now) => new() + public static AuditLog ToLog(PendingAudit p, int userId, DateTime now) => new() { UserId = userId, EntityType = p.EntityType, @@ -64,12 +64,12 @@ public static class AuditScribe CreatedAt = now, }; - private static long ReadKey(EntityEntry entry) + private static int ReadKey(EntityEntry entry) { var pk = entry.Metadata.FindPrimaryKey(); if (pk is null || pk.Properties.Count != 1) return 0; var value = entry.Property(pk.Properties[0].Name).CurrentValue; - return value is null ? 0 : Convert.ToInt64(value); + return value is null ? 0 : Convert.ToInt32(value); } private static string BuildChangeSet(EntityEntry entry, AuditAction action) diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260709095653_InitialCreate.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260709095653_InitialCreate.Designer.cs deleted file mode 100644 index 4d6fe75..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260709095653_InitialCreate.Designer.cs +++ /dev/null @@ -1,445 +0,0 @@ -// -using System; -using ERPCore.Infra.Persistence; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - [DbContext(typeof(ErpDbContext))] - [Migration("20260709095653_InitialCreate")] - partial class InitialCreate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.Property("BinId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); - - b.Property("BinType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("BinId"); - - b.HasIndex("WarehouseId", "Code") - .IsUnique(); - - b.ToTable("bins", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Property("CategoryId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.HasKey("CategoryId"); - - b.HasIndex("ParentId"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Property("ItemId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - - b.Property("BaseUomId") - .HasColumnType("bigint"); - - b.Property("CategoryId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultVendorId") - .HasColumnType("bigint"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxClass") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TrackingMode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("ItemId"); - - b.HasIndex("BaseUomId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("DefaultVendorId"); - - b.HasIndex("Sku") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("items", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.Property("ReorderId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ReorderPoint") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReorderQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReorderId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId") - .IsUnique(); - - b.ToTable("item_reorders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => - { - b.Property("UomId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("UomId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("uoms", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.Property("ConversionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); - - b.Property("Factor") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("FromUomId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ToUomId") - .HasColumnType("bigint"); - - b.HasKey("ConversionId"); - - b.HasIndex("FromUomId"); - - b.HasIndex("ToUomId"); - - b.HasIndex("ItemId", "FromUomId", "ToUomId") - .IsUnique(); - - b.ToTable("uom_conversions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => - { - b.Property("VendorId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Currency") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(3) - .HasColumnType("character varying(3)") - .HasDefaultValue("LKR"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxReg") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Terms") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("VendorId"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("vendors", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Property("WarehouseId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("WarehouseId"); - - b.HasIndex("Code") - .IsUnique(); - - b.ToTable("warehouses", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany("Bins") - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.HasOne("ERPCore.Domain.Entities.Category", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") - .WithMany() - .HasForeignKey("BaseUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") - .WithMany() - .HasForeignKey("DefaultVendorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("BaseUom"); - - b.Navigation("Category"); - - b.Navigation("DefaultVendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("ReorderSettings") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") - .WithMany() - .HasForeignKey("FromUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("UomConversions") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") - .WithMany() - .HasForeignKey("ToUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("FromUom"); - - b.Navigation("Item"); - - b.Navigation("ToUom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Navigation("ReorderSettings"); - - b.Navigation("UomConversions"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Navigation("Bins"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260709095653_InitialCreate.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260709095653_InitialCreate.cs deleted file mode 100644 index 7ba3005..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260709095653_InitialCreate.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - /// - public partial class InitialCreate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "categories", - columns: table => new - { - CategoryId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - ParentId = table.Column(type: "bigint", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_categories", x => x.CategoryId); - table.ForeignKey( - name: "FK_categories_categories_ParentId", - column: x => x.ParentId, - principalTable: "categories", - principalColumn: "CategoryId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "uoms", - columns: table => new - { - UomId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_uoms", x => x.UomId); - }); - - migrationBuilder.CreateTable( - name: "vendors", - columns: table => new - { - VendorId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - Terms = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), - TaxReg = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), - Currency = table.Column(type: "character varying(3)", maxLength: 3, nullable: false, defaultValue: "LKR"), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), - xmin = table.Column(type: "xid", rowVersion: true, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_vendors", x => x.VendorId); - }); - - migrationBuilder.CreateTable( - name: "warehouses", - columns: table => new - { - WarehouseId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_warehouses", x => x.WarehouseId); - }); - - migrationBuilder.CreateTable( - name: "items", - columns: table => new - { - ItemId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Sku = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - CategoryId = table.Column(type: "bigint", nullable: false), - BaseUomId = table.Column(type: "bigint", nullable: false), - DefaultVendorId = table.Column(type: "bigint", nullable: true), - ItemType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - TrackingMode = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - TaxClass = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), - xmin = table.Column(type: "xid", rowVersion: true, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_items", x => x.ItemId); - table.ForeignKey( - name: "FK_items_categories_CategoryId", - column: x => x.CategoryId, - principalTable: "categories", - principalColumn: "CategoryId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_items_uoms_BaseUomId", - column: x => x.BaseUomId, - principalTable: "uoms", - principalColumn: "UomId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_items_vendors_DefaultVendorId", - column: x => x.DefaultVendorId, - principalTable: "vendors", - principalColumn: "VendorId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "bins", - columns: table => new - { - BinId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - WarehouseId = table.Column(type: "bigint", nullable: false), - Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - BinType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_bins", x => x.BinId); - table.ForeignKey( - name: "FK_bins_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "item_reorders", - columns: table => new - { - ReorderId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ItemId = table.Column(type: "bigint", nullable: false), - WarehouseId = table.Column(type: "bigint", nullable: false), - ReorderPoint = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - ReorderQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_item_reorders", x => x.ReorderId); - table.ForeignKey( - name: "FK_item_reorders_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_item_reorders_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "uom_conversions", - columns: table => new - { - ConversionId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ItemId = table.Column(type: "bigint", nullable: false), - FromUomId = table.Column(type: "bigint", nullable: false), - ToUomId = table.Column(type: "bigint", nullable: false), - Factor = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_uom_conversions", x => x.ConversionId); - table.ForeignKey( - name: "FK_uom_conversions_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_uom_conversions_uoms_FromUomId", - column: x => x.FromUomId, - principalTable: "uoms", - principalColumn: "UomId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_uom_conversions_uoms_ToUomId", - column: x => x.ToUomId, - principalTable: "uoms", - principalColumn: "UomId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_bins_WarehouseId_Code", - table: "bins", - columns: new[] { "WarehouseId", "Code" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_categories_ParentId", - table: "categories", - column: "ParentId"); - - migrationBuilder.CreateIndex( - name: "IX_item_reorders_ItemId_WarehouseId", - table: "item_reorders", - columns: new[] { "ItemId", "WarehouseId" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_item_reorders_WarehouseId", - table: "item_reorders", - column: "WarehouseId"); - - migrationBuilder.CreateIndex( - name: "IX_items_BaseUomId", - table: "items", - column: "BaseUomId"); - - migrationBuilder.CreateIndex( - name: "IX_items_CategoryId", - table: "items", - column: "CategoryId"); - - migrationBuilder.CreateIndex( - name: "IX_items_DefaultVendorId", - table: "items", - column: "DefaultVendorId"); - - migrationBuilder.CreateIndex( - name: "IX_items_Sku", - table: "items", - column: "Sku", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_items_Status", - table: "items", - column: "Status"); - - migrationBuilder.CreateIndex( - name: "IX_uom_conversions_FromUomId", - table: "uom_conversions", - column: "FromUomId"); - - migrationBuilder.CreateIndex( - name: "IX_uom_conversions_ItemId_FromUomId_ToUomId", - table: "uom_conversions", - columns: new[] { "ItemId", "FromUomId", "ToUomId" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_uom_conversions_ToUomId", - table: "uom_conversions", - column: "ToUomId"); - - migrationBuilder.CreateIndex( - name: "IX_uoms_Name", - table: "uoms", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_vendors_Code", - table: "vendors", - column: "Code", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_vendors_Status", - table: "vendors", - column: "Status"); - - migrationBuilder.CreateIndex( - name: "IX_warehouses_Code", - table: "warehouses", - column: "Code", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "bins"); - - migrationBuilder.DropTable( - name: "item_reorders"); - - migrationBuilder.DropTable( - name: "uom_conversions"); - - migrationBuilder.DropTable( - name: "warehouses"); - - migrationBuilder.DropTable( - name: "items"); - - migrationBuilder.DropTable( - name: "categories"); - - migrationBuilder.DropTable( - name: "uoms"); - - migrationBuilder.DropTable( - name: "vendors"); - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260709124415_initial.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260709124415_initial.Designer.cs deleted file mode 100644 index ee80b14..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260709124415_initial.Designer.cs +++ /dev/null @@ -1,445 +0,0 @@ -// -using System; -using ERPCore.Infra.Persistence; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - [DbContext(typeof(ErpDbContext))] - [Migration("20260709124415_initial")] - partial class initial - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.Property("BinId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); - - b.Property("BinType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("BinId"); - - b.HasIndex("WarehouseId", "Code") - .IsUnique(); - - b.ToTable("bins", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Property("CategoryId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.HasKey("CategoryId"); - - b.HasIndex("ParentId"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Property("ItemId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - - b.Property("BaseUomId") - .HasColumnType("bigint"); - - b.Property("CategoryId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultVendorId") - .HasColumnType("bigint"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxClass") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TrackingMode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("ItemId"); - - b.HasIndex("BaseUomId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("DefaultVendorId"); - - b.HasIndex("Sku") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("items", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.Property("ReorderId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ReorderPoint") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReorderQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReorderId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId") - .IsUnique(); - - b.ToTable("item_reorders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => - { - b.Property("UomId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("UomId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("uoms", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.Property("ConversionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); - - b.Property("Factor") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("FromUomId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ToUomId") - .HasColumnType("bigint"); - - b.HasKey("ConversionId"); - - b.HasIndex("FromUomId"); - - b.HasIndex("ToUomId"); - - b.HasIndex("ItemId", "FromUomId", "ToUomId") - .IsUnique(); - - b.ToTable("uom_conversions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => - { - b.Property("VendorId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Currency") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(3) - .HasColumnType("character varying(3)") - .HasDefaultValue("LKR"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxReg") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Terms") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("VendorId"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("vendors", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Property("WarehouseId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("WarehouseId"); - - b.HasIndex("Code") - .IsUnique(); - - b.ToTable("warehouses", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany("Bins") - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.HasOne("ERPCore.Domain.Entities.Category", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") - .WithMany() - .HasForeignKey("BaseUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") - .WithMany() - .HasForeignKey("DefaultVendorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("BaseUom"); - - b.Navigation("Category"); - - b.Navigation("DefaultVendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("ReorderSettings") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") - .WithMany() - .HasForeignKey("FromUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("UomConversions") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") - .WithMany() - .HasForeignKey("ToUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("FromUom"); - - b.Navigation("Item"); - - b.Navigation("ToUom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Navigation("ReorderSettings"); - - b.Navigation("UomConversions"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Navigation("Bins"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260709124415_initial.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260709124415_initial.cs deleted file mode 100644 index f83a4b3..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260709124415_initial.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - /// - public partial class initial : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260710090753_AddProcurement.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260710090753_AddProcurement.Designer.cs deleted file mode 100644 index 9ce865a..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260710090753_AddProcurement.Designer.cs +++ /dev/null @@ -1,987 +0,0 @@ -// -using System; -using ERPCore.Infra.Persistence; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - [DbContext(typeof(ErpDbContext))] - [Migration("20260710090753_AddProcurement")] - partial class AddProcurement - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.Property("BinId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); - - b.Property("BinType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("BinId"); - - b.HasIndex("WarehouseId", "Code") - .IsUnique(); - - b.ToTable("bins", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Property("CategoryId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.HasKey("CategoryId"); - - b.HasIndex("ParentId"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Property("ItemId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - - b.Property("BaseUomId") - .HasColumnType("bigint"); - - b.Property("CategoryId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultVendorId") - .HasColumnType("bigint"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxClass") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TrackingMode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("ItemId"); - - b.HasIndex("BaseUomId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("DefaultVendorId"); - - b.HasIndex("Sku") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("items", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.Property("ReorderId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ReorderPoint") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReorderQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReorderId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId") - .IsUnique(); - - b.ToTable("item_reorders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => - { - b.Property("SequenceId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); - - b.Property("DocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("doc_type"); - - b.Property("LastNumber") - .HasColumnType("bigint") - .HasColumnName("last_number"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("SequenceId"); - - b.HasIndex("DocType", "Year") - .IsUnique(); - - b.ToTable("number_sequences", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.Property("PoLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("Tax") - .HasPrecision(9, 4) - .HasColumnType("numeric(9,4)"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("PoLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoId"); - - b.HasIndex("UomId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("po_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Property("PoId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); - - b.Property("ApprovalRequired") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("PoId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.ToTable("purchase_orders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Property("RequisitionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequestedBy") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RequisitionId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequestedBy"); - - b.HasIndex("Status"); - - b.ToTable("requisitions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.Property("ReqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RequiredBy") - .HasColumnType("date"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.HasKey("ReqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RequisitionId"); - - b.ToTable("requisition_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Property("RfqId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RfqId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.ToTable("rfqs", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.Property("RfqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.HasKey("RfqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RfqId"); - - b.ToTable("rfq_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => - { - b.Property("UomId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("UomId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("uoms", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.Property("ConversionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); - - b.Property("Factor") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("FromUomId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ToUomId") - .HasColumnType("bigint"); - - b.HasKey("ConversionId"); - - b.HasIndex("FromUomId"); - - b.HasIndex("ToUomId"); - - b.HasIndex("ItemId", "FromUomId", "ToUomId") - .IsUnique(); - - b.ToTable("uom_conversions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.User", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Username") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.HasKey("UserId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users", (string)null); - - b.HasData( - new - { - UserId = 1L, - DisplayName = "System", - Status = "Active", - Username = "system" - }); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => - { - b.Property("VendorId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Currency") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(3) - .HasColumnType("character varying(3)") - .HasDefaultValue("LKR"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxReg") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Terms") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("VendorId"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("vendors", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Property("QuotationId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("QuotationId"); - - b.HasIndex("VendorId"); - - b.HasIndex("RfqId", "VendorId") - .IsUnique(); - - b.ToTable("vendor_quotations", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.Property("QuotationLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("LeadDays") - .HasColumnType("integer"); - - b.Property("QuotationId") - .HasColumnType("bigint"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.HasKey("QuotationLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("QuotationId"); - - b.ToTable("vendor_quotation_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Property("WarehouseId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("WarehouseId"); - - b.HasIndex("Code") - .IsUnique(); - - b.ToTable("warehouses", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany("Bins") - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.HasOne("ERPCore.Domain.Entities.Category", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") - .WithMany() - .HasForeignKey("BaseUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") - .WithMany() - .HasForeignKey("DefaultVendorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("BaseUom"); - - b.Navigation("Category"); - - b.Navigation("DefaultVendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("ReorderSettings") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany("Lines") - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Uom"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("Requisition"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Requester") - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requester"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany("Lines") - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Lines") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Rfq"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") - .WithMany() - .HasForeignKey("FromUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("UomConversions") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") - .WithMany() - .HasForeignKey("ToUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("FromUom"); - - b.Navigation("Item"); - - b.Navigation("ToUom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Quotations") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Rfq"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") - .WithMany("Lines") - .HasForeignKey("QuotationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Quotation"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Navigation("ReorderSettings"); - - b.Navigation("UomConversions"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Navigation("Lines"); - - b.Navigation("Quotations"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Navigation("Bins"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260710090753_AddProcurement.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260710090753_AddProcurement.cs deleted file mode 100644 index ee2d903..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260710090753_AddProcurement.cs +++ /dev/null @@ -1,448 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - /// - public partial class AddProcurement : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "number_sequences", - columns: table => new - { - SequenceId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - doc_type = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), - year = table.Column(type: "integer", nullable: false), - last_number = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_number_sequences", x => x.SequenceId); - }); - - migrationBuilder.CreateTable( - name: "users", - columns: table => new - { - UserId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Username = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_users", x => x.UserId); - }); - - migrationBuilder.CreateTable( - name: "requisitions", - columns: table => new - { - RequisitionId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - RequestedBy = table.Column(type: "bigint", nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_requisitions", x => x.RequisitionId); - table.ForeignKey( - name: "FK_requisitions_users_RequestedBy", - column: x => x.RequestedBy, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "purchase_orders", - columns: table => new - { - PoId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - VendorId = table.Column(type: "bigint", nullable: false), - RequisitionId = table.Column(type: "bigint", nullable: true), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - ApprovalRequired = table.Column(type: "boolean", nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), - xmin = table.Column(type: "xid", rowVersion: true, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_purchase_orders", x => x.PoId); - table.ForeignKey( - name: "FK_purchase_orders_requisitions_RequisitionId", - column: x => x.RequisitionId, - principalTable: "requisitions", - principalColumn: "RequisitionId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_purchase_orders_users_CreatedBy", - column: x => x.CreatedBy, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_purchase_orders_vendors_VendorId", - column: x => x.VendorId, - principalTable: "vendors", - principalColumn: "VendorId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "requisition_lines", - columns: table => new - { - ReqLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RequisitionId = table.Column(type: "bigint", nullable: false), - ItemId = table.Column(type: "bigint", nullable: false), - Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - RequiredBy = table.Column(type: "date", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_requisition_lines", x => x.ReqLineId); - table.ForeignKey( - name: "FK_requisition_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_requisition_lines_requisitions_RequisitionId", - column: x => x.RequisitionId, - principalTable: "requisitions", - principalColumn: "RequisitionId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "rfqs", - columns: table => new - { - RfqId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - RequisitionId = table.Column(type: "bigint", nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_rfqs", x => x.RfqId); - table.ForeignKey( - name: "FK_rfqs_requisitions_RequisitionId", - column: x => x.RequisitionId, - principalTable: "requisitions", - principalColumn: "RequisitionId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "po_lines", - columns: table => new - { - PoLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - PoId = table.Column(type: "bigint", nullable: false), - ItemId = table.Column(type: "bigint", nullable: false), - UomId = table.Column(type: "bigint", nullable: false), - WarehouseId = table.Column(type: "bigint", nullable: false), - Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - UnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - Tax = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), - QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_po_lines", x => x.PoLineId); - table.ForeignKey( - name: "FK_po_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_po_lines_purchase_orders_PoId", - column: x => x.PoId, - principalTable: "purchase_orders", - principalColumn: "PoId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_po_lines_uoms_UomId", - column: x => x.UomId, - principalTable: "uoms", - principalColumn: "UomId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_po_lines_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "rfq_lines", - columns: table => new - { - RfqLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RfqId = table.Column(type: "bigint", nullable: false), - ItemId = table.Column(type: "bigint", nullable: false), - Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_rfq_lines", x => x.RfqLineId); - table.ForeignKey( - name: "FK_rfq_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_rfq_lines_rfqs_RfqId", - column: x => x.RfqId, - principalTable: "rfqs", - principalColumn: "RfqId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "vendor_quotations", - columns: table => new - { - QuotationId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RfqId = table.Column(type: "bigint", nullable: false), - VendorId = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_vendor_quotations", x => x.QuotationId); - table.ForeignKey( - name: "FK_vendor_quotations_rfqs_RfqId", - column: x => x.RfqId, - principalTable: "rfqs", - principalColumn: "RfqId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_vendor_quotations_vendors_VendorId", - column: x => x.VendorId, - principalTable: "vendors", - principalColumn: "VendorId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "vendor_quotation_lines", - columns: table => new - { - QuotationLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - QuotationId = table.Column(type: "bigint", nullable: false), - ItemId = table.Column(type: "bigint", nullable: false), - UnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - LeadDays = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_vendor_quotation_lines", x => x.QuotationLineId); - table.ForeignKey( - name: "FK_vendor_quotation_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_vendor_quotation_lines_vendor_quotations_QuotationId", - column: x => x.QuotationId, - principalTable: "vendor_quotations", - principalColumn: "QuotationId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.InsertData( - table: "users", - columns: new[] { "UserId", "DisplayName", "Status", "Username" }, - values: new object[] { 1L, "System", "Active", "system" }); - - migrationBuilder.CreateIndex( - name: "IX_number_sequences_doc_type_year", - table: "number_sequences", - columns: new[] { "doc_type", "year" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_po_lines_ItemId", - table: "po_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_po_lines_PoId", - table: "po_lines", - column: "PoId"); - - migrationBuilder.CreateIndex( - name: "IX_po_lines_UomId", - table: "po_lines", - column: "UomId"); - - migrationBuilder.CreateIndex( - name: "IX_po_lines_WarehouseId", - table: "po_lines", - column: "WarehouseId"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_orders_CreatedBy", - table: "purchase_orders", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_orders_DocNo", - table: "purchase_orders", - column: "DocNo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_purchase_orders_RequisitionId", - table: "purchase_orders", - column: "RequisitionId"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_orders_Status", - table: "purchase_orders", - column: "Status"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_orders_VendorId", - table: "purchase_orders", - column: "VendorId"); - - migrationBuilder.CreateIndex( - name: "IX_requisition_lines_ItemId", - table: "requisition_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_requisition_lines_RequisitionId", - table: "requisition_lines", - column: "RequisitionId"); - - migrationBuilder.CreateIndex( - name: "IX_requisitions_DocNo", - table: "requisitions", - column: "DocNo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_requisitions_RequestedBy", - table: "requisitions", - column: "RequestedBy"); - - migrationBuilder.CreateIndex( - name: "IX_requisitions_Status", - table: "requisitions", - column: "Status"); - - migrationBuilder.CreateIndex( - name: "IX_rfq_lines_ItemId", - table: "rfq_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_rfq_lines_RfqId", - table: "rfq_lines", - column: "RfqId"); - - migrationBuilder.CreateIndex( - name: "IX_rfqs_DocNo", - table: "rfqs", - column: "DocNo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_rfqs_RequisitionId", - table: "rfqs", - column: "RequisitionId"); - - migrationBuilder.CreateIndex( - name: "IX_users_Username", - table: "users", - column: "Username", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_vendor_quotation_lines_ItemId", - table: "vendor_quotation_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_vendor_quotation_lines_QuotationId", - table: "vendor_quotation_lines", - column: "QuotationId"); - - migrationBuilder.CreateIndex( - name: "IX_vendor_quotations_RfqId_VendorId", - table: "vendor_quotations", - columns: new[] { "RfqId", "VendorId" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_vendor_quotations_VendorId", - table: "vendor_quotations", - column: "VendorId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "number_sequences"); - - migrationBuilder.DropTable( - name: "po_lines"); - - migrationBuilder.DropTable( - name: "requisition_lines"); - - migrationBuilder.DropTable( - name: "rfq_lines"); - - migrationBuilder.DropTable( - name: "vendor_quotation_lines"); - - migrationBuilder.DropTable( - name: "purchase_orders"); - - migrationBuilder.DropTable( - name: "vendor_quotations"); - - migrationBuilder.DropTable( - name: "rfqs"); - - migrationBuilder.DropTable( - name: "requisitions"); - - migrationBuilder.DropTable( - name: "users"); - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.Designer.cs deleted file mode 100644 index d10be22..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.Designer.cs +++ /dev/null @@ -1,1480 +0,0 @@ -// -using System; -using ERPCore.Infra.Persistence; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - [DbContext(typeof(ErpDbContext))] - [Migration("20260713054124_AddStockAndGrn")] - partial class AddStockAndGrn - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => - { - b.Property("BatchId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); - - b.Property("BatchNo") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ExpiryDate") - .HasColumnType("date"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.HasKey("BatchId"); - - b.HasIndex("ItemId", "BatchNo") - .IsUnique(); - - b.ToTable("batches", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.Property("BinId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); - - b.Property("BinType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("BinId"); - - b.HasIndex("WarehouseId", "Code") - .IsUnique(); - - b.ToTable("bins", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Property("CategoryId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.HasKey("CategoryId"); - - b.HasIndex("ParentId"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.Property("GrnId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("PostedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("GrnId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("PoId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("grns", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => - { - b.Property("GrnLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("GrnId") - .HasColumnType("bigint"); - - b.Property("HoldStatus") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoLineId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReceivedValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.HasKey("GrnLineId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("GrnId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoLineId"); - - b.HasIndex("UomId"); - - b.ToTable("grn_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Property("ItemId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - - b.Property("BaseUomId") - .HasColumnType("bigint"); - - b.Property("CategoryId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultVendorId") - .HasColumnType("bigint"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxClass") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TrackingMode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("ItemId"); - - b.HasIndex("BaseUomId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("DefaultVendorId"); - - b.HasIndex("Sku") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("items", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.Property("ReorderId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ReorderPoint") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReorderQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReorderId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId") - .IsUnique(); - - b.ToTable("item_reorders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => - { - b.Property("SequenceId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); - - b.Property("DocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("doc_type"); - - b.Property("LastNumber") - .HasColumnType("bigint") - .HasColumnName("last_number"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("SequenceId"); - - b.HasIndex("DocType", "Year") - .IsUnique(); - - b.ToTable("number_sequences", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.Property("PoLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("Tax") - .HasPrecision(9, 4) - .HasColumnType("numeric(9,4)"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("PoLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoId"); - - b.HasIndex("UomId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("po_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Property("PoId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); - - b.Property("ApprovalRequired") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("PoId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.ToTable("purchase_orders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Property("RequisitionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequestedBy") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RequisitionId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequestedBy"); - - b.HasIndex("Status"); - - b.ToTable("requisitions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.Property("ReqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RequiredBy") - .HasColumnType("date"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.HasKey("ReqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RequisitionId"); - - b.ToTable("requisition_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Property("RfqId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RfqId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.ToTable("rfqs", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.Property("RfqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.HasKey("RfqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RfqId"); - - b.ToTable("rfq_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => - { - b.Property("SerialId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("SerialNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("SerialId"); - - b.HasIndex("ItemId", "SerialNo") - .IsUnique(); - - b.ToTable("serials", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => - { - b.Property("LayerId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("GrnLineId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyRemaining") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReceiptDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("LayerId"); - - b.HasIndex("BatchId"); - - b.HasIndex("GrnLineId"); - - b.HasIndex("SerialId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); - - b.ToTable("stock_layers", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => - { - b.Property("LedgerId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Direction") - .IsRequired() - .HasMaxLength(5) - .HasColumnType("character varying(5)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyBase") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RunningBalance") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("SourceDocId") - .HasColumnType("bigint"); - - b.Property("SourceDocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("Value") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("LedgerId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("SerialId"); - - b.HasIndex("UserId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("SourceDocType", "SourceDocId"); - - b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); - - b.HasIndex("ItemId", "WarehouseId", "LedgerId"); - - b.ToTable("stock_ledger", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => - { - b.Property("UomId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("UomId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("uoms", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.Property("ConversionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); - - b.Property("Factor") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("FromUomId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ToUomId") - .HasColumnType("bigint"); - - b.HasKey("ConversionId"); - - b.HasIndex("FromUomId"); - - b.HasIndex("ToUomId"); - - b.HasIndex("ItemId", "FromUomId", "ToUomId") - .IsUnique(); - - b.ToTable("uom_conversions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.User", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Username") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.HasKey("UserId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users", (string)null); - - b.HasData( - new - { - UserId = 1L, - DisplayName = "System", - Status = "Active", - Username = "system" - }); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => - { - b.Property("VendorId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Currency") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(3) - .HasColumnType("character varying(3)") - .HasDefaultValue("LKR"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxReg") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Terms") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("VendorId"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("vendors", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Property("QuotationId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("QuotationId"); - - b.HasIndex("VendorId"); - - b.HasIndex("RfqId", "VendorId") - .IsUnique(); - - b.ToTable("vendor_quotations", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.Property("QuotationLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("LeadDays") - .HasColumnType("integer"); - - b.Property("QuotationId") - .HasColumnType("bigint"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.HasKey("QuotationLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("QuotationId"); - - b.ToTable("vendor_quotation_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Property("WarehouseId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("WarehouseId"); - - b.HasIndex("Code") - .IsUnique(); - - b.ToTable("warehouses", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany("Bins") - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.HasOne("ERPCore.Domain.Entities.Category", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany() - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Vendor"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") - .WithMany("Lines") - .HasForeignKey("GrnId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") - .WithMany() - .HasForeignKey("PoLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Batch"); - - b.Navigation("Bin"); - - b.Navigation("Grn"); - - b.Navigation("Item"); - - b.Navigation("PoLine"); - - b.Navigation("Uom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") - .WithMany() - .HasForeignKey("BaseUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") - .WithMany() - .HasForeignKey("DefaultVendorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("BaseUom"); - - b.Navigation("Category"); - - b.Navigation("DefaultVendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("ReorderSettings") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany("Lines") - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Uom"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("Requisition"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Requester") - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requester"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany("Lines") - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Lines") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Rfq"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") - .WithMany() - .HasForeignKey("GrnLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Batch"); - - b.Navigation("GrnLine"); - - b.Navigation("Item"); - - b.Navigation("Serial"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", null) - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", null) - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") - .WithMany() - .HasForeignKey("FromUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("UomConversions") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") - .WithMany() - .HasForeignKey("ToUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("FromUom"); - - b.Navigation("Item"); - - b.Navigation("ToUom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Quotations") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Rfq"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") - .WithMany("Lines") - .HasForeignKey("QuotationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Quotation"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Navigation("ReorderSettings"); - - b.Navigation("UomConversions"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Navigation("Lines"); - - b.Navigation("Quotations"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Navigation("Bins"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.cs deleted file mode 100644 index a43fc8c..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260713054124_AddStockAndGrn.cs +++ /dev/null @@ -1,434 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - /// - public partial class AddStockAndGrn : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "batches", - columns: table => new - { - BatchId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ItemId = table.Column(type: "bigint", nullable: false), - BatchNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - ExpiryDate = table.Column(type: "date", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_batches", x => x.BatchId); - table.ForeignKey( - name: "FK_batches_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "grns", - columns: table => new - { - GrnId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - PoId = table.Column(type: "bigint", nullable: true), - VendorId = table.Column(type: "bigint", nullable: false), - WarehouseId = table.Column(type: "bigint", nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - PostedAt = table.Column(type: "timestamp with time zone", nullable: true), - xmin = table.Column(type: "xid", rowVersion: true, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_grns", x => x.GrnId); - table.ForeignKey( - name: "FK_grns_purchase_orders_PoId", - column: x => x.PoId, - principalTable: "purchase_orders", - principalColumn: "PoId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_grns_users_CreatedBy", - column: x => x.CreatedBy, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_grns_vendors_VendorId", - column: x => x.VendorId, - principalTable: "vendors", - principalColumn: "VendorId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_grns_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "serials", - columns: table => new - { - SerialId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ItemId = table.Column(type: "bigint", nullable: false), - SerialNo = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_serials", x => x.SerialId); - table.ForeignKey( - name: "FK_serials_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "grn_lines", - columns: table => new - { - GrnLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - GrnId = table.Column(type: "bigint", nullable: false), - PoLineId = table.Column(type: "bigint", nullable: true), - ItemId = table.Column(type: "bigint", nullable: false), - UomId = table.Column(type: "bigint", nullable: false), - BinId = table.Column(type: "bigint", nullable: true), - BatchId = table.Column(type: "bigint", nullable: true), - Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), - ReceivedValue = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - HoldStatus = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_grn_lines", x => x.GrnLineId); - table.ForeignKey( - name: "FK_grn_lines_batches_BatchId", - column: x => x.BatchId, - principalTable: "batches", - principalColumn: "BatchId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_grn_lines_bins_BinId", - column: x => x.BinId, - principalTable: "bins", - principalColumn: "BinId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_grn_lines_grns_GrnId", - column: x => x.GrnId, - principalTable: "grns", - principalColumn: "GrnId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_grn_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_grn_lines_po_lines_PoLineId", - column: x => x.PoLineId, - principalTable: "po_lines", - principalColumn: "PoLineId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_grn_lines_uoms_UomId", - column: x => x.UomId, - principalTable: "uoms", - principalColumn: "UomId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "stock_ledger", - columns: table => new - { - LedgerId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ItemId = table.Column(type: "bigint", nullable: false), - WarehouseId = table.Column(type: "bigint", nullable: false), - BinId = table.Column(type: "bigint", nullable: true), - BatchId = table.Column(type: "bigint", nullable: true), - SerialId = table.Column(type: "bigint", nullable: true), - UserId = table.Column(type: "bigint", nullable: false), - Direction = table.Column(type: "character varying(5)", maxLength: 5, nullable: false), - QtyBase = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), - Value = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - RunningBalance = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - SourceDocType = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), - SourceDocId = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_stock_ledger", x => x.LedgerId); - table.ForeignKey( - name: "FK_stock_ledger_batches_BatchId", - column: x => x.BatchId, - principalTable: "batches", - principalColumn: "BatchId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_ledger_bins_BinId", - column: x => x.BinId, - principalTable: "bins", - principalColumn: "BinId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_ledger_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_ledger_serials_SerialId", - column: x => x.SerialId, - principalTable: "serials", - principalColumn: "SerialId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_ledger_users_UserId", - column: x => x.UserId, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_ledger_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "stock_layers", - columns: table => new - { - LayerId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ItemId = table.Column(type: "bigint", nullable: false), - WarehouseId = table.Column(type: "bigint", nullable: false), - BatchId = table.Column(type: "bigint", nullable: true), - SerialId = table.Column(type: "bigint", nullable: true), - GrnLineId = table.Column(type: "bigint", nullable: true), - QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - QtyRemaining = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), - ReceiptDate = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_stock_layers", x => x.LayerId); - table.ForeignKey( - name: "FK_stock_layers_batches_BatchId", - column: x => x.BatchId, - principalTable: "batches", - principalColumn: "BatchId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_layers_grn_lines_GrnLineId", - column: x => x.GrnLineId, - principalTable: "grn_lines", - principalColumn: "GrnLineId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_layers_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_layers_serials_SerialId", - column: x => x.SerialId, - principalTable: "serials", - principalColumn: "SerialId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_layers_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_batches_ItemId_BatchNo", - table: "batches", - columns: new[] { "ItemId", "BatchNo" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_grn_lines_BatchId", - table: "grn_lines", - column: "BatchId"); - - migrationBuilder.CreateIndex( - name: "IX_grn_lines_BinId", - table: "grn_lines", - column: "BinId"); - - migrationBuilder.CreateIndex( - name: "IX_grn_lines_GrnId", - table: "grn_lines", - column: "GrnId"); - - migrationBuilder.CreateIndex( - name: "IX_grn_lines_ItemId", - table: "grn_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_grn_lines_PoLineId", - table: "grn_lines", - column: "PoLineId"); - - migrationBuilder.CreateIndex( - name: "IX_grn_lines_UomId", - table: "grn_lines", - column: "UomId"); - - migrationBuilder.CreateIndex( - name: "IX_grns_CreatedBy", - table: "grns", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_grns_DocNo", - table: "grns", - column: "DocNo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_grns_PoId", - table: "grns", - column: "PoId"); - - migrationBuilder.CreateIndex( - name: "IX_grns_Status", - table: "grns", - column: "Status"); - - migrationBuilder.CreateIndex( - name: "IX_grns_VendorId", - table: "grns", - column: "VendorId"); - - migrationBuilder.CreateIndex( - name: "IX_grns_WarehouseId", - table: "grns", - column: "WarehouseId"); - - migrationBuilder.CreateIndex( - name: "IX_serials_ItemId_SerialNo", - table: "serials", - columns: new[] { "ItemId", "SerialNo" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_stock_layers_BatchId", - table: "stock_layers", - column: "BatchId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_layers_GrnLineId", - table: "stock_layers", - column: "GrnLineId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_layers_ItemId_WarehouseId_ReceiptDate_LayerId", - table: "stock_layers", - columns: new[] { "ItemId", "WarehouseId", "ReceiptDate", "LayerId" }); - - migrationBuilder.CreateIndex( - name: "IX_stock_layers_SerialId", - table: "stock_layers", - column: "SerialId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_layers_WarehouseId", - table: "stock_layers", - column: "WarehouseId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_ledger_BatchId", - table: "stock_ledger", - column: "BatchId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_ledger_BinId", - table: "stock_ledger", - column: "BinId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_ledger_ItemId_WarehouseId_CreatedAt", - table: "stock_ledger", - columns: new[] { "ItemId", "WarehouseId", "CreatedAt" }); - - migrationBuilder.CreateIndex( - name: "IX_stock_ledger_ItemId_WarehouseId_LedgerId", - table: "stock_ledger", - columns: new[] { "ItemId", "WarehouseId", "LedgerId" }); - - migrationBuilder.CreateIndex( - name: "IX_stock_ledger_SerialId", - table: "stock_ledger", - column: "SerialId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_ledger_SourceDocType_SourceDocId", - table: "stock_ledger", - columns: new[] { "SourceDocType", "SourceDocId" }); - - migrationBuilder.CreateIndex( - name: "IX_stock_ledger_UserId", - table: "stock_ledger", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_ledger_WarehouseId", - table: "stock_ledger", - column: "WarehouseId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "stock_layers"); - - migrationBuilder.DropTable( - name: "stock_ledger"); - - migrationBuilder.DropTable( - name: "grn_lines"); - - migrationBuilder.DropTable( - name: "serials"); - - migrationBuilder.DropTable( - name: "batches"); - - migrationBuilder.DropTable( - name: "grns"); - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.Designer.cs deleted file mode 100644 index ff45314..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.Designer.cs +++ /dev/null @@ -1,1847 +0,0 @@ -// -using System; -using ERPCore.Infra.Persistence; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - [DbContext(typeof(ErpDbContext))] - [Migration("20260713061141_AddStockTransactions")] - partial class AddStockTransactions - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => - { - b.Property("BatchId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); - - b.Property("BatchNo") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ExpiryDate") - .HasColumnType("date"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.HasKey("BatchId"); - - b.HasIndex("ItemId", "BatchNo") - .IsUnique(); - - b.ToTable("batches", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.Property("BinId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); - - b.Property("BinType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("BinId"); - - b.HasIndex("WarehouseId", "Code") - .IsUnique(); - - b.ToTable("bins", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Property("CategoryId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.HasKey("CategoryId"); - - b.HasIndex("ParentId"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.Property("GrnId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("PostedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("GrnId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("PoId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("grns", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => - { - b.Property("GrnLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("GrnId") - .HasColumnType("bigint"); - - b.Property("HoldStatus") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoLineId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReceivedValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.HasKey("GrnLineId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("GrnId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoLineId"); - - b.HasIndex("UomId"); - - b.ToTable("grn_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Property("ItemId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - - b.Property("BaseUomId") - .HasColumnType("bigint"); - - b.Property("CategoryId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultVendorId") - .HasColumnType("bigint"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxClass") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TrackingMode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("ItemId"); - - b.HasIndex("BaseUomId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("DefaultVendorId"); - - b.HasIndex("Sku") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("items", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.Property("ReorderId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ReorderPoint") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReorderQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReorderId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId") - .IsUnique(); - - b.ToTable("item_reorders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => - { - b.Property("SequenceId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); - - b.Property("DocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("doc_type"); - - b.Property("LastNumber") - .HasColumnType("bigint") - .HasColumnName("last_number"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("SequenceId"); - - b.HasIndex("DocType", "Year") - .IsUnique(); - - b.ToTable("number_sequences", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.Property("PoLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("Tax") - .HasPrecision(9, 4) - .HasColumnType("numeric(9,4)"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("PoLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoId"); - - b.HasIndex("UomId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("po_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Property("PoId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); - - b.Property("ApprovalRequired") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("PoId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.ToTable("purchase_orders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => - { - b.Property("ReasonCodeId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Context") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("ReasonCodeId"); - - b.HasIndex("Context", "Code") - .IsUnique(); - - b.ToTable("reason_codes", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Property("RequisitionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequestedBy") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RequisitionId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequestedBy"); - - b.HasIndex("Status"); - - b.ToTable("requisitions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.Property("ReqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RequiredBy") - .HasColumnType("date"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.HasKey("ReqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RequisitionId"); - - b.ToTable("requisition_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Property("RfqId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RfqId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.ToTable("rfqs", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.Property("RfqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.HasKey("RfqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RfqId"); - - b.ToTable("rfq_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => - { - b.Property("SerialId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("SerialNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("SerialId"); - - b.HasIndex("ItemId", "SerialNo") - .IsUnique(); - - b.ToTable("serials", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.Property("AdjustmentId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("ReasonCodeId") - .HasColumnType("bigint"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("AdjustmentId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("ReasonCodeId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("stock_adjustments", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => - { - b.Property("AdjLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); - - b.Property("AdjustmentId") - .HasColumnType("bigint"); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyDelta") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.HasKey("AdjLineId"); - - b.HasIndex("AdjustmentId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("ItemId"); - - b.HasIndex("SerialId"); - - b.ToTable("stock_adjustment_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => - { - b.Property("LayerId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("GrnLineId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyRemaining") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReceiptDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("LayerId"); - - b.HasIndex("BatchId"); - - b.HasIndex("GrnLineId"); - - b.HasIndex("SerialId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); - - b.ToTable("stock_layers", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => - { - b.Property("LedgerId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Direction") - .IsRequired() - .HasMaxLength(5) - .HasColumnType("character varying(5)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyBase") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RunningBalance") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("SourceDocId") - .HasColumnType("bigint"); - - b.Property("SourceDocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("Value") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("LedgerId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("SerialId"); - - b.HasIndex("UserId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("SourceDocType", "SourceDocId"); - - b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); - - b.HasIndex("ItemId", "WarehouseId", "LedgerId"); - - b.ToTable("stock_ledger", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.Property("TransferId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DestWarehouseId") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("SrcWarehouseId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("TransferId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DestWarehouseId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("SrcWarehouseId"); - - b.HasIndex("Status"); - - b.ToTable("stock_transfers", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => - { - b.Property("TransferLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("DestBinId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("SrcBinId") - .HasColumnType("bigint"); - - b.Property("TransferId") - .HasColumnType("bigint"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.HasKey("TransferLineId"); - - b.HasIndex("BatchId"); - - b.HasIndex("DestBinId"); - - b.HasIndex("ItemId"); - - b.HasIndex("SerialId"); - - b.HasIndex("SrcBinId"); - - b.HasIndex("TransferId"); - - b.ToTable("stock_transfer_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => - { - b.Property("UomId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("UomId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("uoms", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.Property("ConversionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); - - b.Property("Factor") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("FromUomId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ToUomId") - .HasColumnType("bigint"); - - b.HasKey("ConversionId"); - - b.HasIndex("FromUomId"); - - b.HasIndex("ToUomId"); - - b.HasIndex("ItemId", "FromUomId", "ToUomId") - .IsUnique(); - - b.ToTable("uom_conversions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.User", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Username") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.HasKey("UserId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users", (string)null); - - b.HasData( - new - { - UserId = 1L, - DisplayName = "System", - Status = "Active", - Username = "system" - }); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => - { - b.Property("VendorId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Currency") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(3) - .HasColumnType("character varying(3)") - .HasDefaultValue("LKR"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxReg") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Terms") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("VendorId"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("vendors", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Property("QuotationId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("QuotationId"); - - b.HasIndex("VendorId"); - - b.HasIndex("RfqId", "VendorId") - .IsUnique(); - - b.ToTable("vendor_quotations", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.Property("QuotationLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("LeadDays") - .HasColumnType("integer"); - - b.Property("QuotationId") - .HasColumnType("bigint"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.HasKey("QuotationLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("QuotationId"); - - b.ToTable("vendor_quotation_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Property("WarehouseId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("WarehouseId"); - - b.HasIndex("Code") - .IsUnique(); - - b.ToTable("warehouses", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany("Bins") - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.HasOne("ERPCore.Domain.Entities.Category", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany() - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Vendor"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") - .WithMany("Lines") - .HasForeignKey("GrnId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") - .WithMany() - .HasForeignKey("PoLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Batch"); - - b.Navigation("Bin"); - - b.Navigation("Grn"); - - b.Navigation("Item"); - - b.Navigation("PoLine"); - - b.Navigation("Uom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") - .WithMany() - .HasForeignKey("BaseUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") - .WithMany() - .HasForeignKey("DefaultVendorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("BaseUom"); - - b.Navigation("Category"); - - b.Navigation("DefaultVendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("ReorderSettings") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany("Lines") - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Uom"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("Requisition"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Requester") - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requester"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany("Lines") - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Lines") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Rfq"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") - .WithMany() - .HasForeignKey("ReasonCodeId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("ReasonCode"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => - { - b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") - .WithMany("Lines") - .HasForeignKey("AdjustmentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Adjustment"); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") - .WithMany() - .HasForeignKey("GrnLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Batch"); - - b.Navigation("GrnLine"); - - b.Navigation("Item"); - - b.Navigation("Serial"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", null) - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", null) - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") - .WithMany() - .HasForeignKey("DestWarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") - .WithMany() - .HasForeignKey("SrcWarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("DestWarehouse"); - - b.Navigation("SrcWarehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("DestBinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("SrcBinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") - .WithMany("Lines") - .HasForeignKey("TransferId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Transfer"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") - .WithMany() - .HasForeignKey("FromUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("UomConversions") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") - .WithMany() - .HasForeignKey("ToUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("FromUom"); - - b.Navigation("Item"); - - b.Navigation("ToUom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Quotations") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Rfq"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") - .WithMany("Lines") - .HasForeignKey("QuotationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Quotation"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Navigation("ReorderSettings"); - - b.Navigation("UomConversions"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Navigation("Lines"); - - b.Navigation("Quotations"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Navigation("Bins"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.cs deleted file mode 100644 index 08a79bd..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260713061141_AddStockTransactions.cs +++ /dev/null @@ -1,337 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - /// - public partial class AddStockTransactions : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "reason_codes", - columns: table => new - { - ReasonCodeId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - Context = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_reason_codes", x => x.ReasonCodeId); - }); - - migrationBuilder.CreateTable( - name: "stock_transfers", - columns: table => new - { - TransferId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - SrcWarehouseId = table.Column(type: "bigint", nullable: false), - DestWarehouseId = table.Column(type: "bigint", nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - xmin = table.Column(type: "xid", rowVersion: true, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_stock_transfers", x => x.TransferId); - table.ForeignKey( - name: "FK_stock_transfers_users_CreatedBy", - column: x => x.CreatedBy, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_transfers_warehouses_DestWarehouseId", - column: x => x.DestWarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_transfers_warehouses_SrcWarehouseId", - column: x => x.SrcWarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "stock_adjustments", - columns: table => new - { - AdjustmentId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - WarehouseId = table.Column(type: "bigint", nullable: false), - ReasonCodeId = table.Column(type: "bigint", nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - xmin = table.Column(type: "xid", rowVersion: true, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_stock_adjustments", x => x.AdjustmentId); - table.ForeignKey( - name: "FK_stock_adjustments_reason_codes_ReasonCodeId", - column: x => x.ReasonCodeId, - principalTable: "reason_codes", - principalColumn: "ReasonCodeId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_adjustments_users_CreatedBy", - column: x => x.CreatedBy, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_adjustments_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "stock_transfer_lines", - columns: table => new - { - TransferLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - TransferId = table.Column(type: "bigint", nullable: false), - ItemId = table.Column(type: "bigint", nullable: false), - SrcBinId = table.Column(type: "bigint", nullable: true), - DestBinId = table.Column(type: "bigint", nullable: true), - BatchId = table.Column(type: "bigint", nullable: true), - SerialId = table.Column(type: "bigint", nullable: true), - Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: true), - QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_stock_transfer_lines", x => x.TransferLineId); - table.ForeignKey( - name: "FK_stock_transfer_lines_batches_BatchId", - column: x => x.BatchId, - principalTable: "batches", - principalColumn: "BatchId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_transfer_lines_bins_DestBinId", - column: x => x.DestBinId, - principalTable: "bins", - principalColumn: "BinId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_transfer_lines_bins_SrcBinId", - column: x => x.SrcBinId, - principalTable: "bins", - principalColumn: "BinId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_transfer_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_transfer_lines_serials_SerialId", - column: x => x.SerialId, - principalTable: "serials", - principalColumn: "SerialId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_transfer_lines_stock_transfers_TransferId", - column: x => x.TransferId, - principalTable: "stock_transfers", - principalColumn: "TransferId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "stock_adjustment_lines", - columns: table => new - { - AdjLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - AdjustmentId = table.Column(type: "bigint", nullable: false), - ItemId = table.Column(type: "bigint", nullable: false), - BinId = table.Column(type: "bigint", nullable: true), - BatchId = table.Column(type: "bigint", nullable: true), - SerialId = table.Column(type: "bigint", nullable: true), - QtyDelta = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_stock_adjustment_lines", x => x.AdjLineId); - table.ForeignKey( - name: "FK_stock_adjustment_lines_batches_BatchId", - column: x => x.BatchId, - principalTable: "batches", - principalColumn: "BatchId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_adjustment_lines_bins_BinId", - column: x => x.BinId, - principalTable: "bins", - principalColumn: "BinId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_adjustment_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_adjustment_lines_serials_SerialId", - column: x => x.SerialId, - principalTable: "serials", - principalColumn: "SerialId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_adjustment_lines_stock_adjustments_AdjustmentId", - column: x => x.AdjustmentId, - principalTable: "stock_adjustments", - principalColumn: "AdjustmentId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_reason_codes_Context_Code", - table: "reason_codes", - columns: new[] { "Context", "Code" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustment_lines_AdjustmentId", - table: "stock_adjustment_lines", - column: "AdjustmentId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustment_lines_BatchId", - table: "stock_adjustment_lines", - column: "BatchId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustment_lines_BinId", - table: "stock_adjustment_lines", - column: "BinId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustment_lines_ItemId", - table: "stock_adjustment_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustment_lines_SerialId", - table: "stock_adjustment_lines", - column: "SerialId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustments_CreatedBy", - table: "stock_adjustments", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustments_DocNo", - table: "stock_adjustments", - column: "DocNo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustments_ReasonCodeId", - table: "stock_adjustments", - column: "ReasonCodeId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_adjustments_WarehouseId", - table: "stock_adjustments", - column: "WarehouseId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfer_lines_BatchId", - table: "stock_transfer_lines", - column: "BatchId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfer_lines_DestBinId", - table: "stock_transfer_lines", - column: "DestBinId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfer_lines_ItemId", - table: "stock_transfer_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfer_lines_SerialId", - table: "stock_transfer_lines", - column: "SerialId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfer_lines_SrcBinId", - table: "stock_transfer_lines", - column: "SrcBinId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfer_lines_TransferId", - table: "stock_transfer_lines", - column: "TransferId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfers_CreatedBy", - table: "stock_transfers", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfers_DestWarehouseId", - table: "stock_transfers", - column: "DestWarehouseId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfers_DocNo", - table: "stock_transfers", - column: "DocNo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfers_SrcWarehouseId", - table: "stock_transfers", - column: "SrcWarehouseId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_transfers_Status", - table: "stock_transfers", - column: "Status"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "stock_adjustment_lines"); - - migrationBuilder.DropTable( - name: "stock_transfer_lines"); - - migrationBuilder.DropTable( - name: "stock_adjustments"); - - migrationBuilder.DropTable( - name: "stock_transfers"); - - migrationBuilder.DropTable( - name: "reason_codes"); - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.Designer.cs deleted file mode 100644 index 15cd145..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.Designer.cs +++ /dev/null @@ -1,2134 +0,0 @@ -// -using System; -using ERPCore.Infra.Persistence; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - [DbContext(typeof(ErpDbContext))] - [Migration("20260713105512_AddCountsAndReturns")] - partial class AddCountsAndReturns - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => - { - b.Property("BatchId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); - - b.Property("BatchNo") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ExpiryDate") - .HasColumnType("date"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.HasKey("BatchId"); - - b.HasIndex("ItemId", "BatchNo") - .IsUnique(); - - b.ToTable("batches", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.Property("BinId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); - - b.Property("BinType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("BinId"); - - b.HasIndex("WarehouseId", "Code") - .IsUnique(); - - b.ToTable("bins", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Property("CategoryId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.HasKey("CategoryId"); - - b.HasIndex("ParentId"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.Property("GrnId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("PostedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("GrnId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("PoId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("grns", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => - { - b.Property("GrnLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("GrnId") - .HasColumnType("bigint"); - - b.Property("HoldStatus") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoLineId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReceivedValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.HasKey("GrnLineId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("GrnId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoLineId"); - - b.HasIndex("UomId"); - - b.ToTable("grn_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Property("ItemId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - - b.Property("BaseUomId") - .HasColumnType("bigint"); - - b.Property("CategoryId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultVendorId") - .HasColumnType("bigint"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxClass") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TrackingMode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("ItemId"); - - b.HasIndex("BaseUomId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("DefaultVendorId"); - - b.HasIndex("Sku") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("items", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.Property("ReorderId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ReorderPoint") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReorderQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReorderId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId") - .IsUnique(); - - b.ToTable("item_reorders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => - { - b.Property("SequenceId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); - - b.Property("DocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("doc_type"); - - b.Property("LastNumber") - .HasColumnType("bigint") - .HasColumnName("last_number"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("SequenceId"); - - b.HasIndex("DocType", "Year") - .IsUnique(); - - b.ToTable("number_sequences", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.Property("PoLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("Tax") - .HasPrecision(9, 4) - .HasColumnType("numeric(9,4)"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("PoLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoId"); - - b.HasIndex("UomId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("po_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Property("PoId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); - - b.Property("ApprovalRequired") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("PoId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.ToTable("purchase_orders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => - { - b.Property("ReturnId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("ReasonCodeId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReturnId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("ReasonCodeId"); - - b.HasIndex("VendorId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("purchase_returns", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => - { - b.Property("ReturnLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); - - b.Property("GrnLineId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReturnId") - .HasColumnType("bigint"); - - b.HasKey("ReturnLineId"); - - b.HasIndex("GrnLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("ReturnId"); - - b.ToTable("purchase_return_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => - { - b.Property("ReasonCodeId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Context") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("ReasonCodeId"); - - b.HasIndex("Context", "Code") - .IsUnique(); - - b.ToTable("reason_codes", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Property("RequisitionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequestedBy") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RequisitionId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequestedBy"); - - b.HasIndex("Status"); - - b.ToTable("requisitions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.Property("ReqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RequiredBy") - .HasColumnType("date"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.HasKey("ReqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RequisitionId"); - - b.ToTable("requisition_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Property("RfqId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RfqId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.ToTable("rfqs", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.Property("RfqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.HasKey("RfqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RfqId"); - - b.ToTable("rfq_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => - { - b.Property("SerialId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("SerialNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("SerialId"); - - b.HasIndex("ItemId", "SerialNo") - .IsUnique(); - - b.ToTable("serials", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.Property("AdjustmentId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("ReasonCodeId") - .HasColumnType("bigint"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("AdjustmentId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("ReasonCodeId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("stock_adjustments", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => - { - b.Property("AdjLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); - - b.Property("AdjustmentId") - .HasColumnType("bigint"); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyDelta") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.HasKey("AdjLineId"); - - b.HasIndex("AdjustmentId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("ItemId"); - - b.HasIndex("SerialId"); - - b.ToTable("stock_adjustment_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => - { - b.Property("CountId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); - - b.Property("CountType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("CountId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("Status"); - - b.HasIndex("WarehouseId"); - - b.ToTable("stock_counts", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => - { - b.Property("CountLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("CountId") - .HasColumnType("bigint"); - - b.Property("CountedQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("SystemQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("Variance") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.HasKey("CountLineId"); - - b.HasIndex("BinId"); - - b.HasIndex("CountId"); - - b.HasIndex("ItemId"); - - b.ToTable("stock_count_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => - { - b.Property("LayerId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("GrnLineId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyRemaining") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReceiptDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("LayerId"); - - b.HasIndex("BatchId"); - - b.HasIndex("GrnLineId"); - - b.HasIndex("SerialId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); - - b.ToTable("stock_layers", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => - { - b.Property("LedgerId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Direction") - .IsRequired() - .HasMaxLength(5) - .HasColumnType("character varying(5)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyBase") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RunningBalance") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("SourceDocId") - .HasColumnType("bigint"); - - b.Property("SourceDocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("Value") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("LedgerId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("SerialId"); - - b.HasIndex("UserId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("SourceDocType", "SourceDocId"); - - b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); - - b.HasIndex("ItemId", "WarehouseId", "LedgerId"); - - b.ToTable("stock_ledger", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.Property("TransferId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DestWarehouseId") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("SrcWarehouseId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("TransferId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DestWarehouseId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("SrcWarehouseId"); - - b.HasIndex("Status"); - - b.ToTable("stock_transfers", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => - { - b.Property("TransferLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("DestBinId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("SrcBinId") - .HasColumnType("bigint"); - - b.Property("TransferId") - .HasColumnType("bigint"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.HasKey("TransferLineId"); - - b.HasIndex("BatchId"); - - b.HasIndex("DestBinId"); - - b.HasIndex("ItemId"); - - b.HasIndex("SerialId"); - - b.HasIndex("SrcBinId"); - - b.HasIndex("TransferId"); - - b.ToTable("stock_transfer_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => - { - b.Property("UomId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("UomId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("uoms", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.Property("ConversionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); - - b.Property("Factor") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("FromUomId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ToUomId") - .HasColumnType("bigint"); - - b.HasKey("ConversionId"); - - b.HasIndex("FromUomId"); - - b.HasIndex("ToUomId"); - - b.HasIndex("ItemId", "FromUomId", "ToUomId") - .IsUnique(); - - b.ToTable("uom_conversions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.User", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Username") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.HasKey("UserId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users", (string)null); - - b.HasData( - new - { - UserId = 1L, - DisplayName = "System", - Status = "Active", - Username = "system" - }); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => - { - b.Property("VendorId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Currency") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(3) - .HasColumnType("character varying(3)") - .HasDefaultValue("LKR"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxReg") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Terms") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("VendorId"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("vendors", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Property("QuotationId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("QuotationId"); - - b.HasIndex("VendorId"); - - b.HasIndex("RfqId", "VendorId") - .IsUnique(); - - b.ToTable("vendor_quotations", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.Property("QuotationLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("LeadDays") - .HasColumnType("integer"); - - b.Property("QuotationId") - .HasColumnType("bigint"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.HasKey("QuotationLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("QuotationId"); - - b.ToTable("vendor_quotation_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Property("WarehouseId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("WarehouseId"); - - b.HasIndex("Code") - .IsUnique(); - - b.ToTable("warehouses", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany("Bins") - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.HasOne("ERPCore.Domain.Entities.Category", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany() - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Vendor"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") - .WithMany("Lines") - .HasForeignKey("GrnId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") - .WithMany() - .HasForeignKey("PoLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Batch"); - - b.Navigation("Bin"); - - b.Navigation("Grn"); - - b.Navigation("Item"); - - b.Navigation("PoLine"); - - b.Navigation("Uom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") - .WithMany() - .HasForeignKey("BaseUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") - .WithMany() - .HasForeignKey("DefaultVendorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("BaseUom"); - - b.Navigation("Category"); - - b.Navigation("DefaultVendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("ReorderSettings") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany("Lines") - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Uom"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("Requisition"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") - .WithMany() - .HasForeignKey("ReasonCodeId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("ReasonCode"); - - b.Navigation("Vendor"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => - { - b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") - .WithMany() - .HasForeignKey("GrnLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") - .WithMany("Lines") - .HasForeignKey("ReturnId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("GrnLine"); - - b.Navigation("Item"); - - b.Navigation("Return"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Requester") - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requester"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany("Lines") - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Lines") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Rfq"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") - .WithMany() - .HasForeignKey("ReasonCodeId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("ReasonCode"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => - { - b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") - .WithMany("Lines") - .HasForeignKey("AdjustmentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Adjustment"); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") - .WithMany("Lines") - .HasForeignKey("CountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Count"); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") - .WithMany() - .HasForeignKey("GrnLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Batch"); - - b.Navigation("GrnLine"); - - b.Navigation("Item"); - - b.Navigation("Serial"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", null) - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", null) - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") - .WithMany() - .HasForeignKey("DestWarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") - .WithMany() - .HasForeignKey("SrcWarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("DestWarehouse"); - - b.Navigation("SrcWarehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("DestBinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("SrcBinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") - .WithMany("Lines") - .HasForeignKey("TransferId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Transfer"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") - .WithMany() - .HasForeignKey("FromUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("UomConversions") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") - .WithMany() - .HasForeignKey("ToUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("FromUom"); - - b.Navigation("Item"); - - b.Navigation("ToUom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Quotations") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Rfq"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") - .WithMany("Lines") - .HasForeignKey("QuotationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Quotation"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Navigation("ReorderSettings"); - - b.Navigation("UomConversions"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Navigation("Lines"); - - b.Navigation("Quotations"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Navigation("Bins"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.cs deleted file mode 100644 index bc674d9..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260713105512_AddCountsAndReturns.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - /// - public partial class AddCountsAndReturns : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "purchase_returns", - columns: table => new - { - ReturnId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - VendorId = table.Column(type: "bigint", nullable: false), - WarehouseId = table.Column(type: "bigint", nullable: false), - ReasonCodeId = table.Column(type: "bigint", nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_purchase_returns", x => x.ReturnId); - table.ForeignKey( - name: "FK_purchase_returns_reason_codes_ReasonCodeId", - column: x => x.ReasonCodeId, - principalTable: "reason_codes", - principalColumn: "ReasonCodeId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_purchase_returns_users_CreatedBy", - column: x => x.CreatedBy, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_purchase_returns_vendors_VendorId", - column: x => x.VendorId, - principalTable: "vendors", - principalColumn: "VendorId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_purchase_returns_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "stock_counts", - columns: table => new - { - CountId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - WarehouseId = table.Column(type: "bigint", nullable: false), - CountType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreatedBy = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - xmin = table.Column(type: "xid", rowVersion: true, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_stock_counts", x => x.CountId); - table.ForeignKey( - name: "FK_stock_counts_users_CreatedBy", - column: x => x.CreatedBy, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_counts_warehouses_WarehouseId", - column: x => x.WarehouseId, - principalTable: "warehouses", - principalColumn: "WarehouseId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "purchase_return_lines", - columns: table => new - { - ReturnLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ReturnId = table.Column(type: "bigint", nullable: false), - GrnLineId = table.Column(type: "bigint", nullable: true), - ItemId = table.Column(type: "bigint", nullable: false), - Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_purchase_return_lines", x => x.ReturnLineId); - table.ForeignKey( - name: "FK_purchase_return_lines_grn_lines_GrnLineId", - column: x => x.GrnLineId, - principalTable: "grn_lines", - principalColumn: "GrnLineId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_purchase_return_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_purchase_return_lines_purchase_returns_ReturnId", - column: x => x.ReturnId, - principalTable: "purchase_returns", - principalColumn: "ReturnId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "stock_count_lines", - columns: table => new - { - CountLineId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - CountId = table.Column(type: "bigint", nullable: false), - ItemId = table.Column(type: "bigint", nullable: false), - BinId = table.Column(type: "bigint", nullable: true), - SystemQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - CountedQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true), - Variance = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_stock_count_lines", x => x.CountLineId); - table.ForeignKey( - name: "FK_stock_count_lines_bins_BinId", - column: x => x.BinId, - principalTable: "bins", - principalColumn: "BinId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_count_lines_items_ItemId", - column: x => x.ItemId, - principalTable: "items", - principalColumn: "ItemId", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_stock_count_lines_stock_counts_CountId", - column: x => x.CountId, - principalTable: "stock_counts", - principalColumn: "CountId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_purchase_return_lines_GrnLineId", - table: "purchase_return_lines", - column: "GrnLineId"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_return_lines_ItemId", - table: "purchase_return_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_return_lines_ReturnId", - table: "purchase_return_lines", - column: "ReturnId"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_returns_CreatedBy", - table: "purchase_returns", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_returns_DocNo", - table: "purchase_returns", - column: "DocNo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_purchase_returns_ReasonCodeId", - table: "purchase_returns", - column: "ReasonCodeId"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_returns_VendorId", - table: "purchase_returns", - column: "VendorId"); - - migrationBuilder.CreateIndex( - name: "IX_purchase_returns_WarehouseId", - table: "purchase_returns", - column: "WarehouseId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_count_lines_BinId", - table: "stock_count_lines", - column: "BinId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_count_lines_CountId", - table: "stock_count_lines", - column: "CountId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_count_lines_ItemId", - table: "stock_count_lines", - column: "ItemId"); - - migrationBuilder.CreateIndex( - name: "IX_stock_counts_CreatedBy", - table: "stock_counts", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_stock_counts_DocNo", - table: "stock_counts", - column: "DocNo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_stock_counts_Status", - table: "stock_counts", - column: "Status"); - - migrationBuilder.CreateIndex( - name: "IX_stock_counts_WarehouseId", - table: "stock_counts", - column: "WarehouseId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "purchase_return_lines"); - - migrationBuilder.DropTable( - name: "stock_count_lines"); - - migrationBuilder.DropTable( - name: "purchase_returns"); - - migrationBuilder.DropTable( - name: "stock_counts"); - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.Designer.cs deleted file mode 100644 index 04650de..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.Designer.cs +++ /dev/null @@ -1,2222 +0,0 @@ -// -using System; -using ERPCore.Infra.Persistence; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - [DbContext(typeof(ErpDbContext))] - [Migration("20260713121825_AddAuditAndJournal")] - partial class AddAuditAndJournal - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => - { - b.Property("AuditId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); - - b.Property("Action") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("ChangeSet") - .IsRequired() - .HasColumnType("jsonb"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .HasColumnType("bigint"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("AuditId"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("UserId"); - - b.HasIndex("EntityType", "EntityId"); - - b.ToTable("audit_logs", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => - { - b.Property("BatchId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); - - b.Property("BatchNo") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ExpiryDate") - .HasColumnType("date"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.HasKey("BatchId"); - - b.HasIndex("ItemId", "BatchNo") - .IsUnique(); - - b.ToTable("batches", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.Property("BinId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); - - b.Property("BinType") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("BinId"); - - b.HasIndex("WarehouseId", "Code") - .IsUnique(); - - b.ToTable("bins", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Property("CategoryId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.HasKey("CategoryId"); - - b.HasIndex("ParentId"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.Property("GrnId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("PostedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("GrnId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("PoId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("grns", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => - { - b.Property("GrnLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("GrnId") - .HasColumnType("bigint"); - - b.Property("HoldStatus") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoLineId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReceivedValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.HasKey("GrnLineId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("GrnId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoLineId"); - - b.HasIndex("UomId"); - - b.ToTable("grn_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Property("ItemId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - - b.Property("BaseUomId") - .HasColumnType("bigint"); - - b.Property("CategoryId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultVendorId") - .HasColumnType("bigint"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxClass") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TrackingMode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("ItemId"); - - b.HasIndex("BaseUomId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("DefaultVendorId"); - - b.HasIndex("Sku") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("items", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.Property("ReorderId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ReorderPoint") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReorderQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReorderId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId") - .IsUnique(); - - b.ToTable("item_reorders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => - { - b.Property("JournalId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); - - b.Property("Amount") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("CreditAccount") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("DebitAccount") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SourceDocId") - .HasColumnType("bigint"); - - b.Property("SourceDocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.HasKey("JournalId"); - - b.HasIndex("SourceDocType", "SourceDocId"); - - b.ToTable("journal_entry_stubs", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => - { - b.Property("SequenceId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); - - b.Property("DocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("doc_type"); - - b.Property("LastNumber") - .HasColumnType("bigint") - .HasColumnName("last_number"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("SequenceId"); - - b.HasIndex("DocType", "Year") - .IsUnique(); - - b.ToTable("number_sequences", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.Property("PoLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("PoId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("Tax") - .HasPrecision(9, 4) - .HasColumnType("numeric(9,4)"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("UomId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("PoLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("PoId"); - - b.HasIndex("UomId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("po_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Property("PoId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); - - b.Property("ApprovalRequired") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("PoId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.HasIndex("Status"); - - b.HasIndex("VendorId"); - - b.ToTable("purchase_orders", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => - { - b.Property("ReturnId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("ReasonCodeId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("ReturnId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("ReasonCodeId"); - - b.HasIndex("VendorId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("purchase_returns", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => - { - b.Property("ReturnLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); - - b.Property("GrnLineId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReturnId") - .HasColumnType("bigint"); - - b.HasKey("ReturnLineId"); - - b.HasIndex("GrnLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("ReturnId"); - - b.ToTable("purchase_return_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => - { - b.Property("ReasonCodeId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Context") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("ReasonCodeId"); - - b.HasIndex("Context", "Code") - .IsUnique(); - - b.ToTable("reason_codes", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Property("RequisitionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequestedBy") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RequisitionId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequestedBy"); - - b.HasIndex("Status"); - - b.ToTable("requisitions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.Property("ReqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RequiredBy") - .HasColumnType("date"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.HasKey("ReqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RequisitionId"); - - b.ToTable("requisition_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Property("RfqId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RequisitionId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("RfqId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("RequisitionId"); - - b.ToTable("rfqs", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.Property("RfqLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.HasKey("RfqLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("RfqId"); - - b.ToTable("rfq_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => - { - b.Property("SerialId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("SerialNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("SerialId"); - - b.HasIndex("ItemId", "SerialNo") - .IsUnique(); - - b.ToTable("serials", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.Property("AdjustmentId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("ReasonCodeId") - .HasColumnType("bigint"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("AdjustmentId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("ReasonCodeId"); - - b.HasIndex("WarehouseId"); - - b.ToTable("stock_adjustments", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => - { - b.Property("AdjLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); - - b.Property("AdjustmentId") - .HasColumnType("bigint"); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyDelta") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.HasKey("AdjLineId"); - - b.HasIndex("AdjustmentId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("ItemId"); - - b.HasIndex("SerialId"); - - b.ToTable("stock_adjustment_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => - { - b.Property("CountId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); - - b.Property("CountType") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("CountId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("Status"); - - b.HasIndex("WarehouseId"); - - b.ToTable("stock_counts", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => - { - b.Property("CountLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("CountId") - .HasColumnType("bigint"); - - b.Property("CountedQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("SystemQty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("Variance") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.HasKey("CountLineId"); - - b.HasIndex("BinId"); - - b.HasIndex("CountId"); - - b.HasIndex("ItemId"); - - b.ToTable("stock_count_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => - { - b.Property("LayerId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("GrnLineId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyRemaining") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("ReceiptDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("LayerId"); - - b.HasIndex("BatchId"); - - b.HasIndex("GrnLineId"); - - b.HasIndex("SerialId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); - - b.ToTable("stock_layers", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => - { - b.Property("LedgerId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("BinId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Direction") - .IsRequired() - .HasMaxLength(5) - .HasColumnType("character varying(5)"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("QtyBase") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("RunningBalance") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("SourceDocId") - .HasColumnType("bigint"); - - b.Property("SourceDocType") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("Value") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("WarehouseId") - .HasColumnType("bigint"); - - b.HasKey("LedgerId"); - - b.HasIndex("BatchId"); - - b.HasIndex("BinId"); - - b.HasIndex("SerialId"); - - b.HasIndex("UserId"); - - b.HasIndex("WarehouseId"); - - b.HasIndex("SourceDocType", "SourceDocId"); - - b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); - - b.HasIndex("ItemId", "WarehouseId", "LedgerId"); - - b.ToTable("stock_ledger", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.Property("TransferId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("bigint"); - - b.Property("DestWarehouseId") - .HasColumnType("bigint"); - - b.Property("DocNo") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("SrcWarehouseId") - .HasColumnType("bigint"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.HasKey("TransferId"); - - b.HasIndex("CreatedBy"); - - b.HasIndex("DestWarehouseId"); - - b.HasIndex("DocNo") - .IsUnique(); - - b.HasIndex("SrcWarehouseId"); - - b.HasIndex("Status"); - - b.ToTable("stock_transfers", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => - { - b.Property("TransferLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); - - b.Property("BatchId") - .HasColumnType("bigint"); - - b.Property("DestBinId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("Qty") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("QtyReceived") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.Property("SerialId") - .HasColumnType("bigint"); - - b.Property("SrcBinId") - .HasColumnType("bigint"); - - b.Property("TransferId") - .HasColumnType("bigint"); - - b.Property("UnitCost") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.HasKey("TransferLineId"); - - b.HasIndex("BatchId"); - - b.HasIndex("DestBinId"); - - b.HasIndex("ItemId"); - - b.HasIndex("SerialId"); - - b.HasIndex("SrcBinId"); - - b.HasIndex("TransferId"); - - b.ToTable("stock_transfer_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => - { - b.Property("UomId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("UomId"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("uoms", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.Property("ConversionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); - - b.Property("Factor") - .HasPrecision(18, 6) - .HasColumnType("numeric(18,6)"); - - b.Property("FromUomId") - .HasColumnType("bigint"); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("ToUomId") - .HasColumnType("bigint"); - - b.HasKey("ConversionId"); - - b.HasIndex("FromUomId"); - - b.HasIndex("ToUomId"); - - b.HasIndex("ItemId", "FromUomId", "ToUomId") - .IsUnique(); - - b.ToTable("uom_conversions", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.User", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Username") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.HasKey("UserId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("users", (string)null); - - b.HasData( - new - { - UserId = 1L, - DisplayName = "System", - Status = "Active", - Username = "system" - }); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => - { - b.Property("VendorId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Currency") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(3) - .HasColumnType("character varying(3)") - .HasDefaultValue("LKR"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("xid") - .HasColumnName("xmin"); - - b.Property("Status") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasDefaultValue("Active"); - - b.Property("TaxReg") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Terms") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("VendorId"); - - b.HasIndex("Code") - .IsUnique(); - - b.HasIndex("Status"); - - b.ToTable("vendors", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Property("QuotationId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("RfqId") - .HasColumnType("bigint"); - - b.Property("VendorId") - .HasColumnType("bigint"); - - b.HasKey("QuotationId"); - - b.HasIndex("VendorId"); - - b.HasIndex("RfqId", "VendorId") - .IsUnique(); - - b.ToTable("vendor_quotations", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.Property("QuotationLineId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); - - b.Property("ItemId") - .HasColumnType("bigint"); - - b.Property("LeadDays") - .HasColumnType("integer"); - - b.Property("QuotationId") - .HasColumnType("bigint"); - - b.Property("UnitPrice") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)"); - - b.HasKey("QuotationLineId"); - - b.HasIndex("ItemId"); - - b.HasIndex("QuotationId"); - - b.ToTable("vendor_quotation_lines", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Property("WarehouseId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("WarehouseId"); - - b.HasIndex("Code") - .IsUnique(); - - b.ToTable("warehouses", (string)null); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => - { - b.HasOne("ERPCore.Domain.Entities.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => - { - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany("Bins") - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.HasOne("ERPCore.Domain.Entities.Category", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany() - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Vendor"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") - .WithMany("Lines") - .HasForeignKey("GrnId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") - .WithMany() - .HasForeignKey("PoLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Batch"); - - b.Navigation("Bin"); - - b.Navigation("Grn"); - - b.Navigation("Item"); - - b.Navigation("PoLine"); - - b.Navigation("Uom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") - .WithMany() - .HasForeignKey("BaseUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") - .WithMany() - .HasForeignKey("DefaultVendorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("BaseUom"); - - b.Navigation("Category"); - - b.Navigation("DefaultVendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("ReorderSettings") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") - .WithMany("Lines") - .HasForeignKey("PoId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") - .WithMany() - .HasForeignKey("UomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("PurchaseOrder"); - - b.Navigation("Uom"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("Requisition"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") - .WithMany() - .HasForeignKey("ReasonCodeId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("ReasonCode"); - - b.Navigation("Vendor"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => - { - b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") - .WithMany() - .HasForeignKey("GrnLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") - .WithMany("Lines") - .HasForeignKey("ReturnId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("GrnLine"); - - b.Navigation("Item"); - - b.Navigation("Return"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Requester") - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requester"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany("Lines") - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") - .WithMany() - .HasForeignKey("RequisitionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Requisition"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Lines") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Rfq"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") - .WithMany() - .HasForeignKey("ReasonCodeId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("ReasonCode"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => - { - b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") - .WithMany("Lines") - .HasForeignKey("AdjustmentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Adjustment"); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") - .WithMany("Lines") - .HasForeignKey("CountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Count"); - - b.Navigation("Item"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") - .WithMany() - .HasForeignKey("GrnLineId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Batch"); - - b.Navigation("GrnLine"); - - b.Navigation("Item"); - - b.Navigation("Serial"); - - b.Navigation("Warehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("BinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", null) - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", null) - .WithMany() - .HasForeignKey("WarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.HasOne("ERPCore.Domain.Entities.User", "Creator") - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") - .WithMany() - .HasForeignKey("DestWarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") - .WithMany() - .HasForeignKey("SrcWarehouseId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("DestWarehouse"); - - b.Navigation("SrcWarehouse"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Batch", null) - .WithMany() - .HasForeignKey("BatchId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("DestBinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Serial", null) - .WithMany() - .HasForeignKey("SerialId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.Bin", null) - .WithMany() - .HasForeignKey("SrcBinId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") - .WithMany("Lines") - .HasForeignKey("TransferId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Transfer"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => - { - b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") - .WithMany() - .HasForeignKey("FromUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany("UomConversions") - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") - .WithMany() - .HasForeignKey("ToUomId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("FromUom"); - - b.Navigation("Item"); - - b.Navigation("ToUom"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") - .WithMany("Quotations") - .HasForeignKey("RfqId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") - .WithMany() - .HasForeignKey("VendorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Rfq"); - - b.Navigation("Vendor"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => - { - b.HasOne("ERPCore.Domain.Entities.Item", "Item") - .WithMany() - .HasForeignKey("ItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") - .WithMany("Lines") - .HasForeignKey("QuotationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Item"); - - b.Navigation("Quotation"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => - { - b.Navigation("ReorderSettings"); - - b.Navigation("UomConversions"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => - { - b.Navigation("Lines"); - - b.Navigation("Quotations"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => - { - b.Navigation("Lines"); - }); - - modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => - { - b.Navigation("Bins"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.cs deleted file mode 100644 index 0db846e..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260713121825_AddAuditAndJournal.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - /// - public partial class AddAuditAndJournal : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "audit_logs", - columns: table => new - { - AuditId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserId = table.Column(type: "bigint", nullable: false), - EntityType = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), - EntityId = table.Column(type: "bigint", nullable: false), - Action = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), - ChangeSet = table.Column(type: "jsonb", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_audit_logs", x => x.AuditId); - table.ForeignKey( - name: "FK_audit_logs_users_UserId", - column: x => x.UserId, - principalTable: "users", - principalColumn: "UserId", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "journal_entry_stubs", - columns: table => new - { - JournalId = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - SourceDocType = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), - SourceDocId = table.Column(type: "bigint", nullable: false), - DebitAccount = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - CreditAccount = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - Amount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_journal_entry_stubs", x => x.JournalId); - }); - - migrationBuilder.CreateIndex( - name: "IX_audit_logs_CreatedAt", - table: "audit_logs", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_audit_logs_EntityType_EntityId", - table: "audit_logs", - columns: new[] { "EntityType", "EntityId" }); - - migrationBuilder.CreateIndex( - name: "IX_audit_logs_UserId", - table: "audit_logs", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_journal_entry_stubs_SourceDocType_SourceDocId", - table: "journal_entry_stubs", - columns: new[] { "SourceDocType", "SourceDocId" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "audit_logs"); - - migrationBuilder.DropTable( - name: "journal_entry_stubs"); - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.cs deleted file mode 100644 index 026db87..0000000 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace ERPCore.Infra.Persistence.Migrations -{ - /// - public partial class AddAuthUserId : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "auth_user_id", - table: "users", - type: "uuid", - nullable: true); - - migrationBuilder.UpdateData( - table: "users", - keyColumn: "UserId", - keyValue: 1L, - column: "auth_user_id", - value: null); - - migrationBuilder.CreateIndex( - name: "IX_users_auth_user_id", - table: "users", - column: "auth_user_id", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_users_auth_user_id", - table: "users"); - - migrationBuilder.DropColumn( - name: "auth_user_id", - table: "users"); - } - } -} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.Designer.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260715093552_InitialCreate.Designer.cs similarity index 83% rename from Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.Designer.cs rename to Backend/ERPCore/Infra/Persistence/Migrations/20260715093552_InitialCreate.Designer.cs index 7251936..0406326 100644 --- a/Backend/ERPCore/Infra/Persistence/Migrations/20260714103443_AddAuthUserId.Designer.cs +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260715093552_InitialCreate.Designer.cs @@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace ERPCore.Infra.Persistence.Migrations { [DbContext(typeof(ErpDbContext))] - [Migration("20260714103443_AddAuthUserId")] - partial class AddAuthUserId + [Migration("20260715093552_InitialCreate")] + partial class InitialCreate { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -27,11 +27,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => { - b.Property("AuditId") + b.Property("AuditId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); b.Property("Action") .IsRequired() @@ -45,16 +45,16 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("EntityId") - .HasColumnType("bigint"); + b.Property("EntityId") + .HasColumnType("integer"); b.Property("EntityType") .IsRequired() .HasMaxLength(80) .HasColumnType("character varying(80)"); - b.Property("UserId") - .HasColumnType("bigint"); + b.Property("UserId") + .HasColumnType("integer"); b.HasKey("AuditId"); @@ -69,11 +69,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => { - b.Property("BatchId") + b.Property("BatchId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); b.Property("BatchNo") .IsRequired() @@ -83,8 +83,8 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("ExpiryDate") .HasColumnType("date"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.HasKey("BatchId"); @@ -96,11 +96,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => { - b.Property("BinId") + b.Property("BinId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); b.Property("BinType") .HasMaxLength(50) @@ -111,8 +111,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(50) .HasColumnType("character varying(50)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("BinId"); @@ -124,19 +124,19 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => { - b.Property("CategoryId") + b.Property("CategoryId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); b.Property("Name") .IsRequired() .HasMaxLength(200) .HasColumnType("character varying(200)"); - b.Property("ParentId") - .HasColumnType("bigint"); + b.Property("ParentId") + .HasColumnType("integer"); b.HasKey("CategoryId"); @@ -147,25 +147,25 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => { - b.Property("GrnId") + b.Property("GrnId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("PoId") - .HasColumnType("bigint"); + b.Property("PoId") + .HasColumnType("integer"); b.Property("PostedAt") .HasColumnType("timestamp with time zone"); @@ -181,11 +181,11 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("VendorId") - .HasColumnType("bigint"); + b.Property("VendorId") + .HasColumnType("integer"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("GrnId"); @@ -207,31 +207,31 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => { - b.Property("GrnLineId") + b.Property("GrnLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("BinId") - .HasColumnType("bigint"); + b.Property("BinId") + .HasColumnType("integer"); - b.Property("GrnId") - .HasColumnType("bigint"); + b.Property("GrnId") + .HasColumnType("integer"); b.Property("HoldStatus") .IsRequired() .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); - b.Property("PoLineId") - .HasColumnType("bigint"); + b.Property("PoLineId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) @@ -245,8 +245,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 6) .HasColumnType("numeric(18,6)"); - b.Property("UomId") - .HasColumnType("bigint"); + b.Property("UomId") + .HasColumnType("integer"); b.HasKey("GrnLineId"); @@ -267,23 +267,23 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => { - b.Property("ItemId") + b.Property("ItemId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - b.Property("BaseUomId") - .HasColumnType("bigint"); + b.Property("BaseUomId") + .HasColumnType("integer"); - b.Property("CategoryId") - .HasColumnType("bigint"); + b.Property("CategoryId") + .HasColumnType("integer"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("DefaultVendorId") - .HasColumnType("bigint"); + b.Property("DefaultVendorId") + .HasColumnType("integer"); b.Property("Description") .HasMaxLength(1000) @@ -347,14 +347,14 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => { - b.Property("ReorderId") + b.Property("ReorderId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("ReorderPoint") .HasPrecision(18, 4) @@ -364,8 +364,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("ReorderId"); @@ -379,11 +379,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => { - b.Property("JournalId") + b.Property("JournalId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); b.Property("Amount") .HasPrecision(18, 4) @@ -399,8 +399,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("SourceDocId") - .HasColumnType("bigint"); + b.Property("SourceDocId") + .HasColumnType("integer"); b.Property("SourceDocType") .IsRequired() @@ -416,11 +416,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => { - b.Property("SequenceId") + b.Property("SequenceId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); b.Property("DocType") .IsRequired() @@ -428,8 +428,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasColumnType("character varying(10)") .HasColumnName("doc_type"); - b.Property("LastNumber") - .HasColumnType("bigint") + b.Property("LastNumber") + .HasColumnType("integer") .HasColumnName("last_number"); b.Property("Year") @@ -446,17 +446,17 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => { - b.Property("PoLineId") + b.Property("PoLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); - b.Property("PoId") - .HasColumnType("bigint"); + b.Property("PoId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) @@ -474,11 +474,11 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("UomId") - .HasColumnType("bigint"); + b.Property("UomId") + .HasColumnType("integer"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("PoLineId"); @@ -495,11 +495,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => { - b.Property("PoId") + b.Property("PoId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); b.Property("ApprovalRequired") .HasColumnType("boolean"); @@ -507,16 +507,16 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("RequisitionId") - .HasColumnType("bigint"); + b.Property("RequisitionId") + .HasColumnType("integer"); b.Property("RowVersion") .IsConcurrencyToken() @@ -532,8 +532,8 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); - b.Property("VendorId") - .HasColumnType("bigint"); + b.Property("VendorId") + .HasColumnType("integer"); b.HasKey("PoId"); @@ -553,36 +553,36 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => { - b.Property("ReturnId") + b.Property("ReturnId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("ReasonCodeId") - .HasColumnType("bigint"); + b.Property("ReasonCodeId") + .HasColumnType("integer"); b.Property("Status") .IsRequired() .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("VendorId") - .HasColumnType("bigint"); + b.Property("VendorId") + .HasColumnType("integer"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("ReturnId"); @@ -602,24 +602,24 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => { - b.Property("ReturnLineId") + b.Property("ReturnLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); - b.Property("GrnLineId") - .HasColumnType("bigint"); + b.Property("GrnLineId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("ReturnId") - .HasColumnType("bigint"); + b.Property("ReturnId") + .HasColumnType("integer"); b.HasKey("ReturnLineId"); @@ -634,11 +634,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => { - b.Property("ReasonCodeId") + b.Property("ReasonCodeId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); b.Property("Code") .IsRequired() @@ -665,11 +665,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => { - b.Property("RequisitionId") + b.Property("RequisitionId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); @@ -679,8 +679,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("RequestedBy") - .HasColumnType("bigint"); + b.Property("RequestedBy") + .HasColumnType("integer"); b.Property("Status") .IsRequired() @@ -701,14 +701,14 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => { - b.Property("ReqLineId") + b.Property("ReqLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) @@ -717,8 +717,8 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("RequiredBy") .HasColumnType("date"); - b.Property("RequisitionId") - .HasColumnType("bigint"); + b.Property("RequisitionId") + .HasColumnType("integer"); b.HasKey("ReqLineId"); @@ -731,11 +731,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => { - b.Property("RfqId") + b.Property("RfqId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); @@ -745,8 +745,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("RequisitionId") - .HasColumnType("bigint"); + b.Property("RequisitionId") + .HasColumnType("integer"); b.Property("Status") .IsRequired() @@ -765,21 +765,21 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => { - b.Property("RfqLineId") + b.Property("RfqLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("RfqId") - .HasColumnType("bigint"); + b.Property("RfqId") + .HasColumnType("integer"); b.HasKey("RfqLineId"); @@ -792,14 +792,14 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => { - b.Property("SerialId") + b.Property("SerialId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("SerialNo") .IsRequired() @@ -821,25 +821,25 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => { - b.Property("AdjustmentId") + b.Property("AdjustmentId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("ReasonCodeId") - .HasColumnType("bigint"); + b.Property("ReasonCodeId") + .HasColumnType("integer"); b.Property("RowVersion") .IsConcurrencyToken() @@ -852,8 +852,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("AdjustmentId"); @@ -871,30 +871,30 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => { - b.Property("AdjLineId") + b.Property("AdjLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); - b.Property("AdjustmentId") - .HasColumnType("bigint"); + b.Property("AdjustmentId") + .HasColumnType("integer"); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("BinId") - .HasColumnType("bigint"); + b.Property("BinId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("QtyDelta") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("SerialId") - .HasColumnType("bigint"); + b.Property("SerialId") + .HasColumnType("integer"); b.HasKey("AdjLineId"); @@ -913,11 +913,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => { - b.Property("CountId") + b.Property("CountId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); b.Property("CountType") .IsRequired() @@ -927,8 +927,8 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() @@ -946,8 +946,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("CountId"); @@ -965,24 +965,24 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => { - b.Property("CountLineId") + b.Property("CountLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); - b.Property("BinId") - .HasColumnType("bigint"); + b.Property("BinId") + .HasColumnType("integer"); - b.Property("CountId") - .HasColumnType("bigint"); + b.Property("CountId") + .HasColumnType("integer"); b.Property("CountedQty") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("SystemQty") .HasPrecision(18, 4) @@ -1005,20 +1005,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => { - b.Property("LayerId") + b.Property("LayerId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("GrnLineId") - .HasColumnType("bigint"); + b.Property("GrnLineId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("QtyReceived") .HasPrecision(18, 4) @@ -1031,15 +1031,15 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("ReceiptDate") .HasColumnType("timestamp with time zone"); - b.Property("SerialId") - .HasColumnType("bigint"); + b.Property("SerialId") + .HasColumnType("integer"); b.Property("UnitCost") .HasPrecision(18, 6) .HasColumnType("numeric(18,6)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("LayerId"); @@ -1058,17 +1058,17 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => { - b.Property("LedgerId") + b.Property("LedgerId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("BinId") - .HasColumnType("bigint"); + b.Property("BinId") + .HasColumnType("integer"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); @@ -1078,8 +1078,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(5) .HasColumnType("character varying(5)"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("QtyBase") .HasPrecision(18, 4) @@ -1089,11 +1089,11 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("SerialId") - .HasColumnType("bigint"); + b.Property("SerialId") + .HasColumnType("integer"); - b.Property("SourceDocId") - .HasColumnType("bigint"); + b.Property("SourceDocId") + .HasColumnType("integer"); b.Property("SourceDocType") .IsRequired() @@ -1104,15 +1104,15 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 6) .HasColumnType("numeric(18,6)"); - b.Property("UserId") - .HasColumnType("bigint"); + b.Property("UserId") + .HasColumnType("integer"); b.Property("Value") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("LedgerId"); @@ -1137,20 +1137,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => { - b.Property("TransferId") + b.Property("TransferId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); - b.Property("DestWarehouseId") - .HasColumnType("bigint"); + b.Property("DestWarehouseId") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() @@ -1163,8 +1163,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasColumnType("xid") .HasColumnName("xmin"); - b.Property("SrcWarehouseId") - .HasColumnType("bigint"); + b.Property("SrcWarehouseId") + .HasColumnType("integer"); b.Property("Status") .IsRequired() @@ -1189,20 +1189,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => { - b.Property("TransferLineId") + b.Property("TransferLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("DestBinId") - .HasColumnType("bigint"); + b.Property("DestBinId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) @@ -1212,14 +1212,14 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("SerialId") - .HasColumnType("bigint"); + b.Property("SerialId") + .HasColumnType("integer"); - b.Property("SrcBinId") - .HasColumnType("bigint"); + b.Property("SrcBinId") + .HasColumnType("integer"); - b.Property("TransferId") - .HasColumnType("bigint"); + b.Property("TransferId") + .HasColumnType("integer"); b.Property("UnitCost") .HasPrecision(18, 6) @@ -1244,11 +1244,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => { - b.Property("UomId") + b.Property("UomId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); b.Property("Name") .IsRequired() @@ -1265,24 +1265,24 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => { - b.Property("ConversionId") + b.Property("ConversionId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); b.Property("Factor") .HasPrecision(18, 6) .HasColumnType("numeric(18,6)"); - b.Property("FromUomId") - .HasColumnType("bigint"); + b.Property("FromUomId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); - b.Property("ToUomId") - .HasColumnType("bigint"); + b.Property("ToUomId") + .HasColumnType("integer"); b.HasKey("ConversionId"); @@ -1298,11 +1298,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.User", b => { - b.Property("UserId") + b.Property("UserId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); b.Property("AuthUserId") .HasColumnType("uuid") @@ -1336,7 +1336,7 @@ namespace ERPCore.Infra.Persistence.Migrations b.HasData( new { - UserId = 1L, + UserId = 1, DisplayName = "System", Status = "Active", Username = "system" @@ -1345,11 +1345,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => { - b.Property("VendorId") + b.Property("VendorId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); b.Property("Code") .IsRequired() @@ -1407,20 +1407,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => { - b.Property("QuotationId") + b.Property("QuotationId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("RfqId") - .HasColumnType("bigint"); + b.Property("RfqId") + .HasColumnType("integer"); - b.Property("VendorId") - .HasColumnType("bigint"); + b.Property("VendorId") + .HasColumnType("integer"); b.HasKey("QuotationId"); @@ -1434,20 +1434,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => { - b.Property("QuotationLineId") + b.Property("QuotationLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("LeadDays") .HasColumnType("integer"); - b.Property("QuotationId") - .HasColumnType("bigint"); + b.Property("QuotationId") + .HasColumnType("integer"); b.Property("UnitPrice") .HasPrecision(18, 4) @@ -1464,11 +1464,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => { - b.Property("WarehouseId") + b.Property("WarehouseId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); b.Property("Code") .IsRequired() diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/20260715093552_InitialCreate.cs b/Backend/ERPCore/Infra/Persistence/Migrations/20260715093552_InitialCreate.cs new file mode 100644 index 0000000..1d0bd52 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Migrations/20260715093552_InitialCreate.cs @@ -0,0 +1,1791 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Infra.Persistence.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "categories", + columns: table => new + { + CategoryId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ParentId = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_categories", x => x.CategoryId); + table.ForeignKey( + name: "FK_categories_categories_ParentId", + column: x => x.ParentId, + principalTable: "categories", + principalColumn: "CategoryId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "journal_entry_stubs", + columns: table => new + { + JournalId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SourceDocType = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + SourceDocId = table.Column(type: "integer", nullable: false), + DebitAccount = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreditAccount = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Amount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_journal_entry_stubs", x => x.JournalId); + }); + + migrationBuilder.CreateTable( + name: "number_sequences", + columns: table => new + { + SequenceId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + doc_type = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + year = table.Column(type: "integer", nullable: false), + last_number = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_number_sequences", x => x.SequenceId); + }); + + migrationBuilder.CreateTable( + name: "reason_codes", + columns: table => new + { + ReasonCodeId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Context = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_reason_codes", x => x.ReasonCodeId); + }); + + migrationBuilder.CreateTable( + name: "uoms", + columns: table => new + { + UomId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_uoms", x => x.UomId); + }); + + migrationBuilder.CreateTable( + name: "users", + columns: table => new + { + UserId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Username = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + auth_user_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_users", x => x.UserId); + }); + + migrationBuilder.CreateTable( + name: "vendors", + columns: table => new + { + VendorId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Terms = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + TaxReg = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Currency = table.Column(type: "character varying(3)", maxLength: 3, nullable: false, defaultValue: "LKR"), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_vendors", x => x.VendorId); + }); + + migrationBuilder.CreateTable( + name: "warehouses", + columns: table => new + { + WarehouseId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_warehouses", x => x.WarehouseId); + }); + + migrationBuilder.CreateTable( + name: "audit_logs", + columns: table => new + { + AuditId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "integer", nullable: false), + EntityType = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + EntityId = table.Column(type: "integer", nullable: false), + Action = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + ChangeSet = table.Column(type: "jsonb", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_audit_logs", x => x.AuditId); + table.ForeignKey( + name: "FK_audit_logs_users_UserId", + column: x => x.UserId, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "requisitions", + columns: table => new + { + RequisitionId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + RequestedBy = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_requisitions", x => x.RequisitionId); + table.ForeignKey( + name: "FK_requisitions_users_RequestedBy", + column: x => x.RequestedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "items", + columns: table => new + { + ItemId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Sku = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + CategoryId = table.Column(type: "integer", nullable: false), + BaseUomId = table.Column(type: "integer", nullable: false), + DefaultVendorId = table.Column(type: "integer", nullable: true), + ItemType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + TrackingMode = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + TaxClass = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_items", x => x.ItemId); + table.ForeignKey( + name: "FK_items_categories_CategoryId", + column: x => x.CategoryId, + principalTable: "categories", + principalColumn: "CategoryId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_items_uoms_BaseUomId", + column: x => x.BaseUomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_items_vendors_DefaultVendorId", + column: x => x.DefaultVendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "bins", + columns: table => new + { + BinId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + WarehouseId = table.Column(type: "integer", nullable: false), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + BinType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_bins", x => x.BinId); + table.ForeignKey( + name: "FK_bins_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "purchase_returns", + columns: table => new + { + ReturnId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + VendorId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + ReasonCodeId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_purchase_returns", x => x.ReturnId); + table.ForeignKey( + name: "FK_purchase_returns_reason_codes_ReasonCodeId", + column: x => x.ReasonCodeId, + principalTable: "reason_codes", + principalColumn: "ReasonCodeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_adjustments", + columns: table => new + { + AdjustmentId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + ReasonCodeId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_adjustments", x => x.AdjustmentId); + table.ForeignKey( + name: "FK_stock_adjustments_reason_codes_ReasonCodeId", + column: x => x.ReasonCodeId, + principalTable: "reason_codes", + principalColumn: "ReasonCodeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustments_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustments_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_counts", + columns: table => new + { + CountId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + CountType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_counts", x => x.CountId); + table.ForeignKey( + name: "FK_stock_counts_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_counts_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_transfers", + columns: table => new + { + TransferId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + SrcWarehouseId = table.Column(type: "integer", nullable: false), + DestWarehouseId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_transfers", x => x.TransferId); + table.ForeignKey( + name: "FK_stock_transfers_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfers_warehouses_DestWarehouseId", + column: x => x.DestWarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfers_warehouses_SrcWarehouseId", + column: x => x.SrcWarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "purchase_orders", + columns: table => new + { + PoId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + VendorId = table.Column(type: "integer", nullable: false), + RequisitionId = table.Column(type: "integer", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ApprovalRequired = table.Column(type: "boolean", nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_purchase_orders", x => x.PoId); + table.ForeignKey( + name: "FK_purchase_orders_requisitions_RequisitionId", + column: x => x.RequisitionId, + principalTable: "requisitions", + principalColumn: "RequisitionId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_orders_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_orders_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "rfqs", + columns: table => new + { + RfqId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + RequisitionId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_rfqs", x => x.RfqId); + table.ForeignKey( + name: "FK_rfqs_requisitions_RequisitionId", + column: x => x.RequisitionId, + principalTable: "requisitions", + principalColumn: "RequisitionId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "batches", + columns: table => new + { + BatchId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + BatchNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + ExpiryDate = table.Column(type: "date", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_batches", x => x.BatchId); + table.ForeignKey( + name: "FK_batches_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "item_reorders", + columns: table => new + { + ReorderId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + ReorderPoint = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + ReorderQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_item_reorders", x => x.ReorderId); + table.ForeignKey( + name: "FK_item_reorders_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_item_reorders_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "requisition_lines", + columns: table => new + { + ReqLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RequisitionId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + RequiredBy = table.Column(type: "date", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_requisition_lines", x => x.ReqLineId); + table.ForeignKey( + name: "FK_requisition_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_requisition_lines_requisitions_RequisitionId", + column: x => x.RequisitionId, + principalTable: "requisitions", + principalColumn: "RequisitionId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "serials", + columns: table => new + { + SerialId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + SerialNo = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_serials", x => x.SerialId); + table.ForeignKey( + name: "FK_serials_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "uom_conversions", + columns: table => new + { + ConversionId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + FromUomId = table.Column(type: "integer", nullable: false), + ToUomId = table.Column(type: "integer", nullable: false), + Factor = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_uom_conversions", x => x.ConversionId); + table.ForeignKey( + name: "FK_uom_conversions_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_uom_conversions_uoms_FromUomId", + column: x => x.FromUomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_uom_conversions_uoms_ToUomId", + column: x => x.ToUomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_count_lines", + columns: table => new + { + CountLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CountId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + BinId = table.Column(type: "integer", nullable: true), + SystemQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + CountedQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true), + Variance = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_count_lines", x => x.CountLineId); + table.ForeignKey( + name: "FK_stock_count_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_count_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_count_lines_stock_counts_CountId", + column: x => x.CountId, + principalTable: "stock_counts", + principalColumn: "CountId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "grns", + columns: table => new + { + GrnId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + PoId = table.Column(type: "integer", nullable: true), + VendorId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + PostedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_grns", x => x.GrnId); + table.ForeignKey( + name: "FK_grns_purchase_orders_PoId", + column: x => x.PoId, + principalTable: "purchase_orders", + principalColumn: "PoId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "po_lines", + columns: table => new + { + PoLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PoId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + UomId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + Tax = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), + QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_po_lines", x => x.PoLineId); + table.ForeignKey( + name: "FK_po_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_po_lines_purchase_orders_PoId", + column: x => x.PoId, + principalTable: "purchase_orders", + principalColumn: "PoId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_po_lines_uoms_UomId", + column: x => x.UomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_po_lines_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "rfq_lines", + columns: table => new + { + RfqLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RfqId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_rfq_lines", x => x.RfqLineId); + table.ForeignKey( + name: "FK_rfq_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_rfq_lines_rfqs_RfqId", + column: x => x.RfqId, + principalTable: "rfqs", + principalColumn: "RfqId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "vendor_quotations", + columns: table => new + { + QuotationId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RfqId = table.Column(type: "integer", nullable: false), + VendorId = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_vendor_quotations", x => x.QuotationId); + table.ForeignKey( + name: "FK_vendor_quotations_rfqs_RfqId", + column: x => x.RfqId, + principalTable: "rfqs", + principalColumn: "RfqId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_vendor_quotations_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_adjustment_lines", + columns: table => new + { + AdjLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + AdjustmentId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + BinId = table.Column(type: "integer", nullable: true), + BatchId = table.Column(type: "integer", nullable: true), + SerialId = table.Column(type: "integer", nullable: true), + QtyDelta = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_adjustment_lines", x => x.AdjLineId); + table.ForeignKey( + name: "FK_stock_adjustment_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_stock_adjustments_AdjustmentId", + column: x => x.AdjustmentId, + principalTable: "stock_adjustments", + principalColumn: "AdjustmentId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "stock_ledger", + columns: table => new + { + LedgerId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + BinId = table.Column(type: "integer", nullable: true), + BatchId = table.Column(type: "integer", nullable: true), + SerialId = table.Column(type: "integer", nullable: true), + UserId = table.Column(type: "integer", nullable: false), + Direction = table.Column(type: "character varying(5)", maxLength: 5, nullable: false), + QtyBase = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + Value = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + RunningBalance = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + SourceDocType = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + SourceDocId = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_ledger", x => x.LedgerId); + table.ForeignKey( + name: "FK_stock_ledger_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_users_UserId", + column: x => x.UserId, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_transfer_lines", + columns: table => new + { + TransferLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + TransferId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + SrcBinId = table.Column(type: "integer", nullable: true), + DestBinId = table.Column(type: "integer", nullable: true), + BatchId = table.Column(type: "integer", nullable: true), + SerialId = table.Column(type: "integer", nullable: true), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: true), + QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_transfer_lines", x => x.TransferLineId); + table.ForeignKey( + name: "FK_stock_transfer_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_bins_DestBinId", + column: x => x.DestBinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_bins_SrcBinId", + column: x => x.SrcBinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_stock_transfers_TransferId", + column: x => x.TransferId, + principalTable: "stock_transfers", + principalColumn: "TransferId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "grn_lines", + columns: table => new + { + GrnLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + GrnId = table.Column(type: "integer", nullable: false), + PoLineId = table.Column(type: "integer", nullable: true), + ItemId = table.Column(type: "integer", nullable: false), + UomId = table.Column(type: "integer", nullable: false), + BinId = table.Column(type: "integer", nullable: true), + BatchId = table.Column(type: "integer", nullable: true), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + ReceivedValue = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + HoldStatus = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_grn_lines", x => x.GrnLineId); + table.ForeignKey( + name: "FK_grn_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_grns_GrnId", + column: x => x.GrnId, + principalTable: "grns", + principalColumn: "GrnId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_grn_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_po_lines_PoLineId", + column: x => x.PoLineId, + principalTable: "po_lines", + principalColumn: "PoLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_uoms_UomId", + column: x => x.UomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "vendor_quotation_lines", + columns: table => new + { + QuotationLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + QuotationId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + UnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + LeadDays = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_vendor_quotation_lines", x => x.QuotationLineId); + table.ForeignKey( + name: "FK_vendor_quotation_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_vendor_quotation_lines_vendor_quotations_QuotationId", + column: x => x.QuotationId, + principalTable: "vendor_quotations", + principalColumn: "QuotationId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "purchase_return_lines", + columns: table => new + { + ReturnLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ReturnId = table.Column(type: "integer", nullable: false), + GrnLineId = table.Column(type: "integer", nullable: true), + ItemId = table.Column(type: "integer", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_purchase_return_lines", x => x.ReturnLineId); + table.ForeignKey( + name: "FK_purchase_return_lines_grn_lines_GrnLineId", + column: x => x.GrnLineId, + principalTable: "grn_lines", + principalColumn: "GrnLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_return_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_return_lines_purchase_returns_ReturnId", + column: x => x.ReturnId, + principalTable: "purchase_returns", + principalColumn: "ReturnId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "stock_layers", + columns: table => new + { + LayerId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + BatchId = table.Column(type: "integer", nullable: true), + SerialId = table.Column(type: "integer", nullable: true), + GrnLineId = table.Column(type: "integer", nullable: true), + QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + QtyRemaining = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + ReceiptDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_layers", x => x.LayerId); + table.ForeignKey( + name: "FK_stock_layers_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_grn_lines_GrnLineId", + column: x => x.GrnLineId, + principalTable: "grn_lines", + principalColumn: "GrnLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.InsertData( + table: "users", + columns: new[] { "UserId", "auth_user_id", "DisplayName", "Status", "Username" }, + values: new object[] { 1, null, "System", "Active", "system" }); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_CreatedAt", + table: "audit_logs", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_EntityType_EntityId", + table: "audit_logs", + columns: new[] { "EntityType", "EntityId" }); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_UserId", + table: "audit_logs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_batches_ItemId_BatchNo", + table: "batches", + columns: new[] { "ItemId", "BatchNo" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_bins_WarehouseId_Code", + table: "bins", + columns: new[] { "WarehouseId", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_categories_ParentId", + table: "categories", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_BatchId", + table: "grn_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_BinId", + table: "grn_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_GrnId", + table: "grn_lines", + column: "GrnId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_ItemId", + table: "grn_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_PoLineId", + table: "grn_lines", + column: "PoLineId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_UomId", + table: "grn_lines", + column: "UomId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_CreatedBy", + table: "grns", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_grns_DocNo", + table: "grns", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_grns_PoId", + table: "grns", + column: "PoId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_Status", + table: "grns", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_grns_VendorId", + table: "grns", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_WarehouseId", + table: "grns", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_item_reorders_ItemId_WarehouseId", + table: "item_reorders", + columns: new[] { "ItemId", "WarehouseId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_item_reorders_WarehouseId", + table: "item_reorders", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_items_BaseUomId", + table: "items", + column: "BaseUomId"); + + migrationBuilder.CreateIndex( + name: "IX_items_CategoryId", + table: "items", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_items_DefaultVendorId", + table: "items", + column: "DefaultVendorId"); + + migrationBuilder.CreateIndex( + name: "IX_items_Sku", + table: "items", + column: "Sku", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_items_Status", + table: "items", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_journal_entry_stubs_SourceDocType_SourceDocId", + table: "journal_entry_stubs", + columns: new[] { "SourceDocType", "SourceDocId" }); + + migrationBuilder.CreateIndex( + name: "IX_number_sequences_doc_type_year", + table: "number_sequences", + columns: new[] { "doc_type", "year" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_po_lines_ItemId", + table: "po_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_po_lines_PoId", + table: "po_lines", + column: "PoId"); + + migrationBuilder.CreateIndex( + name: "IX_po_lines_UomId", + table: "po_lines", + column: "UomId"); + + migrationBuilder.CreateIndex( + name: "IX_po_lines_WarehouseId", + table: "po_lines", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_CreatedBy", + table: "purchase_orders", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_DocNo", + table: "purchase_orders", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_RequisitionId", + table: "purchase_orders", + column: "RequisitionId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_Status", + table: "purchase_orders", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_VendorId", + table: "purchase_orders", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_GrnLineId", + table: "purchase_return_lines", + column: "GrnLineId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_ItemId", + table: "purchase_return_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_ReturnId", + table: "purchase_return_lines", + column: "ReturnId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_CreatedBy", + table: "purchase_returns", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_DocNo", + table: "purchase_returns", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_ReasonCodeId", + table: "purchase_returns", + column: "ReasonCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_VendorId", + table: "purchase_returns", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_WarehouseId", + table: "purchase_returns", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_reason_codes_Context_Code", + table: "reason_codes", + columns: new[] { "Context", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_requisition_lines_ItemId", + table: "requisition_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_requisition_lines_RequisitionId", + table: "requisition_lines", + column: "RequisitionId"); + + migrationBuilder.CreateIndex( + name: "IX_requisitions_DocNo", + table: "requisitions", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_requisitions_RequestedBy", + table: "requisitions", + column: "RequestedBy"); + + migrationBuilder.CreateIndex( + name: "IX_requisitions_Status", + table: "requisitions", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_rfq_lines_ItemId", + table: "rfq_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_rfq_lines_RfqId", + table: "rfq_lines", + column: "RfqId"); + + migrationBuilder.CreateIndex( + name: "IX_rfqs_DocNo", + table: "rfqs", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_rfqs_RequisitionId", + table: "rfqs", + column: "RequisitionId"); + + migrationBuilder.CreateIndex( + name: "IX_serials_ItemId_SerialNo", + table: "serials", + columns: new[] { "ItemId", "SerialNo" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_AdjustmentId", + table: "stock_adjustment_lines", + column: "AdjustmentId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_BatchId", + table: "stock_adjustment_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_BinId", + table: "stock_adjustment_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_ItemId", + table: "stock_adjustment_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_SerialId", + table: "stock_adjustment_lines", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_CreatedBy", + table: "stock_adjustments", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_DocNo", + table: "stock_adjustments", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_ReasonCodeId", + table: "stock_adjustments", + column: "ReasonCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_WarehouseId", + table: "stock_adjustments", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_BinId", + table: "stock_count_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_CountId", + table: "stock_count_lines", + column: "CountId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_ItemId", + table: "stock_count_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_CreatedBy", + table: "stock_counts", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_DocNo", + table: "stock_counts", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_Status", + table: "stock_counts", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_WarehouseId", + table: "stock_counts", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_BatchId", + table: "stock_layers", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_GrnLineId", + table: "stock_layers", + column: "GrnLineId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_ItemId_WarehouseId_ReceiptDate_LayerId", + table: "stock_layers", + columns: new[] { "ItemId", "WarehouseId", "ReceiptDate", "LayerId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_SerialId", + table: "stock_layers", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_WarehouseId", + table: "stock_layers", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_BatchId", + table: "stock_ledger", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_BinId", + table: "stock_ledger", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_ItemId_WarehouseId_CreatedAt", + table: "stock_ledger", + columns: new[] { "ItemId", "WarehouseId", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_ItemId_WarehouseId_LedgerId", + table: "stock_ledger", + columns: new[] { "ItemId", "WarehouseId", "LedgerId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_SerialId", + table: "stock_ledger", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_SourceDocType_SourceDocId", + table: "stock_ledger", + columns: new[] { "SourceDocType", "SourceDocId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_UserId", + table: "stock_ledger", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_WarehouseId", + table: "stock_ledger", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_BatchId", + table: "stock_transfer_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_DestBinId", + table: "stock_transfer_lines", + column: "DestBinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_ItemId", + table: "stock_transfer_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_SerialId", + table: "stock_transfer_lines", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_SrcBinId", + table: "stock_transfer_lines", + column: "SrcBinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_TransferId", + table: "stock_transfer_lines", + column: "TransferId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_CreatedBy", + table: "stock_transfers", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_DestWarehouseId", + table: "stock_transfers", + column: "DestWarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_DocNo", + table: "stock_transfers", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_SrcWarehouseId", + table: "stock_transfers", + column: "SrcWarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_Status", + table: "stock_transfers", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_uom_conversions_FromUomId", + table: "uom_conversions", + column: "FromUomId"); + + migrationBuilder.CreateIndex( + name: "IX_uom_conversions_ItemId_FromUomId_ToUomId", + table: "uom_conversions", + columns: new[] { "ItemId", "FromUomId", "ToUomId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_uom_conversions_ToUomId", + table: "uom_conversions", + column: "ToUomId"); + + migrationBuilder.CreateIndex( + name: "IX_uoms_Name", + table: "uoms", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_users_auth_user_id", + table: "users", + column: "auth_user_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_users_Username", + table: "users", + column: "Username", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_vendor_quotation_lines_ItemId", + table: "vendor_quotation_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_vendor_quotation_lines_QuotationId", + table: "vendor_quotation_lines", + column: "QuotationId"); + + migrationBuilder.CreateIndex( + name: "IX_vendor_quotations_RfqId_VendorId", + table: "vendor_quotations", + columns: new[] { "RfqId", "VendorId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_vendor_quotations_VendorId", + table: "vendor_quotations", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_vendors_Code", + table: "vendors", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_vendors_Status", + table: "vendors", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_warehouses_Code", + table: "warehouses", + column: "Code", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "audit_logs"); + + migrationBuilder.DropTable( + name: "item_reorders"); + + migrationBuilder.DropTable( + name: "journal_entry_stubs"); + + migrationBuilder.DropTable( + name: "number_sequences"); + + migrationBuilder.DropTable( + name: "purchase_return_lines"); + + migrationBuilder.DropTable( + name: "requisition_lines"); + + migrationBuilder.DropTable( + name: "rfq_lines"); + + migrationBuilder.DropTable( + name: "stock_adjustment_lines"); + + migrationBuilder.DropTable( + name: "stock_count_lines"); + + migrationBuilder.DropTable( + name: "stock_layers"); + + migrationBuilder.DropTable( + name: "stock_ledger"); + + migrationBuilder.DropTable( + name: "stock_transfer_lines"); + + migrationBuilder.DropTable( + name: "uom_conversions"); + + migrationBuilder.DropTable( + name: "vendor_quotation_lines"); + + migrationBuilder.DropTable( + name: "purchase_returns"); + + migrationBuilder.DropTable( + name: "stock_adjustments"); + + migrationBuilder.DropTable( + name: "stock_counts"); + + migrationBuilder.DropTable( + name: "grn_lines"); + + migrationBuilder.DropTable( + name: "serials"); + + migrationBuilder.DropTable( + name: "stock_transfers"); + + migrationBuilder.DropTable( + name: "vendor_quotations"); + + migrationBuilder.DropTable( + name: "reason_codes"); + + migrationBuilder.DropTable( + name: "batches"); + + migrationBuilder.DropTable( + name: "bins"); + + migrationBuilder.DropTable( + name: "grns"); + + migrationBuilder.DropTable( + name: "po_lines"); + + migrationBuilder.DropTable( + name: "rfqs"); + + migrationBuilder.DropTable( + name: "items"); + + migrationBuilder.DropTable( + name: "purchase_orders"); + + migrationBuilder.DropTable( + name: "warehouses"); + + migrationBuilder.DropTable( + name: "categories"); + + migrationBuilder.DropTable( + name: "uoms"); + + migrationBuilder.DropTable( + name: "requisitions"); + + migrationBuilder.DropTable( + name: "vendors"); + + migrationBuilder.DropTable( + name: "users"); + } + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs b/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs index 099275e..92abc54 100644 --- a/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs +++ b/Backend/ERPCore/Infra/Persistence/Migrations/ErpDbContextModelSnapshot.cs @@ -24,11 +24,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => { - b.Property("AuditId") + b.Property("AuditId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); b.Property("Action") .IsRequired() @@ -42,16 +42,16 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("EntityId") - .HasColumnType("bigint"); + b.Property("EntityId") + .HasColumnType("integer"); b.Property("EntityType") .IsRequired() .HasMaxLength(80) .HasColumnType("character varying(80)"); - b.Property("UserId") - .HasColumnType("bigint"); + b.Property("UserId") + .HasColumnType("integer"); b.HasKey("AuditId"); @@ -66,11 +66,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => { - b.Property("BatchId") + b.Property("BatchId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); b.Property("BatchNo") .IsRequired() @@ -80,8 +80,8 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("ExpiryDate") .HasColumnType("date"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.HasKey("BatchId"); @@ -93,11 +93,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => { - b.Property("BinId") + b.Property("BinId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); b.Property("BinType") .HasMaxLength(50) @@ -108,8 +108,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(50) .HasColumnType("character varying(50)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("BinId"); @@ -121,19 +121,19 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => { - b.Property("CategoryId") + b.Property("CategoryId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); b.Property("Name") .IsRequired() .HasMaxLength(200) .HasColumnType("character varying(200)"); - b.Property("ParentId") - .HasColumnType("bigint"); + b.Property("ParentId") + .HasColumnType("integer"); b.HasKey("CategoryId"); @@ -144,25 +144,25 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => { - b.Property("GrnId") + b.Property("GrnId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("PoId") - .HasColumnType("bigint"); + b.Property("PoId") + .HasColumnType("integer"); b.Property("PostedAt") .HasColumnType("timestamp with time zone"); @@ -178,11 +178,11 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("VendorId") - .HasColumnType("bigint"); + b.Property("VendorId") + .HasColumnType("integer"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("GrnId"); @@ -204,31 +204,31 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => { - b.Property("GrnLineId") + b.Property("GrnLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("BinId") - .HasColumnType("bigint"); + b.Property("BinId") + .HasColumnType("integer"); - b.Property("GrnId") - .HasColumnType("bigint"); + b.Property("GrnId") + .HasColumnType("integer"); b.Property("HoldStatus") .IsRequired() .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); - b.Property("PoLineId") - .HasColumnType("bigint"); + b.Property("PoLineId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) @@ -242,8 +242,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 6) .HasColumnType("numeric(18,6)"); - b.Property("UomId") - .HasColumnType("bigint"); + b.Property("UomId") + .HasColumnType("integer"); b.HasKey("GrnLineId"); @@ -264,23 +264,23 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => { - b.Property("ItemId") + b.Property("ItemId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); - b.Property("BaseUomId") - .HasColumnType("bigint"); + b.Property("BaseUomId") + .HasColumnType("integer"); - b.Property("CategoryId") - .HasColumnType("bigint"); + b.Property("CategoryId") + .HasColumnType("integer"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("DefaultVendorId") - .HasColumnType("bigint"); + b.Property("DefaultVendorId") + .HasColumnType("integer"); b.Property("Description") .HasMaxLength(1000) @@ -344,14 +344,14 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => { - b.Property("ReorderId") + b.Property("ReorderId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("ReorderPoint") .HasPrecision(18, 4) @@ -361,8 +361,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("ReorderId"); @@ -376,11 +376,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => { - b.Property("JournalId") + b.Property("JournalId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); b.Property("Amount") .HasPrecision(18, 4) @@ -396,8 +396,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("SourceDocId") - .HasColumnType("bigint"); + b.Property("SourceDocId") + .HasColumnType("integer"); b.Property("SourceDocType") .IsRequired() @@ -413,11 +413,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => { - b.Property("SequenceId") + b.Property("SequenceId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); b.Property("DocType") .IsRequired() @@ -425,8 +425,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasColumnType("character varying(10)") .HasColumnName("doc_type"); - b.Property("LastNumber") - .HasColumnType("bigint") + b.Property("LastNumber") + .HasColumnType("integer") .HasColumnName("last_number"); b.Property("Year") @@ -443,17 +443,17 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => { - b.Property("PoLineId") + b.Property("PoLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); - b.Property("PoId") - .HasColumnType("bigint"); + b.Property("PoId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) @@ -471,11 +471,11 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("UomId") - .HasColumnType("bigint"); + b.Property("UomId") + .HasColumnType("integer"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("PoLineId"); @@ -492,11 +492,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => { - b.Property("PoId") + b.Property("PoId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); b.Property("ApprovalRequired") .HasColumnType("boolean"); @@ -504,16 +504,16 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("RequisitionId") - .HasColumnType("bigint"); + b.Property("RequisitionId") + .HasColumnType("integer"); b.Property("RowVersion") .IsConcurrencyToken() @@ -529,8 +529,8 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); - b.Property("VendorId") - .HasColumnType("bigint"); + b.Property("VendorId") + .HasColumnType("integer"); b.HasKey("PoId"); @@ -550,36 +550,36 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => { - b.Property("ReturnId") + b.Property("ReturnId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("ReasonCodeId") - .HasColumnType("bigint"); + b.Property("ReasonCodeId") + .HasColumnType("integer"); b.Property("Status") .IsRequired() .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("VendorId") - .HasColumnType("bigint"); + b.Property("VendorId") + .HasColumnType("integer"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("ReturnId"); @@ -599,24 +599,24 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => { - b.Property("ReturnLineId") + b.Property("ReturnLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); - b.Property("GrnLineId") - .HasColumnType("bigint"); + b.Property("GrnLineId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("ReturnId") - .HasColumnType("bigint"); + b.Property("ReturnId") + .HasColumnType("integer"); b.HasKey("ReturnLineId"); @@ -631,11 +631,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => { - b.Property("ReasonCodeId") + b.Property("ReasonCodeId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); b.Property("Code") .IsRequired() @@ -662,11 +662,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => { - b.Property("RequisitionId") + b.Property("RequisitionId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); @@ -676,8 +676,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("RequestedBy") - .HasColumnType("bigint"); + b.Property("RequestedBy") + .HasColumnType("integer"); b.Property("Status") .IsRequired() @@ -698,14 +698,14 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => { - b.Property("ReqLineId") + b.Property("ReqLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) @@ -714,8 +714,8 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("RequiredBy") .HasColumnType("date"); - b.Property("RequisitionId") - .HasColumnType("bigint"); + b.Property("RequisitionId") + .HasColumnType("integer"); b.HasKey("ReqLineId"); @@ -728,11 +728,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => { - b.Property("RfqId") + b.Property("RfqId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); @@ -742,8 +742,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("RequisitionId") - .HasColumnType("bigint"); + b.Property("RequisitionId") + .HasColumnType("integer"); b.Property("Status") .IsRequired() @@ -762,21 +762,21 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => { - b.Property("RfqLineId") + b.Property("RfqLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("RfqId") - .HasColumnType("bigint"); + b.Property("RfqId") + .HasColumnType("integer"); b.HasKey("RfqLineId"); @@ -789,14 +789,14 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => { - b.Property("SerialId") + b.Property("SerialId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("SerialNo") .IsRequired() @@ -818,25 +818,25 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => { - b.Property("AdjustmentId") + b.Property("AdjustmentId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() .HasMaxLength(30) .HasColumnType("character varying(30)"); - b.Property("ReasonCodeId") - .HasColumnType("bigint"); + b.Property("ReasonCodeId") + .HasColumnType("integer"); b.Property("RowVersion") .IsConcurrencyToken() @@ -849,8 +849,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("AdjustmentId"); @@ -868,30 +868,30 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => { - b.Property("AdjLineId") + b.Property("AdjLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); - b.Property("AdjustmentId") - .HasColumnType("bigint"); + b.Property("AdjustmentId") + .HasColumnType("integer"); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("BinId") - .HasColumnType("bigint"); + b.Property("BinId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("QtyDelta") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("SerialId") - .HasColumnType("bigint"); + b.Property("SerialId") + .HasColumnType("integer"); b.HasKey("AdjLineId"); @@ -910,11 +910,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => { - b.Property("CountId") + b.Property("CountId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); b.Property("CountType") .IsRequired() @@ -924,8 +924,8 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() @@ -943,8 +943,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("CountId"); @@ -962,24 +962,24 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => { - b.Property("CountLineId") + b.Property("CountLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); - b.Property("BinId") - .HasColumnType("bigint"); + b.Property("BinId") + .HasColumnType("integer"); - b.Property("CountId") - .HasColumnType("bigint"); + b.Property("CountId") + .HasColumnType("integer"); b.Property("CountedQty") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("SystemQty") .HasPrecision(18, 4) @@ -1002,20 +1002,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => { - b.Property("LayerId") + b.Property("LayerId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("GrnLineId") - .HasColumnType("bigint"); + b.Property("GrnLineId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("QtyReceived") .HasPrecision(18, 4) @@ -1028,15 +1028,15 @@ namespace ERPCore.Infra.Persistence.Migrations b.Property("ReceiptDate") .HasColumnType("timestamp with time zone"); - b.Property("SerialId") - .HasColumnType("bigint"); + b.Property("SerialId") + .HasColumnType("integer"); b.Property("UnitCost") .HasPrecision(18, 6) .HasColumnType("numeric(18,6)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("LayerId"); @@ -1055,17 +1055,17 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => { - b.Property("LedgerId") + b.Property("LedgerId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("BinId") - .HasColumnType("bigint"); + b.Property("BinId") + .HasColumnType("integer"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); @@ -1075,8 +1075,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasMaxLength(5) .HasColumnType("character varying(5)"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("QtyBase") .HasPrecision(18, 4) @@ -1086,11 +1086,11 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("SerialId") - .HasColumnType("bigint"); + b.Property("SerialId") + .HasColumnType("integer"); - b.Property("SourceDocId") - .HasColumnType("bigint"); + b.Property("SourceDocId") + .HasColumnType("integer"); b.Property("SourceDocType") .IsRequired() @@ -1101,15 +1101,15 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 6) .HasColumnType("numeric(18,6)"); - b.Property("UserId") - .HasColumnType("bigint"); + b.Property("UserId") + .HasColumnType("integer"); b.Property("Value") .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("WarehouseId") - .HasColumnType("bigint"); + b.Property("WarehouseId") + .HasColumnType("integer"); b.HasKey("LedgerId"); @@ -1134,20 +1134,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => { - b.Property("TransferId") + b.Property("TransferId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("CreatedBy") - .HasColumnType("bigint"); + b.Property("CreatedBy") + .HasColumnType("integer"); - b.Property("DestWarehouseId") - .HasColumnType("bigint"); + b.Property("DestWarehouseId") + .HasColumnType("integer"); b.Property("DocNo") .IsRequired() @@ -1160,8 +1160,8 @@ namespace ERPCore.Infra.Persistence.Migrations .HasColumnType("xid") .HasColumnName("xmin"); - b.Property("SrcWarehouseId") - .HasColumnType("bigint"); + b.Property("SrcWarehouseId") + .HasColumnType("integer"); b.Property("Status") .IsRequired() @@ -1186,20 +1186,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => { - b.Property("TransferLineId") + b.Property("TransferLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); - b.Property("BatchId") - .HasColumnType("bigint"); + b.Property("BatchId") + .HasColumnType("integer"); - b.Property("DestBinId") - .HasColumnType("bigint"); + b.Property("DestBinId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("Qty") .HasPrecision(18, 4) @@ -1209,14 +1209,14 @@ namespace ERPCore.Infra.Persistence.Migrations .HasPrecision(18, 4) .HasColumnType("numeric(18,4)"); - b.Property("SerialId") - .HasColumnType("bigint"); + b.Property("SerialId") + .HasColumnType("integer"); - b.Property("SrcBinId") - .HasColumnType("bigint"); + b.Property("SrcBinId") + .HasColumnType("integer"); - b.Property("TransferId") - .HasColumnType("bigint"); + b.Property("TransferId") + .HasColumnType("integer"); b.Property("UnitCost") .HasPrecision(18, 6) @@ -1241,11 +1241,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => { - b.Property("UomId") + b.Property("UomId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); b.Property("Name") .IsRequired() @@ -1262,24 +1262,24 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => { - b.Property("ConversionId") + b.Property("ConversionId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); b.Property("Factor") .HasPrecision(18, 6) .HasColumnType("numeric(18,6)"); - b.Property("FromUomId") - .HasColumnType("bigint"); + b.Property("FromUomId") + .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); - b.Property("ToUomId") - .HasColumnType("bigint"); + b.Property("ToUomId") + .HasColumnType("integer"); b.HasKey("ConversionId"); @@ -1295,11 +1295,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.User", b => { - b.Property("UserId") + b.Property("UserId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); b.Property("AuthUserId") .HasColumnType("uuid") @@ -1333,7 +1333,7 @@ namespace ERPCore.Infra.Persistence.Migrations b.HasData( new { - UserId = 1L, + UserId = 1, DisplayName = "System", Status = "Active", Username = "system" @@ -1342,11 +1342,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => { - b.Property("VendorId") + b.Property("VendorId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); b.Property("Code") .IsRequired() @@ -1404,20 +1404,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => { - b.Property("QuotationId") + b.Property("QuotationId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("RfqId") - .HasColumnType("bigint"); + b.Property("RfqId") + .HasColumnType("integer"); - b.Property("VendorId") - .HasColumnType("bigint"); + b.Property("VendorId") + .HasColumnType("integer"); b.HasKey("QuotationId"); @@ -1431,20 +1431,20 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => { - b.Property("QuotationLineId") + b.Property("QuotationLineId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); - b.Property("ItemId") - .HasColumnType("bigint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("LeadDays") .HasColumnType("integer"); - b.Property("QuotationId") - .HasColumnType("bigint"); + b.Property("QuotationId") + .HasColumnType("integer"); b.Property("UnitPrice") .HasPrecision(18, 4) @@ -1461,11 +1461,11 @@ namespace ERPCore.Infra.Persistence.Migrations modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => { - b.Property("WarehouseId") + b.Property("WarehouseId") .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); b.Property("Code") .IsRequired() diff --git a/Backend/ERPCore/Program.cs b/Backend/ERPCore/Program.cs index e4fcd86..635ec52 100644 --- a/Backend/ERPCore/Program.cs +++ b/Backend/ERPCore/Program.cs @@ -36,7 +36,7 @@ builder.Services.AddExceptionHandler(); builder.Services.AddErpJwtAuth(builder.Configuration); // Current-user (audit actor). AuthHex has no sub/nameid → a claims transformation -// JIT-provisions a local shadow user and injects the local `long` id as `nameid`. +// JIT-provisions a local shadow user and injects the local `int` id as `nameid`. builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Backend/ERPCore/Properties/launchSettings.json b/Backend/ERPCore/Properties/launchSettings.json index 3d53c17..05cca77 100644 --- a/Backend/ERPCore/Properties/launchSettings.json +++ b/Backend/ERPCore/Properties/launchSettings.json @@ -4,7 +4,8 @@ "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, + "launchUrl": "swagger", "applicationUrl": "http://localhost:5224", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -13,7 +14,8 @@ "https": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, + "launchUrl": "swagger", "applicationUrl": "https://localhost:7112;http://localhost:5224", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Backend/ERPCore/Services/AuditService.cs b/Backend/ERPCore/Services/AuditService.cs index 49290a1..7492591 100644 --- a/Backend/ERPCore/Services/AuditService.cs +++ b/Backend/ERPCore/Services/AuditService.cs @@ -20,7 +20,7 @@ public sealed class AuditService : IAuditService } public async Task> ListLogsAsync( - string? entityType, long? entityId, long? userId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default) + string? entityType, int? entityId, int? userId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default) { var q = _logs.Query().AsNoTracking(); if (!string.IsNullOrWhiteSpace(entityType)) q = q.Where(l => l.EntityType == entityType); @@ -41,7 +41,7 @@ public sealed class AuditService : IAuditService } public async Task> ListJournalAsync( - string? sourceDocType, long? sourceDocId, PageQuery query, CancellationToken ct = default) + string? sourceDocType, int? sourceDocId, PageQuery query, CancellationToken ct = default) { var q = _journal.Query().AsNoTracking(); if (!string.IsNullOrWhiteSpace(sourceDocType)) q = q.Where(j => j.SourceDocType == sourceDocType); diff --git a/Backend/ERPCore/Services/CategoryService.cs b/Backend/ERPCore/Services/CategoryService.cs index 178aa36..ca0f05d 100644 --- a/Backend/ERPCore/Services/CategoryService.cs +++ b/Backend/ERPCore/Services/CategoryService.cs @@ -47,7 +47,7 @@ public sealed class CategoryService : ICategoryService var byParent = all.ToLookup(c => c.ParentId); - List Build(long? parentId) => + List Build(int? parentId) => byParent[parentId] .Select(c => new CategoryTreeDto(c.CategoryId, c.Name, c.ParentId, Build(c.CategoryId))) .ToList(); diff --git a/Backend/ERPCore/Services/CountService.cs b/Backend/ERPCore/Services/CountService.cs index 3e34b26..1c62ecb 100644 --- a/Backend/ERPCore/Services/CountService.cs +++ b/Backend/ERPCore/Services/CountService.cs @@ -49,7 +49,7 @@ public sealed class CountService : ICountService _uow = uow; } - public async Task GetAsync(long countId, CancellationToken ct = default) + public async Task GetAsync(int countId, CancellationToken ct = default) { var count = await _counts.Query().AsNoTracking().Include(c => c.Lines) .FirstOrDefaultAsync(c => c.CountId == countId, ct); @@ -94,7 +94,7 @@ public sealed class CountService : ICountService return Map(count); } - public async Task EnterCountsAsync(long countId, EnterCountsRequest request, CancellationToken ct = default) + public async Task EnterCountsAsync(int countId, EnterCountsRequest request, CancellationToken ct = default) { var count = await _counts.Query().Include(c => c.Lines) .FirstOrDefaultAsync(c => c.CountId == countId, ct) @@ -115,7 +115,7 @@ public sealed class CountService : ICountService return Map(count); } - public async Task PostAsync(long countId, CancellationToken ct = default) + public async Task PostAsync(int countId, CancellationToken ct = default) { var count = await _counts.Query().Include(c => c.Lines) .FirstOrDefaultAsync(c => c.CountId == countId, ct) @@ -135,7 +135,7 @@ public sealed class CountService : ICountService { count.Status = CountStatus.Posted; await _uow.SaveChangesAsync(ct); - return new CountPostResultDto(count.CountId, count.Status, null, Array.Empty()); + return new CountPostResultDto(count.CountId, count.Status, null, Array.Empty()); } var reason = await _reasonCodes.Query().AsNoTracking() diff --git a/Backend/ERPCore/Services/GrnService.cs b/Backend/ERPCore/Services/GrnService.cs index fdfa83e..891163f 100644 --- a/Backend/ERPCore/Services/GrnService.cs +++ b/Backend/ERPCore/Services/GrnService.cs @@ -65,7 +65,7 @@ public sealed class GrnService : IGrnService _uow = uow; } - public async Task GetAsync(long grnId, CancellationToken ct = default) + public async Task GetAsync(int grnId, CancellationToken ct = default) { var grn = await _grns.Query().AsNoTracking() .Include(g => g.Lines) @@ -80,7 +80,7 @@ public sealed class GrnService : IGrnService // Resolve vendor + PO context. PurchaseOrder? po = null; - long vendorId; + int vendorId; if (request.PoId is not null) { po = await _pos.Query().AsNoTracking().Include(p => p.Lines) @@ -100,7 +100,7 @@ public sealed class GrnService : IGrnService } var lines = new List(); - var batchCache = new Dictionary<(long ItemId, string BatchNo), Batch>(); + var batchCache = new Dictionary<(int ItemId, string BatchNo), Batch>(); foreach (var input in request.Lines) { @@ -169,7 +169,7 @@ public sealed class GrnService : IGrnService return Map(grn); } - public async Task ConfirmAsync(long grnId, string? idempotencyKey, CancellationToken ct = default) + public async Task ConfirmAsync(int grnId, string? idempotencyKey, CancellationToken ct = default) { var grn = await _grns.Query() .Include(g => g.Lines) @@ -184,7 +184,7 @@ public sealed class GrnService : IGrnService var actor = _currentUser.AuditUserId; var now = DateTime.UtcNow; - var runningBalance = new Dictionary<(long, long), decimal>(); + var runningBalance = new Dictionary<(int, int), decimal>(); var createdLayers = new List(); var ledgerRefs = new List(); @@ -232,7 +232,7 @@ public sealed class GrnService : IGrnService await GetPoStatusAsync(grn.PoId, ct)); } - public async Task ReleaseLineAsync(long grnId, long grnLineId, string action, CancellationToken ct = default) + public async Task ReleaseLineAsync(int grnId, int grnLineId, string action, CancellationToken ct = default) { var grn = await _grns.Query() .Include(g => g.Lines) @@ -279,7 +279,7 @@ public sealed class GrnService : IGrnService } private async Task ResolveBatchAsync( - Item item, BatchInput? batch, Dictionary<(long, string), Batch> cache, CancellationToken ct) + Item item, BatchInput? batch, Dictionary<(int, string), Batch> cache, CancellationToken ct) { if (item.TrackingMode == TrackingMode.Batch && batch is null) throw new DomainException(ErrorCodes.Validation, $"Item {item.ItemId} is batch-tracked; a batch is required.", 422); @@ -302,7 +302,7 @@ public sealed class GrnService : IGrnService } private async Task<(decimal QtyBase, decimal UnitCostBase)> ToBaseAsync( - Item item, long uomId, decimal qty, decimal unitCostPerUom, CancellationToken ct) + Item item, int uomId, decimal qty, decimal unitCostPerUom, CancellationToken ct) { if (uomId == item.BaseUomId) return (qty, unitCostPerUom); @@ -315,7 +315,7 @@ public sealed class GrnService : IGrnService return (qty * conv.Factor, unitCostPerUom / conv.Factor); } - private async Task UpdatePoStatusAsync(long? poId, CancellationToken ct) + private async Task UpdatePoStatusAsync(int? poId, CancellationToken ct) { if (poId is null) return; var po = await _pos.Query().Include(p => p.Lines).FirstOrDefaultAsync(p => p.PoId == poId, ct); @@ -327,7 +327,7 @@ public sealed class GrnService : IGrnService po.UpdatedAt = DateTime.UtcNow; } - private async Task GetPoStatusAsync(long? poId, CancellationToken ct) + private async Task GetPoStatusAsync(int? poId, CancellationToken ct) { if (poId is null) return null; return await _pos.Query().AsNoTracking().Where(p => p.PoId == poId).Select(p => (PurchaseOrderStatus?)p.Status).FirstOrDefaultAsync(ct); diff --git a/Backend/ERPCore/Services/Interfaces/IAuditService.cs b/Backend/ERPCore/Services/Interfaces/IAuditService.cs index c9b503d..737aaeb 100644 --- a/Backend/ERPCore/Services/Interfaces/IAuditService.cs +++ b/Backend/ERPCore/Services/Interfaces/IAuditService.cs @@ -10,8 +10,8 @@ namespace ERPCore.Services.Interfaces; public interface IAuditService { Task> ListLogsAsync( - string? entityType, long? entityId, long? userId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default); + string? entityType, int? entityId, int? userId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default); Task> ListJournalAsync( - string? sourceDocType, long? sourceDocId, PageQuery query, CancellationToken ct = default); + string? sourceDocType, int? sourceDocId, PageQuery query, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/ICountService.cs b/Backend/ERPCore/Services/Interfaces/ICountService.cs index 51d89cc..667796e 100644 --- a/Backend/ERPCore/Services/Interfaces/ICountService.cs +++ b/Backend/ERPCore/Services/Interfaces/ICountService.cs @@ -5,8 +5,8 @@ namespace ERPCore.Services.Interfaces; /// Cycle/full stock-count business logic (docs/11 §5.6; FR-STK-08). public interface ICountService { - Task GetAsync(long countId, CancellationToken ct = default); + Task GetAsync(int countId, CancellationToken ct = default); Task CreateAsync(CreateCountRequest request, CancellationToken ct = default); - Task EnterCountsAsync(long countId, EnterCountsRequest request, CancellationToken ct = default); - Task PostAsync(long countId, CancellationToken ct = default); + Task EnterCountsAsync(int countId, EnterCountsRequest request, CancellationToken ct = default); + Task PostAsync(int countId, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs b/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs index ef78632..0f85c37 100644 --- a/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs +++ b/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs @@ -15,7 +15,7 @@ public interface IFifoCostingService { /// Create an inbound FIFO layer (qty and unit cost in the item's base UOM). Task CreateInboundLayerAsync( - long itemId, long warehouseId, long? batchId, long? serialId, long? grnLineId, + int itemId, int warehouseId, int? batchId, int? serialId, int? grnLineId, decimal qtyBase, decimal unitCost, DateTime receiptDate, CancellationToken ct = default); /// @@ -27,20 +27,20 @@ public interface IFifoCostingService /// and ledger costing). Must run inside a UoW transaction. /// Task> ConsumeAsync( - long itemId, long warehouseId, long? batchId, decimal qtyBase, CancellationToken ct = default); + int itemId, int warehouseId, int? batchId, decimal qtyBase, CancellationToken ct = default); /// Append an immutable ledger entry (value = qtyBase × unitCost). Task PostLedgerAsync( - long itemId, long warehouseId, long? binId, long? batchId, long? serialId, long userId, + int itemId, int warehouseId, int? binId, int? batchId, int? serialId, int userId, Direction direction, decimal qtyBase, decimal unitCost, decimal runningBalance, - string sourceDocType, long sourceDocId, DateTime createdAt, CancellationToken ct = default); + string sourceDocType, int sourceDocId, DateTime createdAt, CancellationToken ct = default); /// Current on-hand (Σ open-layer qtyRemaining) for an item at a warehouse. - Task GetOnHandAsync(long itemId, long warehouseId, CancellationToken ct = default); + Task GetOnHandAsync(int itemId, int warehouseId, CancellationToken ct = default); /// Valuation over open layers: Σ(qtyRemaining × unitCost) (FR-STK-04). - Task GetValuationAsync(long itemId, long warehouseId, CancellationToken ct = default); + Task GetValuationAsync(int itemId, int warehouseId, CancellationToken ct = default); } /// A quantity consumed from one FIFO layer at that layer's unit cost. -public sealed record ConsumedSegment(long LayerId, decimal Qty, decimal UnitCost); +public sealed record ConsumedSegment(int LayerId, decimal Qty, decimal UnitCost); diff --git a/Backend/ERPCore/Services/Interfaces/IGrnService.cs b/Backend/ERPCore/Services/Interfaces/IGrnService.cs index a02d883..92c0dc7 100644 --- a/Backend/ERPCore/Services/Interfaces/IGrnService.cs +++ b/Backend/ERPCore/Services/Interfaces/IGrnService.cs @@ -5,11 +5,11 @@ namespace ERPCore.Services.Interfaces; /// Goods-receipt business logic (docs/11 §4; FR-GRN-01..08). public interface IGrnService { - Task GetAsync(long grnId, CancellationToken ct = default); + Task GetAsync(int grnId, CancellationToken ct = default); Task CreateAsync(CreateGrnRequest request, CancellationToken ct = default); /// Confirm: create FIFO layers + inbound ledger + update PO receipts, atomically. - Task ConfirmAsync(long grnId, string? idempotencyKey, CancellationToken ct = default); + Task ConfirmAsync(int grnId, string? idempotencyKey, CancellationToken ct = default); - Task ReleaseLineAsync(long grnId, long grnLineId, string action, CancellationToken ct = default); + Task ReleaseLineAsync(int grnId, int grnLineId, string action, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IItemService.cs b/Backend/ERPCore/Services/Interfaces/IItemService.cs index 662db49..b405f27 100644 --- a/Backend/ERPCore/Services/Interfaces/IItemService.cs +++ b/Backend/ERPCore/Services/Interfaces/IItemService.cs @@ -12,17 +12,17 @@ namespace ERPCore.Services.Interfaces; public interface IItemService { Task> ListAsync( - PageQuery query, EntityStatus? status, long? categoryId, TrackingMode? trackingMode, CancellationToken ct = default); + PageQuery query, EntityStatus? status, int? categoryId, TrackingMode? trackingMode, CancellationToken ct = default); - Task?> GetAsync(long itemId, CancellationToken ct = default); + Task?> GetAsync(int itemId, CancellationToken ct = default); Task> CreateAsync(CreateItemRequest request, CancellationToken ct = default); - Task> UpdateAsync(long itemId, UpdateItemRequest request, uint expectedRowVersion, CancellationToken ct = default); + Task> UpdateAsync(int itemId, UpdateItemRequest request, uint expectedRowVersion, CancellationToken ct = default); - Task SetStatusAsync(long itemId, EntityStatus status, CancellationToken ct = default); + Task SetStatusAsync(int itemId, EntityStatus status, CancellationToken ct = default); - Task UpdateReorderAsync(long itemId, UpdateReorderRequest request, CancellationToken ct = default); + Task UpdateReorderAsync(int itemId, UpdateReorderRequest request, CancellationToken ct = default); - Task UpdateUomConversionsAsync(long itemId, UpdateUomConversionsRequest request, CancellationToken ct = default); + Task UpdateUomConversionsAsync(int itemId, UpdateUomConversionsRequest request, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IPurchaseOrderService.cs b/Backend/ERPCore/Services/Interfaces/IPurchaseOrderService.cs index b430a03..898588b 100644 --- a/Backend/ERPCore/Services/Interfaces/IPurchaseOrderService.cs +++ b/Backend/ERPCore/Services/Interfaces/IPurchaseOrderService.cs @@ -9,11 +9,11 @@ namespace ERPCore.Services.Interfaces; public interface IPurchaseOrderService { Task> ListAsync( - PageQuery query, PurchaseOrderStatus? status, long? vendorId, CancellationToken ct = default); + PageQuery query, PurchaseOrderStatus? status, int? vendorId, CancellationToken ct = default); - Task?> GetAsync(long poId, CancellationToken ct = default); + Task?> GetAsync(int poId, CancellationToken ct = default); Task> CreateAsync(CreatePurchaseOrderRequest request, CancellationToken ct = default); - Task> UpdateAsync(long poId, UpdatePurchaseOrderRequest request, uint expectedRowVersion, CancellationToken ct = default); - Task ApproveAsync(long poId, CancellationToken ct = default); - Task CancelAsync(long poId, string? reason, CancellationToken ct = default); + Task> UpdateAsync(int poId, UpdatePurchaseOrderRequest request, uint expectedRowVersion, CancellationToken ct = default); + Task ApproveAsync(int poId, CancellationToken ct = default); + Task CancelAsync(int poId, string? reason, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IReorderService.cs b/Backend/ERPCore/Services/Interfaces/IReorderService.cs index dbb4fd0..f41258c 100644 --- a/Backend/ERPCore/Services/Interfaces/IReorderService.cs +++ b/Backend/ERPCore/Services/Interfaces/IReorderService.cs @@ -7,6 +7,6 @@ namespace ERPCore.Services.Interfaces; /// Reorder alerts and suggested requisitions (docs/11 §5.7; FR-STK-10). public interface IReorderService { - Task> GetAlertsAsync(long? warehouseId, PageQuery query, CancellationToken ct = default); - Task CreateSuggestedRequisitionAsync(long itemId, long warehouseId, CancellationToken ct = default); + Task> GetAlertsAsync(int? warehouseId, PageQuery query, CancellationToken ct = default); + Task CreateSuggestedRequisitionAsync(int itemId, int warehouseId, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IRequisitionService.cs b/Backend/ERPCore/Services/Interfaces/IRequisitionService.cs index 5c708db..309645d 100644 --- a/Backend/ERPCore/Services/Interfaces/IRequisitionService.cs +++ b/Backend/ERPCore/Services/Interfaces/IRequisitionService.cs @@ -7,7 +7,7 @@ namespace ERPCore.Services.Interfaces; public interface IRequisitionService { Task> ListAsync(PageQuery query, CancellationToken ct = default); - Task GetAsync(long requisitionId, CancellationToken ct = default); + Task GetAsync(int requisitionId, CancellationToken ct = default); Task CreateAsync(CreateRequisitionRequest request, CancellationToken ct = default); - Task SubmitAsync(long requisitionId, CancellationToken ct = default); + Task SubmitAsync(int requisitionId, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IRfqService.cs b/Backend/ERPCore/Services/Interfaces/IRfqService.cs index bdeef9a..9074dd1 100644 --- a/Backend/ERPCore/Services/Interfaces/IRfqService.cs +++ b/Backend/ERPCore/Services/Interfaces/IRfqService.cs @@ -5,8 +5,8 @@ namespace ERPCore.Services.Interfaces; /// RFQ & vendor-quotation business logic (docs/11 §3.2). public interface IRfqService { - Task GetAsync(long rfqId, CancellationToken ct = default); + Task GetAsync(int rfqId, CancellationToken ct = default); Task CreateAsync(CreateRfqRequest request, CancellationToken ct = default); - Task AddQuotationAsync(long rfqId, CreateQuotationRequest request, CancellationToken ct = default); - Task GetComparisonAsync(long rfqId, CancellationToken ct = default); + Task AddQuotationAsync(int rfqId, CreateQuotationRequest request, CancellationToken ct = default); + Task GetComparisonAsync(int rfqId, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IStockMutator.cs b/Backend/ERPCore/Services/Interfaces/IStockMutator.cs index f603cc5..1614d19 100644 --- a/Backend/ERPCore/Services/Interfaces/IStockMutator.cs +++ b/Backend/ERPCore/Services/Interfaces/IStockMutator.cs @@ -3,7 +3,7 @@ using ERPCore.Domain.Entities; namespace ERPCore.Services.Interfaces; /// A signed base-UOM change to an item's stock at one warehouse. -public sealed record StockDelta(long ItemId, long? BinId, long? BatchId, decimal QtyDelta); +public sealed record StockDelta(int ItemId, int? BinId, int? BatchId, decimal QtyDelta); /// /// Shared poster for stock-affecting documents (adjustments, count variances, @@ -16,6 +16,6 @@ public sealed record StockDelta(long ItemId, long? BinId, long? BatchId, decimal public interface IStockMutator { Task> ApplyAsync( - long warehouseId, string sourceDocType, long sourceDocId, DateTime now, + int warehouseId, string sourceDocType, int sourceDocId, DateTime now, IReadOnlyList deltas, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IStockService.cs b/Backend/ERPCore/Services/Interfaces/IStockService.cs index d8cc6d1..3fdaf78 100644 --- a/Backend/ERPCore/Services/Interfaces/IStockService.cs +++ b/Backend/ERPCore/Services/Interfaces/IStockService.cs @@ -6,10 +6,10 @@ namespace ERPCore.Services.Interfaces; /// Read-side stock enquiry, ledger and valuation (docs/11 §5.1–5.3). public interface IStockService { - Task GetOnHandAsync(long itemId, long warehouseId, CancellationToken ct = default); + Task GetOnHandAsync(int itemId, int warehouseId, CancellationToken ct = default); Task> GetLedgerAsync( - long? itemId, long? warehouseId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default); + int? itemId, int? warehouseId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default); - Task GetValuationAsync(long itemId, long warehouseId, CancellationToken ct = default); + Task GetValuationAsync(int itemId, int warehouseId, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/ITransferService.cs b/Backend/ERPCore/Services/Interfaces/ITransferService.cs index 6eceb63..44707e2 100644 --- a/Backend/ERPCore/Services/Interfaces/ITransferService.cs +++ b/Backend/ERPCore/Services/Interfaces/ITransferService.cs @@ -5,12 +5,12 @@ namespace ERPCore.Services.Interfaces; /// Inter-warehouse transfer business logic (docs/11 §5.4; FR-STK-05/06). public interface ITransferService { - Task GetAsync(long transferId, CancellationToken ct = default); + Task GetAsync(int transferId, CancellationToken ct = default); Task CreateAsync(CreateTransferRequest request, CancellationToken ct = default); /// Dispatch: consume source FIFO layers into in-transit (row-locked). - Task DispatchAsync(long transferId, CancellationToken ct = default); + Task DispatchAsync(int transferId, CancellationToken ct = default); /// Receive: create the destination layer at the inherited (cost-preserving) cost. - Task ReceiveAsync(long transferId, ReceiveTransferRequest request, CancellationToken ct = default); + Task ReceiveAsync(int transferId, ReceiveTransferRequest request, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IVendorService.cs b/Backend/ERPCore/Services/Interfaces/IVendorService.cs index 9d2c59b..bc78b41 100644 --- a/Backend/ERPCore/Services/Interfaces/IVendorService.cs +++ b/Backend/ERPCore/Services/Interfaces/IVendorService.cs @@ -9,8 +9,8 @@ namespace ERPCore.Services.Interfaces; public interface IVendorService { Task> ListAsync(PageQuery query, EntityStatus? status, CancellationToken ct = default); - Task?> GetAsync(long vendorId, CancellationToken ct = default); + Task?> GetAsync(int vendorId, CancellationToken ct = default); Task> CreateAsync(CreateVendorRequest request, CancellationToken ct = default); - Task> UpdateAsync(long vendorId, UpdateVendorRequest request, uint expectedRowVersion, CancellationToken ct = default); - Task SetStatusAsync(long vendorId, EntityStatus status, CancellationToken ct = default); + Task> UpdateAsync(int vendorId, UpdateVendorRequest request, uint expectedRowVersion, CancellationToken ct = default); + Task SetStatusAsync(int vendorId, EntityStatus status, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IWarehouseService.cs b/Backend/ERPCore/Services/Interfaces/IWarehouseService.cs index a1ab0ee..0e3b96b 100644 --- a/Backend/ERPCore/Services/Interfaces/IWarehouseService.cs +++ b/Backend/ERPCore/Services/Interfaces/IWarehouseService.cs @@ -7,9 +7,9 @@ namespace ERPCore.Services.Interfaces; public interface IWarehouseService { Task> ListAsync(PageQuery query, CancellationToken ct = default); - Task GetAsync(long warehouseId, CancellationToken ct = default); + Task GetAsync(int warehouseId, CancellationToken ct = default); Task CreateAsync(CreateWarehouseRequest request, CancellationToken ct = default); - Task> ListBinsAsync(long warehouseId, CancellationToken ct = default); - Task CreateBinAsync(long warehouseId, CreateBinRequest request, CancellationToken ct = default); + Task> ListBinsAsync(int warehouseId, CancellationToken ct = default); + Task CreateBinAsync(int warehouseId, CreateBinRequest request, CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/ItemService.cs b/Backend/ERPCore/Services/ItemService.cs index 487c65d..3a54e1c 100644 --- a/Backend/ERPCore/Services/ItemService.cs +++ b/Backend/ERPCore/Services/ItemService.cs @@ -42,7 +42,7 @@ public sealed class ItemService : IItemService } public async Task> ListAsync( - PageQuery query, EntityStatus? status, long? categoryId, TrackingMode? trackingMode, CancellationToken ct = default) + PageQuery query, EntityStatus? status, int? categoryId, TrackingMode? trackingMode, CancellationToken ct = default) { var q = _items.Query().AsNoTracking(); @@ -66,7 +66,7 @@ public sealed class ItemService : IItemService return PagedResponse.Create(rows, query.Page, query.PageSize, total); } - public async Task?> GetAsync(long itemId, CancellationToken ct = default) + public async Task?> GetAsync(int itemId, CancellationToken ct = default) { var item = await _items.Query().AsNoTracking() .Include(i => i.ReorderSettings) @@ -104,7 +104,7 @@ public sealed class ItemService : IItemService } public async Task> UpdateAsync( - long itemId, UpdateItemRequest request, uint expectedRowVersion, CancellationToken ct = default) + int itemId, UpdateItemRequest request, uint expectedRowVersion, CancellationToken ct = default) { var item = await _items.Query() .Include(i => i.ReorderSettings) @@ -135,7 +135,7 @@ public sealed class ItemService : IItemService return new ETagged(ToDetail(item), item.RowVersion); } - public async Task SetStatusAsync(long itemId, EntityStatus status, CancellationToken ct = default) + public async Task SetStatusAsync(int itemId, EntityStatus status, CancellationToken ct = default) { var item = await _items.GetByIdAsync(itemId, ct) ?? throw new NotFoundException($"Item {itemId} was not found."); @@ -146,7 +146,7 @@ public sealed class ItemService : IItemService } public async Task UpdateReorderAsync( - long itemId, UpdateReorderRequest request, CancellationToken ct = default) + int itemId, UpdateReorderRequest request, CancellationToken ct = default) { if (request.Settings.Select(s => s.WarehouseId).Distinct().Count() != request.Settings.Count) throw new DomainException(ErrorCodes.Validation, "Duplicate warehouseId in reorder settings.", 400); @@ -194,7 +194,7 @@ public sealed class ItemService : IItemService } public async Task UpdateUomConversionsAsync( - long itemId, UpdateUomConversionsRequest request, CancellationToken ct = default) + int itemId, UpdateUomConversionsRequest request, CancellationToken ct = default) { var pairs = request.Conversions.Select(c => (c.FromUom, c.ToUom)).ToList(); if (pairs.Distinct().Count() != pairs.Count) @@ -240,7 +240,7 @@ public sealed class ItemService : IItemService return new ItemUomConversionsDto(item.ItemId, item.BaseUomId, conversions); } - private async Task ValidateReferencesAsync(long categoryId, long baseUomId, long? defaultVendorId, CancellationToken ct) + private async Task ValidateReferencesAsync(int categoryId, int baseUomId, int? defaultVendorId, CancellationToken ct) { if (!await _categories.Query().AnyAsync(c => c.CategoryId == categoryId, ct)) throw new DomainException(ErrorCodes.Validation, $"Category {categoryId} does not exist.", 422); diff --git a/Backend/ERPCore/Services/PurchaseOrderService.cs b/Backend/ERPCore/Services/PurchaseOrderService.cs index 9b74f93..cae9d35 100644 --- a/Backend/ERPCore/Services/PurchaseOrderService.cs +++ b/Backend/ERPCore/Services/PurchaseOrderService.cs @@ -49,7 +49,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService } public async Task> ListAsync( - PageQuery query, PurchaseOrderStatus? status, long? vendorId, CancellationToken ct = default) + PageQuery query, PurchaseOrderStatus? status, int? vendorId, CancellationToken ct = default) { var q = _pos.Query().AsNoTracking().Include(p => p.Lines).AsQueryable(); if (!string.IsNullOrWhiteSpace(query.Q)) @@ -71,7 +71,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService return PagedResponse.Create(summaries, query.Page, query.PageSize, total); } - public async Task?> GetAsync(long poId, CancellationToken ct = default) + public async Task?> GetAsync(int poId, CancellationToken ct = default) { var po = await _pos.Query().AsNoTracking() .Include(p => p.Lines) @@ -107,7 +107,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService } public async Task> UpdateAsync( - long poId, UpdatePurchaseOrderRequest request, uint expectedRowVersion, CancellationToken ct = default) + int poId, UpdatePurchaseOrderRequest request, uint expectedRowVersion, CancellationToken ct = default) { var po = await _pos.Query() .Include(p => p.Lines) @@ -143,7 +143,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService return new ETagged(Map(po), po.RowVersion); } - public async Task ApproveAsync(long poId, CancellationToken ct = default) + public async Task ApproveAsync(int poId, CancellationToken ct = default) { var po = await _pos.Query() .Include(p => p.Lines) @@ -162,7 +162,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService return Map(po); } - public async Task CancelAsync(long poId, string? reason, CancellationToken ct = default) + public async Task CancelAsync(int poId, string? reason, CancellationToken ct = default) { var po = await _pos.Query() .Include(p => p.Lines) @@ -211,7 +211,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService } private async Task ValidateReferencesAsync( - long vendorId, long? requisitionId, IReadOnlyCollection lines, CancellationToken ct) + int vendorId, int? requisitionId, IReadOnlyCollection lines, CancellationToken ct) { var vendor = await _vendors.Query().AsNoTracking().FirstOrDefaultAsync(v => v.VendorId == vendorId, ct); if (vendor is null) @@ -229,7 +229,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService } private static async Task EnsureAllExistAsync( - IQueryable keySource, IEnumerable requested, string label, CancellationToken ct) + IQueryable keySource, IEnumerable requested, string label, CancellationToken ct) { var ids = requested.Distinct().ToList(); var found = await keySource.Where(k => ids.Contains(k)).ToListAsync(ct); diff --git a/Backend/ERPCore/Services/ReorderService.cs b/Backend/ERPCore/Services/ReorderService.cs index 28a5322..83bc8b1 100644 --- a/Backend/ERPCore/Services/ReorderService.cs +++ b/Backend/ERPCore/Services/ReorderService.cs @@ -26,7 +26,7 @@ public sealed class ReorderService : IReorderService _requisitions = requisitions; } - public async Task> GetAlertsAsync(long? warehouseId, PageQuery query, CancellationToken ct = default) + public async Task> GetAlertsAsync(int? warehouseId, PageQuery query, CancellationToken ct = default) { var q = _reorders.Query().AsNoTracking(); if (warehouseId is not null) q = q.Where(r => r.WarehouseId == warehouseId); @@ -44,7 +44,7 @@ public sealed class ReorderService : IReorderService return PagedResponse.Create(page, query.Page, query.PageSize, alerts.Count); } - public async Task CreateSuggestedRequisitionAsync(long itemId, long warehouseId, CancellationToken ct = default) + public async Task CreateSuggestedRequisitionAsync(int itemId, int warehouseId, CancellationToken ct = default) { var policy = await _reorders.Query().AsNoTracking() .FirstOrDefaultAsync(r => r.ItemId == itemId && r.WarehouseId == warehouseId, ct) diff --git a/Backend/ERPCore/Services/RequisitionService.cs b/Backend/ERPCore/Services/RequisitionService.cs index 08b88d6..8390609 100644 --- a/Backend/ERPCore/Services/RequisitionService.cs +++ b/Backend/ERPCore/Services/RequisitionService.cs @@ -49,7 +49,7 @@ public sealed class RequisitionService : IRequisitionService return PagedResponse.Create(rows, query.Page, query.PageSize, total); } - public async Task GetAsync(long requisitionId, CancellationToken ct = default) + public async Task GetAsync(int requisitionId, CancellationToken ct = default) { var req = await _requisitions.Query().AsNoTracking() .Include(r => r.Lines) @@ -85,7 +85,7 @@ public sealed class RequisitionService : IRequisitionService return Map(req); } - public async Task SubmitAsync(long requisitionId, CancellationToken ct = default) + public async Task SubmitAsync(int requisitionId, CancellationToken ct = default) { var req = await _requisitions.Query() .Include(r => r.Lines) @@ -101,7 +101,7 @@ public sealed class RequisitionService : IRequisitionService return Map(req); } - private async Task EnsureItemsExistAsync(IEnumerable itemIds, CancellationToken ct) + private async Task EnsureItemsExistAsync(IEnumerable itemIds, CancellationToken ct) { var ids = itemIds.Distinct().ToList(); var found = await _items.Query().AsNoTracking() diff --git a/Backend/ERPCore/Services/RfqService.cs b/Backend/ERPCore/Services/RfqService.cs index defcab6..b73db6e 100644 --- a/Backend/ERPCore/Services/RfqService.cs +++ b/Backend/ERPCore/Services/RfqService.cs @@ -34,7 +34,7 @@ public sealed class RfqService : IRfqService _uow = uow; } - public async Task GetAsync(long rfqId, CancellationToken ct = default) + public async Task GetAsync(int rfqId, CancellationToken ct = default) { var rfq = await _rfqs.Query().AsNoTracking() .Include(r => r.Lines) @@ -68,7 +68,7 @@ public sealed class RfqService : IRfqService return MapRfq(rfq); } - public async Task AddQuotationAsync(long rfqId, CreateQuotationRequest request, CancellationToken ct = default) + public async Task AddQuotationAsync(int rfqId, CreateQuotationRequest request, CancellationToken ct = default) { var rfq = await _rfqs.Query().AsNoTracking() .Include(r => r.Lines) @@ -106,7 +106,7 @@ public sealed class RfqService : IRfqService quotation.Lines.Select(l => new QuotationLineDto(l.ItemId, l.UnitPrice, l.LeadDays)).ToList()); } - public async Task GetComparisonAsync(long rfqId, CancellationToken ct = default) + public async Task GetComparisonAsync(int rfqId, CancellationToken ct = default) { var rfq = await _rfqs.Query().AsNoTracking() .Include(r => r.Lines) @@ -130,7 +130,7 @@ public sealed class RfqService : IRfqService return new RfqComparisonDto(rfqId, vendorIds, rows); } - private async Task EnsureItemsExistAsync(IEnumerable itemIds, CancellationToken ct) + private async Task EnsureItemsExistAsync(IEnumerable itemIds, CancellationToken ct) { var ids = itemIds.Distinct().ToList(); var found = await _items.Query().AsNoTracking() @@ -140,7 +140,7 @@ public sealed class RfqService : IRfqService throw new DomainException(ErrorCodes.Validation, $"Item(s) not found: {string.Join(", ", missing)}.", 422); } - private async Task EnsureVendorsExistAsync(IEnumerable vendorIds, CancellationToken ct) + private async Task EnsureVendorsExistAsync(IEnumerable vendorIds, CancellationToken ct) { var ids = vendorIds.Distinct().ToList(); if (ids.Count == 0) return; diff --git a/Backend/ERPCore/Services/Stock/FifoCostingService.cs b/Backend/ERPCore/Services/Stock/FifoCostingService.cs index 6f4f0a8..5deadbc 100644 --- a/Backend/ERPCore/Services/Stock/FifoCostingService.cs +++ b/Backend/ERPCore/Services/Stock/FifoCostingService.cs @@ -39,7 +39,7 @@ public sealed class FifoCostingService : IFifoCostingService } public async Task CreateInboundLayerAsync( - long itemId, long warehouseId, long? batchId, long? serialId, long? grnLineId, + int itemId, int warehouseId, int? batchId, int? serialId, int? grnLineId, decimal qtyBase, decimal unitCost, DateTime receiptDate, CancellationToken ct = default) { var layer = new StockLayer @@ -59,7 +59,7 @@ public sealed class FifoCostingService : IFifoCostingService } public async Task> ConsumeAsync( - long itemId, long warehouseId, long? batchId, decimal qtyBase, CancellationToken ct = default) + int itemId, int warehouseId, int? batchId, decimal qtyBase, CancellationToken ct = default) { var today = DateOnly.FromDateTime(DateTime.UtcNow); @@ -119,9 +119,9 @@ public sealed class FifoCostingService : IFifoCostingService } public async Task PostLedgerAsync( - long itemId, long warehouseId, long? binId, long? batchId, long? serialId, long userId, + int itemId, int warehouseId, int? binId, int? batchId, int? serialId, int userId, Direction direction, decimal qtyBase, decimal unitCost, decimal runningBalance, - string sourceDocType, long sourceDocId, DateTime createdAt, CancellationToken ct = default) + string sourceDocType, int sourceDocId, DateTime createdAt, CancellationToken ct = default) { var entry = new StockLedger { @@ -159,12 +159,12 @@ public sealed class FifoCostingService : IFifoCostingService return entry; } - public async Task GetOnHandAsync(long itemId, long warehouseId, CancellationToken ct = default) + public async Task GetOnHandAsync(int itemId, int warehouseId, CancellationToken ct = default) => await _layers.Query().AsNoTracking() .Where(l => l.ItemId == itemId && l.WarehouseId == warehouseId) .SumAsync(l => (decimal?)l.QtyRemaining, ct) ?? 0m; - public async Task GetValuationAsync(long itemId, long warehouseId, CancellationToken ct = default) + public async Task GetValuationAsync(int itemId, int warehouseId, CancellationToken ct = default) { var open = await _layers.Query().AsNoTracking() .Where(l => l.ItemId == itemId && l.WarehouseId == warehouseId && l.QtyRemaining > 0) diff --git a/Backend/ERPCore/Services/Stock/StockMutator.cs b/Backend/ERPCore/Services/Stock/StockMutator.cs index 729f72e..7a1b9ae 100644 --- a/Backend/ERPCore/Services/Stock/StockMutator.cs +++ b/Backend/ERPCore/Services/Stock/StockMutator.cs @@ -27,11 +27,11 @@ public sealed class StockMutator : IStockMutator } public async Task> ApplyAsync( - long warehouseId, string sourceDocType, long sourceDocId, DateTime now, + int warehouseId, string sourceDocType, int sourceDocId, DateTime now, IReadOnlyList deltas, CancellationToken ct = default) { var actor = _currentUser.AuditUserId; - var balances = new Dictionary(); + var balances = new Dictionary(); var entries = new List(); foreach (var d in deltas) @@ -69,7 +69,7 @@ public sealed class StockMutator : IStockMutator return entries; } - private async Task LastCostAsync(long itemId, long warehouseId, CancellationToken ct) + private async Task LastCostAsync(int itemId, int warehouseId, CancellationToken ct) => await _layers.Query().AsNoTracking() .Where(l => l.ItemId == itemId && l.WarehouseId == warehouseId) .OrderByDescending(l => l.ReceiptDate).ThenByDescending(l => l.LayerId) diff --git a/Backend/ERPCore/Services/Stock/StockService.cs b/Backend/ERPCore/Services/Stock/StockService.cs index 722a069..2eaedab 100644 --- a/Backend/ERPCore/Services/Stock/StockService.cs +++ b/Backend/ERPCore/Services/Stock/StockService.cs @@ -25,7 +25,7 @@ public sealed class StockService : IStockService _transferLines = transferLines; } - public async Task GetOnHandAsync(long itemId, long warehouseId, CancellationToken ct = default) + public async Task GetOnHandAsync(int itemId, int warehouseId, CancellationToken ct = default) { var onHand = await _fifo.GetOnHandAsync(itemId, warehouseId, ct); @@ -51,7 +51,7 @@ public sealed class StockService : IStockService } public async Task> GetLedgerAsync( - long? itemId, long? warehouseId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default) + int? itemId, int? warehouseId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default) { var q = _ledger.Query().AsNoTracking(); if (itemId is not null) q = q.Where(l => l.ItemId == itemId); @@ -71,6 +71,6 @@ public sealed class StockService : IStockService return PagedResponse.Create(rows, query.Page, query.PageSize, total); } - public Task GetValuationAsync(long itemId, long warehouseId, CancellationToken ct = default) + public Task GetValuationAsync(int itemId, int warehouseId, CancellationToken ct = default) => _fifo.GetValuationAsync(itemId, warehouseId, ct); } diff --git a/Backend/ERPCore/Services/TransferService.cs b/Backend/ERPCore/Services/TransferService.cs index 4c765f5..6429811 100644 --- a/Backend/ERPCore/Services/TransferService.cs +++ b/Backend/ERPCore/Services/TransferService.cs @@ -40,7 +40,7 @@ public sealed class TransferService : ITransferService _uow = uow; } - public async Task GetAsync(long transferId, CancellationToken ct = default) + public async Task GetAsync(int transferId, CancellationToken ct = default) { var t = await _transfers.Query().AsNoTracking().Include(x => x.Lines) .FirstOrDefaultAsync(x => x.TransferId == transferId, ct); @@ -85,7 +85,7 @@ public sealed class TransferService : ITransferService return Map(transfer); } - public async Task DispatchAsync(long transferId, CancellationToken ct = default) + public async Task DispatchAsync(int transferId, CancellationToken ct = default) { var transfer = await _transfers.Query().Include(t => t.Lines) .FirstOrDefaultAsync(t => t.TransferId == transferId, ct) @@ -97,7 +97,7 @@ public sealed class TransferService : ITransferService var now = DateTime.UtcNow; var consumed = new List(); var ledgerRefs = new List(); - var balances = new Dictionary(); + var balances = new Dictionary(); await _uow.ExecuteInTransactionAsync(async token => { @@ -127,7 +127,7 @@ public sealed class TransferService : ITransferService return new DispatchResultDto(transfer.TransferId, transfer.Status, consumed, ledgerRefs.Select(l => l.LedgerId).ToList()); } - public async Task ReceiveAsync(long transferId, ReceiveTransferRequest request, CancellationToken ct = default) + public async Task ReceiveAsync(int transferId, ReceiveTransferRequest request, CancellationToken ct = default) { var transfer = await _transfers.Query().Include(t => t.Lines) .FirstOrDefaultAsync(t => t.TransferId == transferId, ct) @@ -139,7 +139,7 @@ public sealed class TransferService : ITransferService var now = DateTime.UtcNow; var createdLayers = new List(); var ledgerRefs = new List(); - var balances = new Dictionary(); + var balances = new Dictionary(); await _uow.ExecuteInTransactionAsync(async token => { @@ -183,7 +183,7 @@ public sealed class TransferService : ITransferService return new ReceiveResultDto(transfer.TransferId, transfer.Status, created, ledgerRefs.Select(l => l.LedgerId).ToList()); } - private async Task EnsureWarehouseAsync(long warehouseId, CancellationToken ct) + private async Task EnsureWarehouseAsync(int warehouseId, CancellationToken ct) { if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == warehouseId, ct)) throw new DomainException(ErrorCodes.Validation, $"Warehouse {warehouseId} does not exist.", 422); diff --git a/Backend/ERPCore/Services/VendorService.cs b/Backend/ERPCore/Services/VendorService.cs index 7c5277d..cbe0306 100644 --- a/Backend/ERPCore/Services/VendorService.cs +++ b/Backend/ERPCore/Services/VendorService.cs @@ -40,7 +40,7 @@ public sealed class VendorService : IVendorService return PagedResponse.Create(rows.Select(Map).ToList(), query.Page, query.PageSize, total); } - public async Task?> GetAsync(long vendorId, CancellationToken ct = default) + public async Task?> GetAsync(int vendorId, CancellationToken ct = default) { var vendor = await _vendors.Query().AsNoTracking() .FirstOrDefaultAsync(v => v.VendorId == vendorId, ct); @@ -71,7 +71,7 @@ public sealed class VendorService : IVendorService } public async Task> UpdateAsync( - long vendorId, UpdateVendorRequest request, uint expectedRowVersion, CancellationToken ct = default) + int vendorId, UpdateVendorRequest request, uint expectedRowVersion, CancellationToken ct = default) { var vendor = await _vendors.GetByIdAsync(vendorId, ct) ?? throw new NotFoundException($"Vendor {vendorId} was not found."); @@ -103,7 +103,7 @@ public sealed class VendorService : IVendorService return new ETagged(Map(vendor), vendor.RowVersion); } - public async Task SetStatusAsync(long vendorId, EntityStatus status, CancellationToken ct = default) + public async Task SetStatusAsync(int vendorId, EntityStatus status, CancellationToken ct = default) { var vendor = await _vendors.GetByIdAsync(vendorId, ct) ?? throw new NotFoundException($"Vendor {vendorId} was not found."); diff --git a/Backend/ERPCore/Services/WarehouseService.cs b/Backend/ERPCore/Services/WarehouseService.cs index a77e82b..da10aae 100644 --- a/Backend/ERPCore/Services/WarehouseService.cs +++ b/Backend/ERPCore/Services/WarehouseService.cs @@ -40,7 +40,7 @@ public sealed class WarehouseService : IWarehouseService return PagedResponse.Create(rows, query.Page, query.PageSize, total); } - public async Task GetAsync(long warehouseId, CancellationToken ct = default) + public async Task GetAsync(int warehouseId, CancellationToken ct = default) { var w = await _warehouses.Query().AsNoTracking() .FirstOrDefaultAsync(x => x.WarehouseId == warehouseId, ct); @@ -60,7 +60,7 @@ public sealed class WarehouseService : IWarehouseService return new WarehouseDto(warehouse.WarehouseId, warehouse.Code, warehouse.Name); } - public async Task> ListBinsAsync(long warehouseId, CancellationToken ct = default) + public async Task> ListBinsAsync(int warehouseId, CancellationToken ct = default) { await EnsureWarehouseExistsAsync(warehouseId, ct); @@ -71,7 +71,7 @@ public sealed class WarehouseService : IWarehouseService .ToListAsync(ct); } - public async Task CreateBinAsync(long warehouseId, CreateBinRequest request, CancellationToken ct = default) + public async Task CreateBinAsync(int warehouseId, CreateBinRequest request, CancellationToken ct = default) { await EnsureWarehouseExistsAsync(warehouseId, ct); @@ -86,7 +86,7 @@ public sealed class WarehouseService : IWarehouseService return new BinDto(bin.BinId, bin.WarehouseId, bin.Code, bin.BinType); } - private async Task EnsureWarehouseExistsAsync(long warehouseId, CancellationToken ct) + private async Task EnsureWarehouseExistsAsync(int warehouseId, CancellationToken ct) { if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == warehouseId, ct)) throw new NotFoundException($"Warehouse {warehouseId} was not found."); diff --git a/Backend/PROGRESS.md b/Backend/PROGRESS.md index ba8a854..7e710c6 100644 --- a/Backend/PROGRESS.md +++ b/Backend/PROGRESS.md @@ -58,7 +58,7 @@ Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** > **Status: COMPLETE.** Audit trail, doc numbering, reason codes, JournalEntryStub, negative-stock block, and now **authentication** (external AuthHex IdP integration) are all done + verified. The §6 security gate (NFR-03 auth + AR-01 audit) is met — **§1–§5 flipped `[~]`→`[x]`** (2026-07-14). FEFO pick-ordering is the only intentional deferral. - [x] Audit log on every mutation (who/when/old→new) — `AuditLog` (jsonb `changeSet`), written by an `ErpDbContext.SaveChanges` override (`AuditScribe`): Create captures the field set, Update captures **only changed fields as {old,new}**, Delete captures the prior row; PK/RowVersion excluded; ledger/layer/seq/self/journal excluded. Actor from `ICurrentUser` (system=1 until auth). Read via `GET /audit-logs`. **Verified** (Item create+update old→new; StockAdjustment create). This is the **AR-01 compensating control** (02-SECURITY B.3) — app-level append-only; DB-role UPDATE/DELETE revoke still deferred. - [x] Document numbering sequences (per type, per year) — `NumberSequence` + `NumberSequenceService` (atomic `INSERT … ON CONFLICT … RETURNING` inside the doc's UoW txn; gap-controlled). Verified issuing + incrementing PR/RFQ/PO. -- [x] Auth: **external AuthHex IdP integration** (2026-07-14) — ERPCore is a resource server. `JwtAuthExtensions` validates **RS256** against AuthHex's RSA **public** key (config `Auth:RsaPublicKeyXml` → `RsaSecurityKey`; `MapInboundClaims=false`), issuer `AuthHex`, audience `AuthHexClient` (no JWKS → static key). `[Authorize(ErpAccess)]` on `ApiControllerBase` gates every v1 endpoint; the `ErpAccess` policy `RequireAuthenticatedUser` + optional `RequireClaim(UserTypeCode/RoleCode)` from `Auth:RequiredUserTypeCode`/`RequiredRoleCode` (empty ⇒ any valid ERP token — AuthHex is ERP-dedicated). **Shadow-user JIT provisioning:** `ShadowUserClaimsTransformation` (`IClaimsTransformation`) maps the token's `UserId` **GUID** → a local `users` row (`auth_user_id` unique; Username/DisplayName = `NIC`), idempotent, and injects the local `long` id as `nameid` so `ICurrentUser.AuditUserId` resolves the real actor. Migration `AddAuthUserId`. **Verified:** no token→401; `/health`,`/api/meta`,Swagger anonymous; valid token→200; shadow user provisioned (User 2, Username=NIC, AuthUserId=GUID); item Create **audited as the shadow user (id 2, not system)**; re-request reuses the same user; door gate → **403** on UserType mismatch, **200** on match. +- [x] Auth: **external AuthHex IdP integration** (2026-07-14) — ERPCore is a resource server. `JwtAuthExtensions` validates **RS256** against AuthHex's RSA **public** key (config `Auth:RsaPublicKeyXml` → `RsaSecurityKey`; `MapInboundClaims=false`), issuer `AuthHex`, audience `AuthHexClient` (no JWKS → static key). `[Authorize(ErpAccess)]` on `ApiControllerBase` gates every v1 endpoint; the `ErpAccess` policy `RequireAuthenticatedUser` + optional `RequireClaim(UserTypeCode/RoleCode)` from `Auth:RequiredUserTypeCode`/`RequiredRoleCode` (empty ⇒ any valid ERP token — AuthHex is ERP-dedicated). **Shadow-user JIT provisioning:** `ShadowUserClaimsTransformation` (`IClaimsTransformation`) maps the token's `UserId` **GUID** → a local `users` row (`auth_user_id` unique; Username/DisplayName = `NIC`), idempotent, and injects the local `int` id as `nameid` so `ICurrentUser.AuditUserId` resolves the real actor. Migration `AddAuthUserId`. **Verified:** no token→401; `/health`,`/api/meta`,Swagger anonymous; valid token→200; shadow user provisioned (User 2, Username=NIC, AuthUserId=GUID); item Create **audited as the shadow user (id 2, not system)**; re-request reuses the same user; door gate → **403** on UserType mismatch, **200** on match. - [x] JournalEntryStub emitted per stock movement (data only) — `JournalEntryStub` written in `FifoCostingService.PostLedgerAsync` for every ledger entry (In → Dr Inventory `1300` / Cr Clearing `2100`; Out reverses; amount = movement value). Placeholder accounts until a chart of accounts exists. Read via `GET /journal-entries`. **Verified** (GRN In 700, ADJ Out 70). - [x] Negative-stock policy enforcement (default block) — enforced in `FifoCostingService.ConsumeAsync` → `409 STOCK_NEGATIVE_BLOCKED` (verified). Per-item override still a config stub. - [~] FEFO picking for perishables; block expired / on-hold issue — **issue-block done + verified** (`ONHOLD_NOT_ISSUABLE`, `EXPIRED_BATCH_BLOCKED`; on-hold/expired layers excluded from consume). FEFO *pick ordering* (oldest-expiry first) not yet built. @@ -123,13 +123,13 @@ Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** ### 2026-07-14 — Auth architecture change: external AuthHex IdP (docs-only pass) - **Plan changed:** auth is no longer a local `POST /auth/login` inside ERPCore. A **separate AuthHex IdP** (runs on `:5011`, source at `c:\Users\WAS\Documents\Developments\ERP_Auth_Service\`) owns login/registration/recovery; ERPCore becomes a **resource server** that only validates AuthHex tokens. Updated `docs/10-BACKEND-PHASE1.md` (header, A.4 auth/audit-actor, A.5 DI, B.2.3, FR-X-01, NFR-03, C.7 `USER`, C.9, B.8.4 decision #10) and this file. **No code changed this pass.** -- **Decisions (confirmed):** (1) identity = **shadow-user JIT provisioning** — add `auth_user_id` GUID (unique) to `users`, keep all `long` FKs; (2) authorization = **door-gate to an ERP `UserType`/`Role`**, per-endpoint RBAC still deferred; (3) scope = **docs only** now, code integration is a follow-up. +- **Decisions (confirmed):** (1) identity = **shadow-user JIT provisioning** — add `auth_user_id` GUID (unique) to `users`, keep all `int` FKs; (2) authorization = **door-gate to an ERP `UserType`/`Role`**, per-endpoint RBAC still deferred; (3) scope = **docs only** now, code integration is a follow-up. - **Confirmed AuthHex facts:** RS256 (RSA 2048; ERPCore needs the static **public** key — no JWKS), issuer `AuthHex`, audience `AuthHexClient`, lifetime 1000 min prod / 60 min dev; claims `UserId`(GUID)/`UserTypeCode`/`RoleCode`/`NIC`/`jti`/`iat` (no `sub`/`nameid`); BCrypt password hashing; login `POST /api/loginUser {identifier,password}`. - **Open blockers (resolve before the code phase):** exact ERP `UserTypeCode`/`RoleCode` for the door gate (must exist in AuthHex); RSA public-key distribution + rotation process (no JWKS); shadow-user `Username`/`DisplayName` source (token has no name); secrets hygiene in AuthHex config (private key/SMTP/DB in plaintext); `docs/11 §2.0` still documents `/auth/login` (now AuthHex-owned) — recommend a follow-up annotation. ### 2026-07-14 (2) — Auth code integration: AuthHex resource server (§6 COMPLETE → §1–§5 flipped `[x]`) - **RS256 validation:** `JwtAuthExtensions` rewritten — `RsaSecurityKey` from `Auth:RsaPublicKeyXml` (AuthHex public key), `ValidIssuer=AuthHex`, `ValidAudience=AuthHexClient`, `ValidAlgorithms=[RS256]`, `MapInboundClaims=false` (keeps `UserId`/`UserTypeCode`/`RoleCode` verbatim). `appsettings.json` `Jwt`→`Auth` (public key + issuer/audience + `RequiredUserTypeCode`/`RequiredRoleCode`); removed the HS256 dev signing key. - **Door policy** `ErpAccess`: `RequireAuthenticatedUser` + optional `RequireClaim(UserTypeCode/RoleCode)` when configured (AuthHex is ERP-dedicated → empty default = any valid token). `[Authorize(ErpAccess)]` on `ApiControllerBase`; `MetaController`/health/Swagger stay anonymous. -- **Shadow-user provisioning:** `ShadowUserClaimsTransformation` (`IClaimsTransformation`, scoped) maps token `UserId` GUID → local `users` row (`auth_user_id` unique, Username/DisplayName=`NIC`), idempotent w/ race-safe re-read, injects local `long` id as `nameid`. `User.AuthUserId` (Guid?) + `AuthHexClaims` consts + migration `AddAuthUserId`. +- **Shadow-user provisioning:** `ShadowUserClaimsTransformation` (`IClaimsTransformation`, scoped) maps token `UserId` GUID → local `users` row (`auth_user_id` unique, Username/DisplayName=`NIC`), idempotent w/ race-safe re-read, injects local `int` id as `nameid`. `User.AuthUserId` (Guid?) + `AuthHexClaims` consts + migration `AddAuthUserId`. - **Verified (minted AuthHex-shaped RS256 token, signed with AuthHex's real private key):** no token→401; `/health`,`/api/meta`,Swagger→200 anon; valid token→200; POST item→201 **audited as shadow user id 2** (Username=NIC, AuthUserId=GUID), not system; repeat request reuses user (1 provision); door gate `RequiredUserTypeCode=WAREHOUSE` → ERP-type token **403**, WAREHOUSE-type token **200**. Build clean; migration applied. - **§6 COMPLETE.** Only intentional deferral left in Phase 1: FEFO pick-ordering (§6, `[~]`). Follow-ups: set the real ERP `Auth:RequiredUserTypeCode`/`RoleCode` for production; secure the RSA key rotation process. diff --git a/docs/10-BACKEND-PHASE1.md b/docs/10-BACKEND-PHASE1.md index e7dd784..591e8cb 100644 --- a/docs/10-BACKEND-PHASE1.md +++ b/docs/10-BACKEND-PHASE1.md @@ -50,7 +50,7 @@ HTTP ─► Controller ─► Service ─► Repository ─► UnitOfWork / ErpD ## A.4 Cross-cutting - **Errors:** RFC 7807 `ProblemDetails` (framework default). Domain exceptions in `System/Errors` carry a stable `code`; a middleware maps them to `ProblemDetails`. Catalog in `11-BACKEND-PHASE1.md §7`. - **Authentication:** ERPCore is a **resource server**. It validates JWTs issued by the **external AuthHex IdP** — algorithm **RS256** (asymmetric RSA), issuer `AuthHex`, audience `AuthHexClient`. AuthHex exposes **no JWKS/OIDC discovery**, so ERPCore is configured with AuthHex's **RSA public key statically** (rotation is a manual config update). Tokens live ~1000 min (prod) / 60 min (dev). A single **door authorization policy** requires an ERP `UserTypeCode`/`RoleCode` claim (AuthHex is a shared IdP, so a valid token alone is not enough); **per-endpoint RBAC stays deferred**. -- **Audit actor:** the token carries no `sub`/`nameid`; identity is AuthHex's custom **`UserId` (GUID)** claim. An `ICurrentUser` abstraction (`Infra/Auth`) resolves the acting user from a **local shadow user** — the GUID is mapped (JIT-provisioned) to a local `long` `users.user_id` that all FKs reference (see C.7). Services stamp mutations with it. **Never** trust a `createdBy` from the request body. +- **Audit actor:** the token carries no `sub`/`nameid`; identity is AuthHex's custom **`UserId` (GUID)** claim. An `ICurrentUser` abstraction (`Infra/Auth`) resolves the acting user from a **local shadow user** — the GUID is mapped (JIT-provisioned) to a local `int` `users.user_id` that all FKs reference (see C.7). Services stamp mutations with it. **Never** trust a `createdBy` from the request body. - **Concurrency:** mutable resources carry a `RowVersion` (`[Timestamp] byte[]`), surfaced as `ETag`; `PUT`/`PATCH` require `If-Match` → `412` on mismatch. - **Numbering:** document numbers come from `NumberSequence` (per doc type, per year), issued inside the same transaction as the document. @@ -58,7 +58,7 @@ HTTP ─► Controller ─► Service ─► Repository ─► UnitOfWork / ErpD - `ErpDbContext`: scoped (default). - `IUnitOfWork`, repositories, services, `ICurrentUser`, `FifoCostingService`: **scoped**. - Register in `Program.cs` (or an `AddApplication()` extension) after `AddDbContext`. -- **Auth wiring:** JWT bearer validation is built from AuthHex's **RSA public key** (config XML → `RsaSecurityKey`) with `ValidIssuer=AuthHex`, `ValidAudience=AuthHexClient`. A scoped **`IClaimsTransformation`** provisions/looks up the local shadow user (by `auth_user_id` = token `UserId` GUID) and injects the resolved local `long` id as a `ClaimTypes.NameIdentifier` (`nameid`) claim, so `ICurrentUser`/`AuditUserId` resolve a real user unchanged (falling back to the seeded system user only when unauthenticated). +- **Auth wiring:** JWT bearer validation is built from AuthHex's **RSA public key** (config XML → `RsaSecurityKey`) with `ValidIssuer=AuthHex`, `ValidAudience=AuthHexClient`. A scoped **`IClaimsTransformation`** provisions/looks up the local shadow user (by `auth_user_id` = token `UserId` GUID) and injects the resolved local `int` id as a `ClaimTypes.NameIdentifier` (`nameid`) claim, so `ICurrentUser`/`AuditUserId` resolve a real user unchanged (falling back to the seeded system user only when unauthenticated). --- @@ -183,7 +183,7 @@ One base currency; invoicing/3-way match in Accounting (GRN carries data); users ### B.3.6 Cross-cutting (FR-X) | ID | Requirement | Pri | |---|---|---| -| FR-X-01 | **[Phase 1: external IdP, user identity only]** Authentication is **delegated to the external AuthHex IdP** (ERPCore validates its RS256 tokens; no local login). ERPCore **provisions a local shadow user** (`auth_user_id` GUID → local `long`) and **stamps every transaction with the acting user's identity** for audit. AuthHex now also supplies `RoleCode`/`UserTypeCode` claims, used only for the door gate; full per-endpoint RBAC (role→permission matrix) remains **deferred** — reserve role/permission structures for no-migration enablement. | M | +| FR-X-01 | **[Phase 1: external IdP, user identity only]** Authentication is **delegated to the external AuthHex IdP** (ERPCore validates its RS256 tokens; no local login). ERPCore **provisions a local shadow user** (`auth_user_id` GUID → local `int`) and **stamps every transaction with the acting user's identity** for audit. AuthHex now also supplies `RoleCode`/`UserTypeCode` claims, used only for the door gate; full per-endpoint RBAC (role→permission matrix) remains **deferred** — reserve role/permission structures for no-migration enablement. | M | | FR-X-02 | Maintain an **immutable audit trail** for every create/update/delete and stock movement (who/when/old→new/reason). | M | | FR-X-03 | Generate **document numbers** from configurable sequences (per type, per year), unique and gap-controlled. | M | | FR-X-04 | Maintain configurable **reason-code** lists (adjustments, returns, count variances). | M | @@ -242,7 +242,7 @@ Adjustment: Damage, Theft/Loss, Count Variance, Expiry Write-off, System Correct | 7 | PO amendments | **Resolved:** Option B, edit-while-open | | 8 | Costing method | **Resolved:** FIFO | | 9 | Tenancy | **Resolved:** single-tenant | -| 10 | Authentication | **Resolved:** external **AuthHex** IdP (RS256; ERPCore validates only), **shadow-user** provisioning (`auth_user_id` GUID → local `long`), door-gated by ERP `UserType`/`Role`; per-endpoint RBAC deferred. *Open sub-item:* exact ERP `UserTypeCode`/`RoleCode` + RSA-key rotation process. | +| 10 | Authentication | **Resolved:** external **AuthHex** IdP (RS256; ERPCore validates only), **shadow-user** provisioning (`auth_user_id` GUID → local `int`), door-gated by ERP `UserType`/`Role`; per-endpoint RBAC deferred. *Open sub-item:* exact ERP `UserTypeCode`/`RoleCode` + RSA-key rotation process. | --- @@ -322,7 +322,7 @@ STOCK_COUNT_LINE(count_line_id PK, count_id FK→STOCK_COUNT, item_id FK→ITEM, ## C.7 Cross-cutting ``` -USER(user_id PK, username, display_name, status, auth_user_id [GUID, unique] → AuthHex identity) -- local shadow/projection of AuthHex users; user_id (long) is what all FKs reference +USER(user_id PK, username, display_name, status, auth_user_id [GUID, unique] → AuthHex identity) -- local shadow/projection of AuthHex users; user_id (int) is what all FKs reference REASON_CODE(reason_code_id PK, code, description, context) NUMBER_SEQUENCE(sequence_id PK, doc_type, year, last_number) AUDIT_LOG(audit_id PK, user_id FK→USER, entity_type, entity_id, action, change_set, created_at) @@ -342,7 +342,7 @@ ROLE_PERMISSION(role_id FK→ROLE, permission_id FK→PERMISSION) - **Polymorphic source.** `STOCK_LEDGER.source_doc_type/source_doc_id` (and `AUDIT_LOG`, `JOURNAL_ENTRY_STUB`) reference the originating document without a hard FK per type — new transaction types (Sales, Manufacturing) write to the ledger without a schema change. - **In-transit + cost-preserving transfer.** `STOCK_TRANSFER` holds `src`/`dest` warehouse; dispatch consumes source layers into in-transit, receive creates the destination layer at the **inherited** source cost. - **FEFO ≠ FIFO.** FIFO governs *costing*; FEFO governs *physical picking* of perishables via `BATCH.expiry_date`. -- **External IdP + shadow user.** Authentication is delegated to **AuthHex** (RS256, issuer `AuthHex`/audience `AuthHexClient`, static public key). `USER` is a **local shadow** of AuthHex identities: `auth_user_id` (GUID from the token's `UserId` claim) is JIT-mapped to the local `long` `user_id` that every `created_by`/`requested_by`/`AUDIT_LOG.user_id`/`STOCK_LEDGER.user_id` FK references — no FK type change. A door policy admits only ERP `UserType`/`Role` holders. +- **External IdP + shadow user.** Authentication is delegated to **AuthHex** (RS256, issuer `AuthHex`/audience `AuthHexClient`, static public key). `USER` is a **local shadow** of AuthHex identities: `auth_user_id` (GUID from the token's `UserId` claim) is JIT-mapped to the local `int` `user_id` that every `created_by`/`requested_by`/`AUDIT_LOG.user_id`/`STOCK_LEDGER.user_id` FK references — no FK type change. A door policy admits only ERP `UserType`/`Role` holders. - **Reserved RBAC.** Role/Permission/UserRole/RolePermission exist for schema-completeness only; only `USER` is live (audit stamp). AuthHex's `RoleCode`/`UserTypeCode` claims drive the door gate today; per-endpoint RBAC is future work. - **Reorder alerts are a query**, not an entity — computed from `ITEM_REORDER` vs available. Add a table only if alert history is required. diff --git a/docs/11-BACKEND-PHASE1.md b/docs/11-BACKEND-PHASE1.md index 845f6d6..8d1a94d 100644 --- a/docs/11-BACKEND-PHASE1.md +++ b/docs/11-BACKEND-PHASE1.md @@ -20,7 +20,7 @@ Authorization: Bearer ``` - Every endpoint requires a valid **Bearer JWT**; unauthenticated → `401`. Tokens are issued by the **external AuthHex IdP** (not ERPCore) — **RS256**, issuer `AuthHex`, audience `AuthHexClient`. ERPCore validates them against AuthHex's static RSA public key (no JWKS) and admits only holders of the configured ERP `UserType`/`Role` (door policy) → otherwise `403`. - **Per-endpoint RBAC is NOT enforced in Phase 1** (FR-X-01): any ERP-admitted user may call any endpoint. -- The **audit actor** is AuthHex's custom **`UserId` (GUID)** claim, mapped to a local shadow user (`long`). Clients never send `createdBy`; the server derives it (docs/10 A.4). +- The **audit actor** is AuthHex's custom **`UserId` (GUID)** claim, mapped to a local shadow user (`int`). Clients never send `createdBy`; the server derives it (docs/10 A.4). ### 1.3 Content type & encoding `application/json`, UTF-8, **camelCase**. Timestamps ISO 8601 UTC (`2026-07-07T09:30:00Z`); dates `YYYY-MM-DD`. Base currency **LKR** in Phase 1.