Add scheme data model and migrations

This commit is contained in:
2026-08-21 14:10:40 +05:30
parent c9f5006727
commit f7101800cd
8 changed files with 694 additions and 0 deletions
+3
View File
@@ -26,6 +26,9 @@ namespace Distribution.Models
public decimal? SalePrice { get; set; }
/// <summary>Cost/purchase price, used to value free-goods scheme redemptions for getSchemeROI.</summary>
public decimal? CostPrice { get; set; }
public SyncSource SyncSource { get; set; } = SyncSource.Manual;
public int? ExternalErpItemId { get; set; }
public DateTime? LastSyncedAt { get; set; }
+62
View File
@@ -0,0 +1,62 @@
namespace Distribution.Models
{
public enum SchemeRuleType
{
BuyXGetY,
SlabDiscount,
FlatPercent,
FreeBundle
}
public class Scheme : Template
{
public string Name { get; set; }
public SchemeRuleType RuleType { get; set; }
/// <summary>
/// JSON payload for percentage-based rules only (SlabDiscount tiers, FlatPercent). Free-goods
/// rules (BuyXGetY / FreeBundle) are structured fields/child rows below, not JSON, so they can
/// be managed through their own CRUD endpoints instead of a hand-edited blob.
/// </summary>
public string? RuleConfig { get; set; }
// ── BuyXGetY: trigger item/qty; shares FK_FreeItemId/FreeQty below as the payout. ──────────
public Guid? FK_TriggerItemId { get; set; }
public Item? TriggerItem { get; set; }
public int? TriggerQty { get; set; }
// ── Free-goods payout, used by both BuyXGetY and FreeBundle. ─────────────────────────────
public Guid? FK_FreeItemId { get; set; }
public Item? FreeItem { get; set; }
public decimal? FreeQty { get; set; }
/// <summary>Required component items for a FreeBundle scheme — managed via its own CRUD.</summary>
public ICollection<SchemeBundleComponent> BundleComponents { get; set; } = [];
public Guid? FK_RegionId { get; set; }
public Region? Region { get; set; }
/// <summary>Store segment this scheme applies to (Gold/Silver/Key Account/...); null = all segments.</summary>
public string? EligibleSegment { get; set; }
public DateTime DateRangeFrom { get; set; }
public DateTime DateRangeTo { get; set; }
/// <summary>
/// When true, this scheme's discount is exempt from the per-role discount ceiling in
/// 08_sales_agent_controls.md and stacks on top of it unconditionally.
/// </summary>
public bool AgentLimitExempt { get; set; }
/// <summary>
/// Manual baseline (expected sales with no scheme running) used by getSchemeROI. Per the
/// open question in docs/17_promotions_schemes.md, this is entered at scheme creation
/// rather than computed from a trailing-period average.
/// </summary>
public decimal? BaselineExpectedSales { get; set; }
public ICollection<SchemeRedemption> Redemptions { get; set; } = [];
}
}
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
/// <summary>A required item+qty line of a FreeBundle scheme (e.g. "buy A + B, get C free").</summary>
public class SchemeBundleComponent
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid FK_SchemeId { get; set; }
[ForeignKey("FK_SchemeId")]
public Scheme Scheme { get; set; }
public Guid FK_ItemId { get; set; }
[ForeignKey("FK_ItemId")]
public Item Item { get; set; }
public int Qty { get; set; }
}
}
+36
View File
@@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class SchemeRedemption
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid FK_SchemeId { get; set; }
[ForeignKey("FK_SchemeId")]
public Scheme Scheme { get; set; }
public Guid? FK_OrderId { get; set; }
[ForeignKey("FK_OrderId")]
public Order? Order { get; set; }
/// <summary>Placeholder for the future field-invoice module — no FieldInvoice entity exists yet.</summary>
public Guid? FieldInvoiceId { get; set; }
public decimal? DiscountAmount { get; set; }
public Guid? FK_FreeItemId { get; set; }
[ForeignKey("FK_FreeItemId")]
public Item? FreeItem { get; set; }
public decimal? FreeQty { get; set; }
/// <summary>
/// Cost price of the free item snapshotted at redemption time, so getSchemeROI stays
/// accurate even after the item's cost price later changes.
/// </summary>
public decimal? FreeItemCostValue { get; set; }
public DateTime RedeemedAt { get; set; } = DateTime.Now;
}
}