86 lines
3.6 KiB
C#
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();
|
|
});
|
|
}
|
|
}
|
|
}
|