Files
Distribution_GL/Services/PdfDocuments/ProfitAndLossPdfDocument.cs
T

86 lines
3.6 KiB
C#

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();
});
}
}
}