14026818e2
- CashFlowStatementDto (Operating, Investing, Financing sections; BeginningCash/EndingCash) - CashFlowLineDto for Investing/Financing line items - GetCashFlowStatementAsync on IFinancialReportService + implementation in FinancialReportService - GenerateCashFlowStatementPdfAsync on IPdfService + QuestPDF implementation in PdfService - ReportsController.CashFlowStatement GET + CashFlowStatementPdf GET with inline/download mode - CashFlowStatement.cshtml view with date filter, 3-section cards, summary sidebar, methodology note - Reports Landing page: Cash Flow Statement card added to Accounting section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
378 lines
16 KiB
C#
378 lines
16 KiB
C#
using PowderCoating.Core.Enums;
|
|
|
|
namespace PowderCoating.Application.DTOs.Accounting;
|
|
|
|
// Accounting method badge — set on report DTOs so views can show "Cash Basis" / "Accrual Basis"
|
|
// without needing a separate round-trip to the company settings.
|
|
|
|
|
|
// ── Cash Flow Statement ──────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Cash Flow Statement using the direct (cash-basis) method for operating activities.
|
|
/// Investing and Financing sections contain line items derived from account-level changes.
|
|
/// BeginningCash + NetChangeInCash should equal EndingCash (within rounding tolerances).
|
|
/// </summary>
|
|
public class CashFlowStatementDto
|
|
{
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
public DateTime From { get; set; }
|
|
public DateTime To { get; set; }
|
|
public AccountingMethod Method { get; set; }
|
|
|
|
// ── Operating (direct / cash method) ───────────────────────────────────
|
|
/// <summary>Customer invoice payments received in the period.</summary>
|
|
public decimal CashFromCustomers { get; set; }
|
|
/// <summary>Vendor bill payments made in the period.</summary>
|
|
public decimal CashToVendors { get; set; }
|
|
/// <summary>Direct expense payments made in the period (not via bills).</summary>
|
|
public decimal CashForExpenses { get; set; }
|
|
public decimal NetOperating => CashFromCustomers - CashToVendors - CashForExpenses;
|
|
|
|
// ── Investing ──────────────────────────────────────────────────────────
|
|
public List<CashFlowLineDto> InvestingLines { get; set; } = new();
|
|
public decimal NetInvesting => InvestingLines.Sum(l => l.Amount);
|
|
|
|
// ── Financing ──────────────────────────────────────────────────────────
|
|
public List<CashFlowLineDto> FinancingLines { get; set; } = new();
|
|
public decimal NetFinancing => FinancingLines.Sum(l => l.Amount);
|
|
|
|
// ── Summary ────────────────────────────────────────────────────────────
|
|
public decimal BeginningCash { get; set; }
|
|
public decimal NetChangeInCash => NetOperating + NetInvesting + NetFinancing;
|
|
public decimal EndingCash => BeginningCash + NetChangeInCash;
|
|
}
|
|
|
|
/// <summary>A single line in the Investing or Financing section of the Cash Flow Statement.</summary>
|
|
public class CashFlowLineDto
|
|
{
|
|
public string Label { get; set; } = string.Empty;
|
|
/// <summary>Positive = cash inflow, negative = cash outflow.</summary>
|
|
public decimal Amount { get; set; }
|
|
}
|
|
|
|
// ── Customer / Vendor Statements ─────────────────────────────────────────────
|
|
|
|
public class CustomerStatementDto
|
|
{
|
|
public int CustomerId { get; set; }
|
|
public string CustomerName { get; set; } = string.Empty;
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
public string? CustomerAddress { get; set; }
|
|
public DateTime From { get; set; }
|
|
public DateTime To { get; set; }
|
|
public decimal OpeningBalance { get; set; }
|
|
public List<StatementLineDto> Lines { get; set; } = new();
|
|
public decimal ClosingBalance { get; set; }
|
|
}
|
|
|
|
public class VendorStatementDto
|
|
{
|
|
public int VendorId { get; set; }
|
|
public string VendorName { get; set; } = string.Empty;
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
public DateTime From { get; set; }
|
|
public DateTime To { get; set; }
|
|
public decimal OpeningBalance { get; set; }
|
|
public List<StatementLineDto> Lines { get; set; } = new();
|
|
public decimal ClosingBalance { get; set; }
|
|
}
|
|
|
|
public class StatementLineDto
|
|
{
|
|
public DateTime Date { get; set; }
|
|
/// <summary>E.g., "Invoice", "Payment", "Credit Applied", "Deposit Applied".</summary>
|
|
public string Type { get; set; } = string.Empty;
|
|
public string Reference { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
/// <summary>Amount added to the balance (invoice for customer, bill for vendor).</summary>
|
|
public decimal? Debit { get; set; }
|
|
/// <summary>Amount reducing the balance (payment, credit).</summary>
|
|
public decimal? Credit { get; set; }
|
|
public decimal RunningBalance { get; set; }
|
|
}
|
|
|
|
// ── AP Aging ──────────────────────────────────────────────────────────────────
|
|
|
|
public class ApAgingReportDto
|
|
{
|
|
public DateTime AsOf { get; set; }
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
|
|
public List<ApAgingVendorDto> Vendors { get; set; } = new();
|
|
|
|
public decimal TotalCurrent { get; set; }
|
|
public decimal Total1to30 { get; set; }
|
|
public decimal Total31to60 { get; set; }
|
|
public decimal Total61to90 { get; set; }
|
|
public decimal TotalOver90 { get; set; }
|
|
public decimal TotalOutstanding => TotalCurrent + Total1to30 + Total31to60 + Total61to90 + TotalOver90;
|
|
}
|
|
|
|
public class ApAgingVendorDto
|
|
{
|
|
public int VendorId { get; set; }
|
|
public string VendorName { get; set; } = string.Empty;
|
|
public List<ApAgingBillDto> Bills { get; set; } = new();
|
|
public decimal TotalCurrent { get; set; }
|
|
public decimal Total1to30 { get; set; }
|
|
public decimal Total31to60 { get; set; }
|
|
public decimal Total61to90 { get; set; }
|
|
public decimal TotalOver90 { get; set; }
|
|
public decimal TotalBalance => TotalCurrent + Total1to30 + Total31to60 + Total61to90 + TotalOver90;
|
|
}
|
|
|
|
public class ApAgingBillDto
|
|
{
|
|
public int BillId { get; set; }
|
|
public string BillNumber { get; set; } = string.Empty;
|
|
public DateTime BillDate { get; set; }
|
|
public DateTime? DueDate { get; set; }
|
|
public decimal BalanceDue { get; set; }
|
|
public int DaysOverdue { get; set; }
|
|
}
|
|
|
|
// ── Trial Balance ─────────────────────────────────────────────────────────────
|
|
|
|
public class TrialBalanceDto
|
|
{
|
|
public DateTime AsOf { get; set; }
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
public List<TrialBalanceLine> Lines { get; set; } = new();
|
|
public decimal TotalDebits { get; set; }
|
|
public decimal TotalCredits { get; set; }
|
|
public bool IsBalanced => Math.Abs(TotalDebits - TotalCredits) < 0.01m;
|
|
}
|
|
|
|
public class TrialBalanceLine
|
|
{
|
|
public int AccountId { get; set; }
|
|
public string AccountNumber { get; set; } = string.Empty;
|
|
public string AccountName { get; set; } = string.Empty;
|
|
public AccountType AccountType { get; set; }
|
|
public decimal DebitBalance { get; set; }
|
|
public decimal CreditBalance { get; set; }
|
|
}
|
|
|
|
// ── Profit & Loss ─────────────────────────────────────────────────────────────
|
|
|
|
public class ProfitAndLossDto
|
|
{
|
|
public DateTime From { get; set; }
|
|
public DateTime To { get; set; }
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
public AccountingMethod AccountingMethod { get; set; } = AccountingMethod.Accrual;
|
|
|
|
public List<FinancialReportLine> RevenueLines { get; set; } = new();
|
|
public decimal TotalRevenue { get; set; }
|
|
|
|
public List<FinancialReportLine> CogsLines { get; set; } = new();
|
|
public decimal TotalCogs { get; set; }
|
|
|
|
public decimal GrossProfit => TotalRevenue - TotalCogs;
|
|
public decimal GrossMarginPercent => TotalRevenue == 0 ? 0 : Math.Round(GrossProfit / TotalRevenue * 100, 1);
|
|
|
|
public List<FinancialReportLine> ExpenseLines { get; set; } = new();
|
|
public decimal TotalExpenses { get; set; }
|
|
|
|
public decimal OperatingIncome => GrossProfit - TotalExpenses;
|
|
public decimal NetIncome => OperatingIncome; // Extend later for other income/tax
|
|
}
|
|
|
|
public class FinancialReportLine
|
|
{
|
|
public int AccountId { get; set; }
|
|
public string AccountNumber { get; set; } = string.Empty;
|
|
public string AccountName { get; set; } = string.Empty;
|
|
public decimal Amount { get; set; }
|
|
}
|
|
|
|
// ── Balance Sheet ─────────────────────────────────────────────────────────────
|
|
|
|
public class BalanceSheetDto
|
|
{
|
|
public DateTime AsOf { get; set; }
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
public AccountingMethod AccountingMethod { get; set; } = AccountingMethod.Accrual;
|
|
|
|
// Assets
|
|
public List<FinancialReportLine> CurrentAssets { get; set; } = new();
|
|
public List<FinancialReportLine> FixedAssets { get; set; } = new();
|
|
public List<FinancialReportLine> OtherAssets { get; set; } = new();
|
|
public decimal TotalAssets { get; set; }
|
|
|
|
// Liabilities
|
|
public List<FinancialReportLine> CurrentLiabilities { get; set; } = new();
|
|
public List<FinancialReportLine> LongTermLiabilities { get; set; } = new();
|
|
public decimal TotalLiabilities { get; set; }
|
|
|
|
// Equity
|
|
public List<FinancialReportLine> EquityLines { get; set; } = new();
|
|
public decimal RetainedEarnings { get; set; } // Computed net income to date
|
|
public decimal TotalEquity { get; set; }
|
|
|
|
public decimal TotalLiabilitiesAndEquity => TotalLiabilities + TotalEquity;
|
|
|
|
// Helper: is the sheet balanced?
|
|
public bool IsBalanced => Math.Abs(TotalAssets - TotalLiabilitiesAndEquity) < 0.01m;
|
|
}
|
|
|
|
// ── AR Aging ──────────────────────────────────────────────────────────────────
|
|
|
|
public class ArAgingReportDto
|
|
{
|
|
public DateTime AsOf { get; set; }
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
|
|
public List<ArAgingCustomerDto> Customers { get; set; } = new();
|
|
|
|
public decimal TotalCurrent { get; set; }
|
|
public decimal Total1to30 { get; set; }
|
|
public decimal Total31to60 { get; set; }
|
|
public decimal Total61to90 { get; set; }
|
|
public decimal TotalOver90 { get; set; }
|
|
public decimal TotalOutstanding => TotalCurrent + Total1to30 + Total31to60 + Total61to90 + TotalOver90;
|
|
}
|
|
|
|
public class ArAgingCustomerDto
|
|
{
|
|
public int CustomerId { get; set; }
|
|
public string CustomerName { get; set; } = string.Empty;
|
|
public List<ArAgingInvoiceDto> Invoices { get; set; } = new();
|
|
public decimal TotalCurrent { get; set; }
|
|
public decimal Total1to30 { get; set; }
|
|
public decimal Total31to60 { get; set; }
|
|
public decimal Total61to90 { get; set; }
|
|
public decimal TotalOver90 { get; set; }
|
|
public decimal TotalBalance => TotalCurrent + Total1to30 + Total31to60 + Total61to90 + TotalOver90;
|
|
}
|
|
|
|
public class ArAgingInvoiceDto
|
|
{
|
|
public int InvoiceId { get; set; }
|
|
public string InvoiceNumber { get; set; } = string.Empty;
|
|
public DateTime InvoiceDate { get; set; }
|
|
public DateTime? DueDate { get; set; }
|
|
public decimal BalanceDue { get; set; }
|
|
public int DaysOverdue { get; set; }
|
|
}
|
|
|
|
// ── Sales & Income ────────────────────────────────────────────────────────────
|
|
|
|
public class SalesIncomeReportDto
|
|
{
|
|
public DateTime From { get; set; }
|
|
public DateTime To { get; set; }
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
|
|
public decimal TotalInvoiced { get; set; }
|
|
public decimal TotalCollected { get; set; }
|
|
public decimal TotalTax { get; set; }
|
|
public decimal TotalDiscount { get; set; }
|
|
public decimal NetRevenue => TotalInvoiced - TotalDiscount;
|
|
public int InvoiceCount { get; set; }
|
|
public int CustomerCount { get; set; }
|
|
public decimal AverageInvoiceValue => InvoiceCount == 0 ? 0 : Math.Round(TotalInvoiced / InvoiceCount, 2);
|
|
|
|
public List<SalesByCustomerDto> ByCustomer { get; set; } = new();
|
|
public List<SalesByMonthDto> ByMonth { get; set; } = new();
|
|
public List<SalesInvoiceLineDto> Invoices { get; set; } = new();
|
|
}
|
|
|
|
public class SalesByCustomerDto
|
|
{
|
|
public int CustomerId { get; set; }
|
|
public string CustomerName { get; set; } = string.Empty;
|
|
public int InvoiceCount { get; set; }
|
|
public decimal TotalInvoiced { get; set; }
|
|
public decimal TotalPaid { get; set; }
|
|
public decimal BalanceDue { get; set; }
|
|
}
|
|
|
|
public class SalesByMonthDto
|
|
{
|
|
public int Year { get; set; }
|
|
public int Month { get; set; }
|
|
public string Label { get; set; } = string.Empty;
|
|
public decimal TotalInvoiced { get; set; }
|
|
public decimal TotalCollected { get; set; }
|
|
public int InvoiceCount { get; set; }
|
|
}
|
|
|
|
public class SalesInvoiceLineDto
|
|
{
|
|
public int InvoiceId { get; set; }
|
|
public string InvoiceNumber { get; set; } = string.Empty;
|
|
public string CustomerName { get; set; } = string.Empty;
|
|
public DateTime InvoiceDate { get; set; }
|
|
public DateTime? DueDate { get; set; }
|
|
public string Status { get; set; } = string.Empty;
|
|
public decimal SubTotal { get; set; }
|
|
public decimal TaxAmount { get; set; }
|
|
public decimal Total { get; set; }
|
|
public decimal AmountPaid { get; set; }
|
|
public decimal BalanceDue { get; set; }
|
|
}
|
|
|
|
// ============================================================
|
|
// SALES TAX REPORT
|
|
// ============================================================
|
|
|
|
public class SalesTaxReportDto
|
|
{
|
|
public DateTime From { get; set; }
|
|
public DateTime To { get; set; }
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
|
|
/// <summary>Subtotal of invoices where TaxAmount > 0.</summary>
|
|
public decimal TotalTaxableSales { get; set; }
|
|
/// <summary>Subtotal of invoices where TaxAmount == 0.</summary>
|
|
public decimal TotalNonTaxableSales { get; set; }
|
|
/// <summary>Sum of all TaxAmount values across the period.</summary>
|
|
public decimal TotalTaxBilled { get; set; }
|
|
public int TaxableInvoiceCount { get; set; }
|
|
public int NonTaxableInvoiceCount { get; set; }
|
|
public decimal EffectiveTaxRate => TotalTaxableSales == 0 ? 0
|
|
: Math.Round(TotalTaxBilled / TotalTaxableSales * 100, 2);
|
|
|
|
public List<SalesTaxByAccountDto> ByAccount { get; set; } = new();
|
|
public List<SalesTaxByMonthDto> ByMonth { get; set; } = new();
|
|
public List<SalesTaxInvoiceLineDto> Invoices { get; set; } = new();
|
|
}
|
|
|
|
public class SalesTaxByAccountDto
|
|
{
|
|
public int? AccountId { get; set; }
|
|
public string AccountName { get; set; } = string.Empty;
|
|
public string AccountNumber { get; set; } = string.Empty;
|
|
public decimal TaxableSales { get; set; }
|
|
public decimal TaxBilled { get; set; }
|
|
public int InvoiceCount { get; set; }
|
|
}
|
|
|
|
public class SalesTaxByMonthDto
|
|
{
|
|
public int Year { get; set; }
|
|
public int Month { get; set; }
|
|
public string Label { get; set; } = string.Empty;
|
|
public decimal TaxableSales { get; set; }
|
|
public decimal TaxBilled { get; set; }
|
|
public int InvoiceCount { get; set; }
|
|
}
|
|
|
|
public class SalesTaxInvoiceLineDto
|
|
{
|
|
public int InvoiceId { get; set; }
|
|
public string InvoiceNumber { get; set; } = string.Empty;
|
|
public string CustomerName { get; set; } = string.Empty;
|
|
public DateTime InvoiceDate { get; set; }
|
|
public string Status { get; set; } = string.Empty;
|
|
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; }
|
|
public decimal BalanceDue { get; set; }
|
|
public string TaxAccountName { get; set; } = string.Empty;
|
|
}
|