feat: add vendor optional fields and configuration hooks
- Implemented VendorOptionalFields component to handle optional vendor details (mobile, email, address) with validation. - Created useCustomerConfig hook to load optional customer field settings from Sales configuration. - Developed useProcurementConfig hook to manage Procurement configuration flags. - Introduced accessConfigApi for managing access configuration settings. - Added openingBalancesApi for handling opening balances operations. - Created procurementConfigApi for managing procurement configuration settings. - Implemented nav-routes for permission-based routing. - Developed PDF generation for purchase orders with dynamic vendor and item details. - Added purchase-order tax helpers for tax calculations and formatting. - Defined types for opening balances and related operations.
This commit is contained in:
@@ -10,7 +10,7 @@ public sealed record ApprovalStepDto(int Level, ApprovalDecision Decision, int D
|
||||
public sealed record ApprovalConfigDto(
|
||||
int StockAdjustmentLevels, decimal StockAdjustmentLevel2ThresholdValue,
|
||||
int PriceAdjustmentLevels, decimal PriceAdjustmentLevel2ThresholdPercent,
|
||||
IReadOnlyList<string> Level2RoleCodes, DateTime? UpdatedAt);
|
||||
IReadOnlyList<int> Level2RoleIds, DateTime? UpdatedAt);
|
||||
|
||||
public sealed class UpdateApprovalConfigRequest
|
||||
{
|
||||
@@ -18,7 +18,8 @@ public sealed class UpdateApprovalConfigRequest
|
||||
[Range(0, double.MaxValue)] public decimal StockAdjustmentLevel2ThresholdValue { get; set; }
|
||||
[Range(0, 2)] public int PriceAdjustmentLevels { get; set; }
|
||||
[Range(0, 1000)] public decimal PriceAdjustmentLevel2ThresholdPercent { get; set; }
|
||||
public List<string> Level2RoleCodes { get; set; } = new();
|
||||
/// <summary>Roles allowed to give the second approval; empty = anyone with the Approve action.</summary>
|
||||
public List<int> Level2RoleIds { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class ApproveRequest
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ERPCore.Dtos.Auth;
|
||||
|
||||
/// <summary>AuthHex's Role projection (ERP_Auth_Service/API_DOCUMENTATION.md, RoleManager section).</summary>
|
||||
public sealed class AuthHexRoleDto
|
||||
{
|
||||
public Guid RoleId { get; set; }
|
||||
public string? Code { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public bool? IsSystemRole { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CreateAuthHexRoleRequest
|
||||
{
|
||||
[Required] public string Code { get; set; } = string.Empty;
|
||||
public string? Name { get; set; }
|
||||
public bool? IsSystemRole { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UpdateAuthHexRoleRequest
|
||||
{
|
||||
[Required] public Guid RoleId { get; set; }
|
||||
public string? Code { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public bool? IsSystemRole { get; set; }
|
||||
}
|
||||
@@ -57,7 +57,9 @@ public sealed class RegisterRequest
|
||||
{
|
||||
/// <summary>Optional — AuthHex requires a client-supplied id; ERPCore generates one when omitted.</summary>
|
||||
public Guid? UserId { get; set; }
|
||||
[Required] public Guid RoleId { get; set; }
|
||||
/// <summary>Ignored for self-registration and never taken from a caller: the ERP assigns roles itself. Sent to AuthHex only
|
||||
/// while the old AuthHex (which still demands a role id) is deployed — see <c>AuthHex:LegacyRoleId</c>.</summary>
|
||||
public Guid? RoleId { get; set; }
|
||||
[Required] public Guid UserTypeId { get; set; }
|
||||
public string? Fullname { get; set; }
|
||||
public string? UserName { get; set; }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ERPCore.Dtos.Config;
|
||||
|
||||
/// <summary>Access configuration resource: which role a person gets when they have none.</summary>
|
||||
public sealed record AccessConfigDto(int? DefaultRoleId, string? DefaultRoleName, DateTime? UpdatedAt, int? UpdatedBy);
|
||||
|
||||
/// <summary>Full replacement. <c>null</c> clears the default (people without a role then have no permissions at all).</summary>
|
||||
public sealed class UpdateAccessConfigRequest
|
||||
{
|
||||
[Range(1, int.MaxValue)] public int? DefaultRoleId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ERPCore.Domain.Enums;
|
||||
|
||||
namespace ERPCore.Dtos.Config;
|
||||
|
||||
/// <summary>Procurement configuration resource.</summary>
|
||||
public sealed record ProcurementConfigDto(
|
||||
VendorContactMode VendorContactMode, bool VendorAddressEnabled, bool PoTaxEnabled,
|
||||
DateTime? UpdatedAt, int? UpdatedBy,
|
||||
bool VendorPaymentTermsEnabled = true, bool VendorTaxRegEnabled = true);
|
||||
|
||||
/// <summary>
|
||||
/// Full replacement of the flags. <c>UpdatedBy</c> is derived from the token, never posted.
|
||||
/// Nullable so an omitted flag is a 400 rather than silently binding to a default
|
||||
/// (same reasoning as <see cref="UpdateGrnConfigRequest"/>).
|
||||
/// </summary>
|
||||
public sealed class UpdateProcurementConfigRequest
|
||||
{
|
||||
[Required] public VendorContactMode? VendorContactMode { get; set; }
|
||||
[Required] public bool? VendorAddressEnabled { get; set; }
|
||||
[Required] public bool? PoTaxEnabled { get; set; }
|
||||
[Required] public bool? VendorPaymentTermsEnabled { get; set; }
|
||||
[Required] public bool? VendorTaxRegEnabled { get; set; }
|
||||
}
|
||||
@@ -6,7 +6,10 @@ namespace ERPCore.Dtos.Config;
|
||||
/// the generic cash customer (null until the option has been switched on at least once).</summary>
|
||||
public sealed record SalesConfigDto(
|
||||
bool AllowCashCustomer, int? CashCustomerId, string? CashCustomerName, bool AllowFreeItems, bool AllowDiscounts, bool AllowOverStockSale,
|
||||
bool AllowWarehouseSelection, int? DefaultWarehouseId, string? DefaultWarehouseName, DateTime? UpdatedAt, int? UpdatedBy);
|
||||
bool AllowWarehouseSelection, int? DefaultWarehouseId, string? DefaultWarehouseName, DateTime? UpdatedAt, int? UpdatedBy,
|
||||
bool CustomerPhoneEnabled = true, bool CustomerEmailEnabled = true, bool CustomerAddressEnabled = true,
|
||||
bool CustomerCityEnabled = true, bool CustomerCountryEnabled = true,
|
||||
bool CustomerCreditLimitEnabled = true, bool CustomerCreditDaysEnabled = true);
|
||||
|
||||
/// <summary>
|
||||
/// Full replacement of the flags. <c>UpdatedBy</c> is derived from the token, never posted.
|
||||
@@ -22,4 +25,11 @@ public sealed class UpdateSalesConfigRequest
|
||||
[Required] public bool? AllowWarehouseSelection { get; set; }
|
||||
/// <summary>Required when <see cref="AllowWarehouseSelection"/> is false.</summary>
|
||||
public int? DefaultWarehouseId { get; set; }
|
||||
[Required] public bool? CustomerPhoneEnabled { get; set; }
|
||||
[Required] public bool? CustomerEmailEnabled { get; set; }
|
||||
[Required] public bool? CustomerAddressEnabled { get; set; }
|
||||
[Required] public bool? CustomerCityEnabled { get; set; }
|
||||
[Required] public bool? CustomerCountryEnabled { get; set; }
|
||||
[Required] public bool? CustomerCreditLimitEnabled { get; set; }
|
||||
[Required] public bool? CustomerCreditDaysEnabled { get; set; }
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ public sealed record GrnDto(
|
||||
public sealed record GrnSummaryDto(
|
||||
int GrnId, string DocNo, int? PoId, int VendorId, int WarehouseId, int BranchId, GrnStatus Status,
|
||||
int CreatedBy, DateTime CreatedAt, DateTime? PostedAt, int LineCount, decimal TotalValue,
|
||||
int? PurchaseInvoiceId, string? PurchaseInvoiceNo);
|
||||
int? PurchaseInvoiceId, string? PurchaseInvoiceNo,
|
||||
string? PoDocNo = null, Dtos.Procurement.PoVendorDto? Vendor = null);
|
||||
|
||||
public sealed record CreatedLayerDto(
|
||||
int LayerId, int ItemId, int WarehouseId, int? BatchId,
|
||||
@@ -93,7 +94,8 @@ public sealed class CreateGrnRequest
|
||||
public int? VendorId { get; set; }
|
||||
[Required] public int WarehouseId { get; set; }
|
||||
/// <summary>Owning branch for this GRN — warehouses are shared, so this is what actually
|
||||
/// scopes the document (resolved via IBranchConfigService; required when branching is enabled).</summary>
|
||||
/// scopes the document (resolved via IBranchConfigService; required when branching is enabled).
|
||||
/// Ignored when <see cref="PoId"/> is set: a GRN against a purchase order always belongs to that order's branch.</summary>
|
||||
[Required] public int BranchId { get; set; }
|
||||
[Required, MinLength(1)] public List<CreateGrnLineInput> Lines { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ERPCore.Dtos.OpeningBalances;
|
||||
|
||||
/// <summary>One cash or bank account as the opening-balance screen shows it. <c>Amount</c> is filled only once posted.</summary>
|
||||
public sealed record OpeningCashBankAccountDto(
|
||||
string AccountType, string AccountName, string? BankName, string? AccountNumber,
|
||||
string GlAccountCode, string CurrencyCode, decimal? Amount);
|
||||
|
||||
public sealed record OpeningBalanceLineDto(string AccountCode, string AccountName, decimal Amount);
|
||||
|
||||
/// <summary>A posted opening balance (the record of the one-time entry).</summary>
|
||||
public sealed record OpeningBalanceRunDto(
|
||||
int OpeningBalanceRunId, string Kind, DateOnly CutoverDate, string? GlJournalNo,
|
||||
decimal TotalAmount, string Currency, DateTime PostedAt, int PostedBy,
|
||||
IReadOnlyList<OpeningBalanceLineDto> Lines);
|
||||
|
||||
/// <summary>The cash/bank screen's state: the accounts to enter amounts for, or the posted record once done.</summary>
|
||||
public sealed record CashBankOpeningStateDto(
|
||||
OpeningBalanceRunDto? Posted, IReadOnlyList<OpeningCashBankAccountDto> Accounts, string BaseCurrency);
|
||||
|
||||
public sealed class OpeningBalanceLineInput
|
||||
{
|
||||
/// <summary>The GL account code of a cash or bank account, as returned by the state endpoint.</summary>
|
||||
[Required, StringLength(20)] public string GlAccountCode { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Opening balance, at most 2 decimals. Positive = money held; negative = an overdraft. Zero lines are ignored.</summary>
|
||||
public decimal Amount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class PostCashBankOpeningRequest
|
||||
{
|
||||
/// <summary>The opening date (the GL entry date). Must fall in an open accounting period and not be in the future.</summary>
|
||||
[Required] public DateOnly? CutoverDate { get; set; }
|
||||
|
||||
[Required, MinLength(1)] public List<OpeningBalanceLineInput> Lines { get; set; } = new();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- opening stock
|
||||
|
||||
public sealed class OpeningStockLineInput
|
||||
{
|
||||
[Required] public int ItemId { get; set; }
|
||||
[Required] public int WarehouseId { get; set; }
|
||||
|
||||
/// <summary>Quantity in the item's base UOM; more than 0, at most 4 decimals.</summary>
|
||||
public decimal Qty { get; set; }
|
||||
|
||||
/// <summary>Cost of ONE base unit, 0 or more, at most 4 decimals — this becomes the FIFO layer's cost.</summary>
|
||||
public decimal UnitCost { get; set; }
|
||||
}
|
||||
|
||||
public sealed class PostOpeningStockRequest
|
||||
{
|
||||
/// <summary>The opening date (the stock layers' receipt date and the GL entry date). Not in the future.</summary>
|
||||
[Required] public DateOnly? CutoverDate { get; set; }
|
||||
|
||||
[Required, MinLength(1), MaxLength(500)] public List<OpeningStockLineInput> Lines { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed record OpeningStockLineDto(
|
||||
int ItemId, string Sku, string ItemName, int WarehouseId, string WarehouseCode,
|
||||
decimal Qty, decimal UnitCost, decimal Value);
|
||||
|
||||
public sealed record OpeningStockRunSummaryDto(
|
||||
int OpeningBalanceRunId, DateOnly CutoverDate, string? GlJournalNo, decimal TotalValue, int LineCount,
|
||||
DateTime PostedAt, int PostedBy);
|
||||
|
||||
public sealed record OpeningStockRunDto(
|
||||
int OpeningBalanceRunId, DateOnly CutoverDate, string? GlJournalNo, decimal TotalValue, int LineCount,
|
||||
DateTime PostedAt, int PostedBy, IReadOnlyList<OpeningStockLineDto> Lines);
|
||||
@@ -5,9 +5,14 @@ namespace ERPCore.Dtos.Procurement;
|
||||
|
||||
// Responses (docs/11 §3.3) ------------------------------------------------------
|
||||
|
||||
/// <summary><c>Tax</c> is the rate (0..1); <c>TaxAmount</c> the stored amount for the line.</summary>
|
||||
public sealed record PoLineDto(
|
||||
int PoLineId, int ItemId, int WarehouseId,
|
||||
decimal Qty, decimal UnitPrice, decimal Tax, decimal QtyReceived);
|
||||
decimal Qty, decimal UnitPrice, decimal Tax, decimal QtyReceived, decimal TaxAmount = 0);
|
||||
|
||||
/// <summary>Vendor details shown on a purchase order. <c>Mobile</c>/<c>Email</c>/<c>Address</c> are null
|
||||
/// unless the matching Procurement configuration switch is on.</summary>
|
||||
public sealed record PoVendorDto(int VendorId, string Code, string Name, string? Mobile, string? Email, string? Address);
|
||||
|
||||
public sealed record PoTotalsDto(decimal SubTotal, decimal Tax, decimal GrandTotal, string Currency);
|
||||
|
||||
@@ -15,11 +20,12 @@ public sealed record PurchaseOrderDto(
|
||||
int PoId, string DocNo, int VendorId, int BranchId, int? RequisitionId, PurchaseOrderStatus Status,
|
||||
bool ApprovalRequired, int CreatedBy, int? ApprovedBy, DateTime? ApprovedAt,
|
||||
DateTime CreatedAt, DateTime? UpdatedAt,
|
||||
PoTotalsDto Totals, IReadOnlyList<PoLineDto> Lines);
|
||||
PoTotalsDto Totals, IReadOnlyList<PoLineDto> Lines, PoVendorDto? Vendor = null);
|
||||
|
||||
public sealed record PurchaseOrderSummaryDto(
|
||||
int PoId, string DocNo, int VendorId, int BranchId, PurchaseOrderStatus Status,
|
||||
bool ApprovalRequired, int CreatedBy, DateTime CreatedAt, PoTotalsDto Totals);
|
||||
bool ApprovalRequired, int CreatedBy, DateTime CreatedAt, PoTotalsDto Totals,
|
||||
PoVendorDto? Vendor = null);
|
||||
|
||||
// Requests — server sets docNo, status, createdBy, timestamps, qtyReceived, totals
|
||||
|
||||
|
||||
@@ -11,4 +11,6 @@ namespace ERPCore.Dtos.Rbac;
|
||||
/// entirely rather than merely restricting them by role.</summary>
|
||||
public sealed record MeResponseDto(
|
||||
string? RoleCode, string? RoleName, IReadOnlyList<string> NavCodes,
|
||||
bool IsAdmin, int? BranchId, string? BranchName, bool BranchingEnabled, bool WarehouseBranchingEnabled);
|
||||
bool IsAdmin, int? BranchId, string? BranchName, bool BranchingEnabled, bool WarehouseBranchingEnabled,
|
||||
int? RoleId = null, bool HasRole = false,
|
||||
IReadOnlyDictionary<string, int>? Permissions = null);
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace ERPCore.Dtos.Rbac;
|
||||
|
||||
public sealed record RoleDto(
|
||||
int RoleId, string Code, string Name, bool IsSystemRole, EntityStatus Status,
|
||||
DateTime CreatedAt, DateTime? UpdatedAt);
|
||||
DateTime CreatedAt, DateTime? UpdatedAt, bool IsAdmin = false);
|
||||
|
||||
public sealed class CreateRoleRequest
|
||||
{
|
||||
|
||||
@@ -84,3 +84,10 @@ public sealed record SendResetLinkResponse(DateTime? ExpiresAt);
|
||||
|
||||
/// <summary>AuthHex UserType lookup, for populating the create-user form's select (no local shadow — read-only passthrough).</summary>
|
||||
public sealed record UserTypeOptionDto(Guid UserTypeId, string? Code, string? Description);
|
||||
|
||||
/// <summary>Outcome of the one-time copy of each user's role from AuthHex into the ERP's users table.</summary>
|
||||
public sealed record BackfillRolesResultDto(
|
||||
int Examined, int Assigned, int AlreadyHadRole, IReadOnlyList<BackfillRolesIssueDto> Issues);
|
||||
|
||||
/// <summary>A user the backfill could not give a role, and why — for an administrator to assign by hand.</summary>
|
||||
public sealed record BackfillRolesIssueDto(int UserId, string Username, string Reason);
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace ERPCore.Dtos.Vendors;
|
||||
/// <summary>Vendor resource (docs/11-BACKEND-PHASE1.md §2.4).</summary>
|
||||
public sealed record VendorDto(
|
||||
int VendorId, string Code, string Name, string? Terms, string? TaxReg,
|
||||
string Currency, EntityStatus Status, int BranchId, DateTime CreatedAt, DateTime? UpdatedAt);
|
||||
string Currency, EntityStatus Status, int BranchId, DateTime CreatedAt, DateTime? UpdatedAt,
|
||||
string? Mobile = null, string? Email = null, string? Address = null);
|
||||
|
||||
public sealed class CreateVendorRequest
|
||||
{
|
||||
@@ -15,6 +16,12 @@ public sealed class CreateVendorRequest
|
||||
[StringLength(50)] public string? Terms { get; set; }
|
||||
[StringLength(50)] public string? TaxReg { get; set; }
|
||||
[Required, StringLength(3, MinimumLength = 3)] public string Currency { get; set; } = "LKR";
|
||||
/// <summary>Only accepted while the vendor contact mode is Mobile (otherwise 422 CONFIG_DISABLED).</summary>
|
||||
[StringLength(20)] public string? Mobile { get; set; }
|
||||
/// <summary>Only accepted while the vendor contact mode is Email (otherwise 422 CONFIG_DISABLED).</summary>
|
||||
[StringLength(200)] public string? Email { get; set; }
|
||||
/// <summary>Only accepted while vendor address is enabled (otherwise 422 CONFIG_DISABLED).</summary>
|
||||
[StringLength(500)] public string? Address { get; set; }
|
||||
/// <summary>Optional — resolved via <see cref="Services.Interfaces.IBranchConfigService.ResolveBranchIdAsync"/>
|
||||
/// when branching is enabled; ignored (defaulted) otherwise.</summary>
|
||||
public int BranchId { get; set; }
|
||||
@@ -27,6 +34,12 @@ public sealed class UpdateVendorRequest
|
||||
[StringLength(50)] public string? Terms { get; set; }
|
||||
[StringLength(50)] public string? TaxReg { get; set; }
|
||||
[Required, StringLength(3, MinimumLength = 3)] public string Currency { get; set; } = "LKR";
|
||||
/// <summary>Only accepted while the vendor contact mode is Mobile (otherwise 422 CONFIG_DISABLED).</summary>
|
||||
[StringLength(20)] public string? Mobile { get; set; }
|
||||
/// <summary>Only accepted while the vendor contact mode is Email (otherwise 422 CONFIG_DISABLED).</summary>
|
||||
[StringLength(200)] public string? Email { get; set; }
|
||||
/// <summary>Only accepted while vendor address is enabled (otherwise 422 CONFIG_DISABLED).</summary>
|
||||
[StringLength(500)] public string? Address { get; set; }
|
||||
public int BranchId { get; set; }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user