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!;
}