using PowderCoating.Core.Enums; namespace PowderCoating.Core.Entities; /// /// Chart of Accounts entry. Supports a flat or one-level hierarchy via ParentAccountId. /// public class Account : BaseEntity { public string AccountNumber { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public AccountType AccountType { get; set; } public AccountSubType AccountSubType { get; set; } public string? Description { get; set; } /// Nullable FK for sub-accounts (one level deep). public int? ParentAccountId { get; set; } /// System accounts cannot be deleted (seeded defaults). public bool IsSystem { get; set; } = false; public bool IsActive { get; set; } = true; /// Starting balance when the account was first set up in this system. public decimal OpeningBalance { get; set; } = 0; /// The date the opening balance is as-of. Null means it pre-dates all transactions. public DateTime? OpeningBalanceDate { get; set; } /// /// Denormalized running balance kept in sync with each transaction. Positive = normal-balance direction /// (debit-normal for Assets/Expenses/COGS; credit-normal for Liabilities/Equity/Revenue). /// Use AccountsController.RecalculateBalances to rebuild from scratch if needed. /// public decimal CurrentBalance { get; set; } = 0; // Navigation public virtual Account? ParentAccount { get; set; } public virtual ICollection SubAccounts { get; set; } = new List(); public virtual ICollection BillLineItems { get; set; } = new List(); public virtual ICollection Bills { get; set; } = new List(); public virtual ICollection BillPayments { get; set; } = new List(); public virtual ICollection Expenses { get; set; } = new List(); public virtual ICollection ExpensePaymentAccounts { get; set; } = new List(); } /// /// Vendor bill (accounts payable). Represents money owed to a supplier. /// public class Bill : BaseEntity { public string BillNumber { get; set; } = string.Empty; /// Vendor's own invoice/reference number. public string? VendorInvoiceNumber { get; set; } public int VendorId { get; set; } /// Which AP account this bill posts to (default: Accounts Payable 2000). public int APAccountId { get; set; } public DateTime BillDate { get; set; } = DateTime.UtcNow; public DateTime? DueDate { get; set; } public BillStatus Status { get; set; } = BillStatus.Draft; public string? Terms { get; set; } public string? Memo { get; set; } // Financials public decimal SubTotal { get; set; } public decimal TaxPercent { get; set; } public decimal TaxAmount { get; set; } public decimal Total { get; set; } public decimal AmountPaid { get; set; } [System.ComponentModel.DataAnnotations.Schema.NotMapped] public decimal BalanceDue => Total - AmountPaid; /// Blob path to an attached receipt/invoice document (PDF or image). public string? ReceiptFilePath { get; set; } // Navigation public virtual Vendor Vendor { get; set; } = null!; public virtual Account APAccount { get; set; } = null!; public virtual ICollection LineItems { get; set; } = new List(); public virtual ICollection Payments { get; set; } = new List(); } /// /// A single line on a vendor bill, posted to an expense or asset account. /// public class BillLineItem : BaseEntity { public int BillId { get; set; } /// Expense/asset account this line item is categorized under. Nullable for QB-imported bills where account is unknown. public int? AccountId { get; set; } /// Optional job costing link. public int? JobId { get; set; } public string Description { get; set; } = string.Empty; public decimal Quantity { get; set; } = 1; public decimal UnitPrice { get; set; } public decimal Amount { get; set; } public int DisplayOrder { get; set; } // Navigation public virtual Bill Bill { get; set; } = null!; public virtual Account? Account { get; set; } public virtual Job? Job { get; set; } } /// /// A payment made against a vendor bill. /// public class BillPayment : BaseEntity { public string PaymentNumber { get; set; } = string.Empty; public int BillId { get; set; } /// Denormalized for AP reporting without joining through Bill. public int VendorId { get; set; } /// Bank/cash account the payment came out of. public int BankAccountId { get; set; } public DateTime PaymentDate { get; set; } = DateTime.UtcNow; public decimal Amount { get; set; } public PaymentMethod PaymentMethod { get; set; } public string? CheckNumber { get; set; } public string? Memo { get; set; } /// True once this payment has been matched against a bank statement during reconciliation. public bool IsCleared { get; set; } = false; public DateTime? ClearedDate { get; set; } // Navigation public virtual Bill Bill { get; set; } = null!; public virtual Vendor Vendor { get; set; } = null!; public virtual Account BankAccount { get; set; } = null!; } /// /// A direct expense paid immediately (not via a bill). Covers cash/card purchases. /// public class Expense : BaseEntity { public string ExpenseNumber { get; set; } = string.Empty; public DateTime Date { get; set; } = DateTime.UtcNow; /// Optional vendor the expense was paid to. public int? VendorId { get; set; } /// Expense category account (e.g. 6200 Powder & Materials). public int ExpenseAccountId { get; set; } /// Account money came out of (e.g. 1000 Checking, 2100 Credit Card). public int PaymentAccountId { get; set; } /// Optional job costing link. public int? JobId { get; set; } public PaymentMethod PaymentMethod { get; set; } public decimal Amount { get; set; } public string? Memo { get; set; } public string? ReceiptFilePath { get; set; } /// True once this expense has been matched against a bank statement during reconciliation. public bool IsCleared { get; set; } = false; public DateTime? ClearedDate { get; set; } // Navigation public virtual Vendor? Vendor { get; set; } public virtual Account ExpenseAccount { get; set; } = null!; public virtual Account PaymentAccount { get; set; } = null!; public virtual Job? Job { get; set; } } /// /// Manual double-entry journal entry. Lines must balance (sum of debits == sum of credits) /// before posting. Once posted the entry is immutable — use Reverse to correct it. /// Entry numbering follows the pattern JE-YYMM-#### scoped per company. /// public class JournalEntry : BaseEntity { public string EntryNumber { get; set; } = string.Empty; public DateTime EntryDate { get; set; } = DateTime.UtcNow; public string? Reference { get; set; } public string? Description { get; set; } public JournalEntryStatus Status { get; set; } = JournalEntryStatus.Draft; /// True if this entry was machine-generated as a reversal of another entry. public bool IsReversal { get; set; } = false; /// FK to the original entry being reversed. Null for normal entries. public int? ReversalOfId { get; set; } public DateTime? PostedAt { get; set; } public string? PostedBy { get; set; } // Navigation public virtual ICollection Lines { get; set; } = new List(); public virtual JournalEntry? ReversalOf { get; set; } } /// /// One debit or credit line within a . Either DebitAmount or CreditAmount /// should be non-zero per line (not both). LineOrder controls display sequence. /// public class JournalEntryLine : BaseEntity { public int JournalEntryId { get; set; } public int AccountId { get; set; } public decimal DebitAmount { get; set; } public decimal CreditAmount { get; set; } public string? Description { get; set; } public int LineOrder { get; set; } // Navigation public virtual JournalEntry JournalEntry { get; set; } = null!; public virtual Account Account { get; set; } = null!; } /// /// A bank reconciliation session for a single bank/cash account against a statement. /// Cleared balance = BeginningBalance + cleared deposits - cleared payments. /// The reconciliation is complete when Difference (EndingBalance - ClearedBalance) == 0. /// public class BankReconciliation : BaseEntity { /// Must be a bank/cash subtype account. public int AccountId { get; set; } public DateTime StatementDate { get; set; } public decimal BeginningBalance { get; set; } public decimal EndingBalance { get; set; } public BankReconciliationStatus Status { get; set; } = BankReconciliationStatus.InProgress; public DateTime? CompletedAt { get; set; } public string? CompletedBy { get; set; } public string? Notes { get; set; } // Navigation public virtual Account Account { get; set; } = null!; } /// /// A credit note received from a vendor (returned goods, pricing dispute, short-ship). /// Reduces Accounts Payable and reverses the original expense/COGS when posted. /// Numbering: VC-YYMM-#### /// public class VendorCredit : BaseEntity { public string CreditNumber { get; set; } = string.Empty; public int VendorId { get; set; } /// AP account this credit reduces (default: Accounts Payable 2000). public int APAccountId { get; set; } public DateTime CreditDate { get; set; } = DateTime.UtcNow; public VendorCreditStatus Status { get; set; } = VendorCreditStatus.Open; public decimal Total { get; set; } public decimal RemainingAmount { get; set; } public string? Memo { get; set; } /// Set by Post() when GL entries are made (DR AP / CR expense lines). Null = unposted. public DateTime? PostedDate { get; set; } // Navigation public virtual Vendor Vendor { get; set; } = null!; public virtual Account APAccount { get; set; } = null!; public virtual ICollection LineItems { get; set; } = new List(); public virtual ICollection Applications { get; set; } = new List(); } /// /// A single line on a vendor credit, each reversing a specific expense/COGS account. /// public class VendorCreditLineItem : BaseEntity { public int VendorCreditId { get; set; } /// Expense/COGS account being reversed by this line. public int? AccountId { get; set; } public string Description { get; set; } = string.Empty; public decimal Amount { get; set; } // Navigation public virtual VendorCredit VendorCredit { get; set; } = null!; public virtual Account? Account { get; set; } } /// /// Records the application of a vendor credit against a specific vendor bill. /// No additional GL posting is needed — AP was already adjusted when the credit was posted. /// public class VendorCreditApplication : BaseEntity { public int VendorCreditId { get; set; } public int BillId { get; set; } public decimal Amount { get; set; } public DateTime AppliedDate { get; set; } = DateTime.UtcNow; // Navigation public virtual VendorCredit VendorCredit { get; set; } = null!; public virtual Bill Bill { get; set; } = null!; } /// /// A saved recipe for a document that should be automatically created on a recurring schedule. /// The column stores a JSON blob whose schema depends on /// : see RecurringTransactionService for the exact shape. /// /// Bills are created as Draft so the user can review before posting. /// Expenses are created immediately (already-paid transactions). /// /// Numbering: REC-YYMM-#### /// public class RecurringTemplate : BaseEntity { public string Name { get; set; } = string.Empty; public RecurringTemplateType TemplateType { get; set; } public RecurringFrequency Frequency { get; set; } /// Every N periods. E.g. Frequency=Monthly, IntervalCount=3 → quarterly. public int IntervalCount { get; set; } = 1; /// UTC date when the template will next fire. Set to the desired first occurrence date on creation. public DateTime NextFireDate { get; set; } /// Optional UTC date after which no further occurrences are generated. public DateTime? EndDate { get; set; } /// Optional hard cap on total occurrences. Null = unlimited. public int? MaxOccurrences { get; set; } /// How many documents have been generated so far. public int OccurrenceCount { get; set; } public bool IsActive { get; set; } = true; /// JSON payload whose schema matches the TemplateType. See RecurringTransactionService. public string TemplateData { get; set; } = "{}"; /// Last error from the background service, cleared on next successful fire. public string? LastError { get; set; } } /// /// A named tax rate (e.g., "CA Sales Tax 8.25%") used to pre-fill the TaxPercent field on /// invoices when a taxable customer is selected. Companies can define multiple rates for /// different jurisdictions and mark one as default. /// public class TaxRate : BaseEntity { public string Name { get; set; } = string.Empty; /// Rate as a percentage, e.g., 8.25 means 8.25%. public decimal Rate { get; set; } public string? State { get; set; } public string? Description { get; set; } /// When true, this rate is auto-applied to new invoices for taxable customers. public bool IsDefault { get; set; } public bool IsActive { get; set; } = true; } /// /// A depreciable fixed asset (oven, blast cabinet, spray booth, vehicle, etc.). /// Stores straight-line depreciation parameters and links to the three GL accounts needed /// to auto-post monthly depreciation journal entries. /// public class FixedAsset : BaseEntity { public string Name { get; set; } = string.Empty; public string? Description { get; set; } public DateTime PurchaseDate { get; set; } public decimal PurchaseCost { get; set; } /// Residual value at end of useful life (often $0 for shop equipment). public decimal SalvageValue { get; set; } = 0; /// Total depreciation period in months (e.g., 60 = 5 years). public int UsefulLifeMonths { get; set; } /// Running total of depreciation posted so far. public decimal AccumulatedDepreciation { get; set; } = 0; public bool IsDisposed { get; set; } = false; public DateTime? DisposalDate { get; set; } // Computed — not persisted /// Current net book value: PurchaseCost minus AccumulatedDepreciation. public decimal BookValue => PurchaseCost - AccumulatedDepreciation; /// Straight-line monthly depreciation amount. public decimal MonthlyDepreciation => UsefulLifeMonths > 0 ? Math.Round((PurchaseCost - SalvageValue) / UsefulLifeMonths, 2) : 0; // GL account links — all optional; assets without accounts can be tracked but not auto-posted /// Balance Sheet FixedAsset account (debited when asset is purchased). public int? AssetAccountId { get; set; } /// P&L Depreciation Expense account (debited each period). public int? DepreciationExpenseAccountId { get; set; } /// Balance Sheet Accumulated Depreciation account (credited each period). public int? AccumDepreciationAccountId { get; set; } // Navigation public virtual Account? AssetAccount { get; set; } public virtual Account? DepreciationExpenseAccount { get; set; } public virtual Account? AccumDepreciationAccount { get; set; } public virtual ICollection DepreciationEntries { get; set; } = new List(); } /// /// Records each periodic depreciation posting for a fixed asset. One record per asset per /// month/year combination; linked to the JournalEntry that was created so the posting /// can be traced back through the GL. /// public class FixedAssetDepreciationEntry : BaseEntity { public int FixedAssetId { get; set; } public int PeriodYear { get; set; } public int PeriodMonth { get; set; } public decimal Amount { get; set; } /// The JE that was posted for this depreciation period (null if manually recorded). public int? JournalEntryId { get; set; } // Navigation public virtual FixedAsset FixedAsset { get; set; } = null!; public virtual JournalEntry? JournalEntry { get; set; } } /// /// A named annual budget. Contains one BudgetLine per account per month. Supports /// multiple budgets per fiscal year (e.g. "Conservative" vs "Optimistic") but only /// one is marked IsDefault for the Budget vs. Actual report. /// public class Budget : BaseEntity { public string Name { get; set; } = string.Empty; public int FiscalYear { get; set; } public string? Notes { get; set; } public bool IsDefault { get; set; } = false; public virtual ICollection Lines { get; set; } = new List(); } /// /// Monthly budget amount for one account within a Budget. Jan–Dec stored as separate /// columns so the grid editor can write them in a single POST without a line-item loop. /// Annual is a computed property summing all twelve months. /// public class BudgetLine : BaseEntity { public int BudgetId { get; set; } public int AccountId { get; set; } public decimal Jan { get; set; } public decimal Feb { get; set; } public decimal Mar { get; set; } public decimal Apr { get; set; } public decimal May { get; set; } public decimal Jun { get; set; } public decimal Jul { get; set; } public decimal Aug { get; set; } public decimal Sep { get; set; } public decimal Oct { get; set; } public decimal Nov { get; set; } public decimal Dec { get; set; } public decimal Annual => Jan + Feb + Mar + Apr + May + Jun + Jul + Aug + Sep + Oct + Nov + Dec; public virtual Budget Budget { get; set; } = null!; public virtual Account Account { get; set; } = null!; } /// /// Records a completed year-end close. The close posts a JE that zeroes all /// Revenue and Expense account balances into Retained Earnings, and marks /// the year as closed so it cannot be closed again. /// public class YearEndClose : BaseEntity { public int ClosedYear { get; set; } public DateTime ClosedAt { get; set; } = DateTime.UtcNow; public string? ClosedBy { get; set; } public int JournalEntryId { get; set; } public virtual JournalEntry JournalEntry { get; set; } = null!; }