using General_Ledger_Service.DTOs.Reports; using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace General_Ledger_Service.Services.PdfDocuments { // 05_LKAS_Report_PDF_Templates.md Section 4 (LKAS 1). Retrofit 2026-07-22: restructured // around accounts.income_statement_section - Sales/Cost of Sales -> Gross Profit, then // Other Income and four expense groups -> Net Profit for the Period. Both GrossProfit and // NetProfitForPeriod are already computed server-side (ReportService.BuildProfitAndLossReportAsync). public class ProfitAndLossPdfDocument(string companyName, string currencyCode, ProfitAndLossReportDto data) : IDocument { public DocumentMetadata GetMetadata() => DocumentMetadata.Default; public void Compose(IDocumentContainer container) { container.Page(page => { page.Size(PageSizes.A4); page.Margin(2, Unit.Centimetre); ReportPageHeader.Compose(page, companyName, "Statement of Profit or Loss", ReportPageHeader.RangePeriodLine(data.PeriodStart, data.PeriodEnd), currencyCode); page.Content().PaddingTop(10).Column(column => { ComposeSection(column, "Sales", data.Sales); ComposeSection(column, "Cost of Sales", data.CostOfSales); ComposeTotal(column, "GROSS PROFIT", data.GrossProfit, topRule: true); ComposeSection(column, "Other Income", data.OtherIncome); ComposeSection(column, "Distribution Expenses", data.DistributionExpenses); ComposeSection(column, "Administration Expenses", data.AdministrationExpenses); ComposeSection(column, "Other Expenses", data.OtherExpenses); ComposeSection(column, "Financial Expenses", data.FinancialExpenses); if (data.Unclassified.Lines.Count > 0) { ComposeSection(column, "Unclassified", data.Unclassified); } ComposeTotal(column, "NET PROFIT FOR THE PERIOD", data.NetProfitForPeriod, topRule: true, doubleRule: true); }); }); } private static void ComposeSection(ColumnDescriptor column, string title, ProfitAndLossSectionDto section) { if (section.Lines.Count == 0) { return; } column.Item().PaddingTop(10).Text(title).Bold(); foreach (var line in section.Lines) { column.Item().Row(r => { r.RelativeItem(4).Text(line.AccountName); r.RelativeItem(1).AlignRight().Text(PdfFormatting.FormatAmount(line.Amount)); }); } column.Item().PaddingTop(3).LineHorizontal(1); column.Item().Row(r => { r.RelativeItem(4).Text($"Total {title}").Bold(); r.RelativeItem(1).AlignRight().Text(PdfFormatting.FormatAmount(section.Total)).Bold(); }); } private static void ComposeTotal(ColumnDescriptor column, string label, decimal amount, bool topRule, bool doubleRule = false) { if (topRule) { column.Item().PaddingTop(10).LineHorizontal(doubleRule ? 1.5f : 1); } column.Item().Row(r => { r.RelativeItem(4).Text(label).Bold(); r.RelativeItem(1).AlignRight().Text(PdfFormatting.FormatAmount(amount)).Bold(); }); } } }