using System.Globalization; using Nop.Services.Localization; using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; using IContainer = QuestPDF.Infrastructure.IContainer; namespace Nop.Services.Common.Pdf; /// /// Represents the invoice document /// public partial class InvoiceDocument : PdfDocument { #region Ctor public InvoiceDocument(InvoiceSource invoiceSource, ILocalizationService localizationService) : base(invoiceSource, localizationService) { } #endregion #region Utilities /// /// Compose the invoice /// /// Content placement container protected void ComposeContent(IContainer container) { container.PaddingVertical(20).Column(column => { column.Spacing(15); column.Item().Table(table => { table.ColumnsDefinition(columns => { columns.RelativeColumn(); columns.ConstantColumn(50); columns.RelativeColumn(); }); table.Cell().Element(ComposeBillingAddress); table.Cell(); table.Cell().Element(ComposeShippingAddress); }); column.Item().Element(ComposeProducts); column.Item().ShowEntire().Element(ComposeCheckoutAttributes); column.Item().ShowEntire().Element(c => ComposeOrderTotals(c, Source.Totals)); column.Item().Element(ComposeOrderNotes); }); } /// /// Compose billing address /// /// Content placement container protected void ComposeBillingAddress(IContainer container) { container.Column(column => { column.Spacing(2); column.Item() .BorderBottom(1) .DefaultTextStyle(style => style.SemiBold()) .Text(t => ComposeLabel(t, x => x.BillingAddress)); column.Item().Column(c => ComposeAddress(c, Source.BillingAddress)); }); } /// /// Compose shipping address /// /// Content placement container protected void ComposeShippingAddress(IContainer container) { container.Column(column => { column.Spacing(2); column.Item() .BorderBottom(1) .DefaultTextStyle(style => style.SemiBold()) .Text(t => ComposeLabel(t, x => x.ShippingAddress)); column.Item().Column(c => ComposeAddress(c, Source.ShippingAddress)); }); } /// /// Compose the header /// /// Content placement container protected void ComposeHeader(IContainer container) { container.ShowOnce().DefaultTextStyle(tStyle => tStyle.SemiBold()).Row(row => { row.RelativeItem().Column(column => { column.Item().Text(t => ComposeField(t, Source, x => x.OrderNumberText, delimiter: "# ")); column.Item().Hyperlink(Source.StoreUrl).Text(Source.StoreUrl); column.Item().Text(t => ComposeField(t, Source, x => x.OrderDateUser, date => date.ToString("D", new CultureInfo(Source.Language.LanguageCulture)), ": " )); }); var logoContainer = row.ConstantItem(65).Height(65); if (Source.LogoData is not null && Source.LogoData.Length != 0) logoContainer.Image(Source.LogoData, ImageScaling.FitArea); }); } /// /// Compose order products /// /// Content placement container protected void ComposeProducts(IContainer container) { container.Table(table => { table.ColumnsDefinition(columns => { columns.RelativeColumn(3); if (Source.ShowSkuInProductList) columns.RelativeColumn(); if (Source.ShowVendorInProductList) columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); }); table.Header(header => { header.Cell().Element(cellStyle).Text(t => ComposeLabel(t, x => x.Name)); if (Source.ShowSkuInProductList) header.Cell().Element(cellStyle).Text(t => ComposeLabel(t, x => x.Sku)); if (Source.ShowVendorInProductList) header.Cell().Element(cellStyle).Text(t => ComposeLabel(t, x => x.VendorName)); header.Cell().Element(cellStyle).AlignRight().Text(t => ComposeLabel(t, x => x.Price)); header.Cell().Element(cellStyle).AlignRight().Text(t => ComposeLabel(t, x => x.Quantity)); header.Cell().Element(cellStyle).AlignRight().Text(t => ComposeLabel(t, x => x.Total)); static IContainer cellStyle(IContainer container) { return container.DefaultTextStyle(x => x.SemiBold()).PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black); } }); foreach (var product in Source.Products) { table.Cell().Element(cellStyle).Element(productContainer => { productContainer.Column(pColumn => { pColumn.Item().Text(product.Name); foreach (var attribute in product.ProductAttributes) pColumn.Item().DefaultTextStyle(s => s.Italic().FontSize(9)).Text(attribute); }); }); if (Source.ShowSkuInProductList) table.Cell().Element(cellStyle).Text(product.Sku); if (Source.ShowVendorInProductList) table.Cell().Element(cellStyle).Text(product.VendorName); table.Cell().Element(cellStyle).AlignRight().Text(product.Price); table.Cell().Element(cellStyle).AlignRight().Text(product.Quantity); table.Cell().Element(cellStyle).AlignRight().Text(product.Total); static IContainer cellStyle(IContainer container) { return container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5).ShowEntire(); } } }); } /// /// Compose an address /// /// Address item protected ColumnDescriptor ComposeAddress(ColumnDescriptor column, AddressItem address) { column.Item().Text(t => ComposeField(t, address, x => x.Company, delimiter: ": ")); column.Item().Text(t => ComposeField(t, address, x => x.Name, delimiter: ": ")); column.Item().Text(t => ComposeField(t, address, x => x.Phone, delimiter: ": ")); column.Item().Text(t => ComposeField(t, address, x => x.AddressLine, delimiter: ": ")); column.Item().Text(t => ComposeField(t, address, x => x.VATNumber)); foreach (var attribute in address.AddressAttributes) column.Item().Text(attribute); column.Item().Text(t => ComposeField(t, address, x => x.PaymentMethod, delimiter: ": ")); column.Item().Text(t => ComposeField(t, address, x => x.ShippingMethod, delimiter: ": ")); foreach (var (key, value) in address.CustomValues) { column.Item().Text(text => { text.Span(key); text.Span(": "); text.Span(value?.ToString()); }); } return column; } /// /// Compose checkout attributes /// /// Content placement container protected void ComposeCheckoutAttributes(IContainer container) { if (string.IsNullOrEmpty(Source.CheckoutAttributes)) return; container.DefaultTextStyle(tStyle => tStyle.SemiBold()).Column(column => { column.Spacing(5); column.Item().Text(Source.CheckoutAttributes); }); } /// /// Compose the order totals /// /// Content placement container protected void ComposeOrderTotals(IContainer container, InvoiceTotals totals) { container.AlignRight().Column(column => { column.Item().Text(t => ComposeField(t, totals, x => x.SubTotal, delimiter: ": ")); column.Item().Text(t => ComposeField(t, totals, x => x.Discount, delimiter: ": ")); column.Item().Text(t => ComposeField(t, totals, x => x.Shipping, delimiter: ": ")); column.Item().Text(t => ComposeField(t, totals, x => x.PaymentMethodAdditionalFee, delimiter: ": ")); column.Item().Text(t => ComposeField(t, totals, x => x.Tax, delimiter: ": ")); foreach (var rate in totals.TaxRates) column.Item().Text(rate); foreach (var card in totals.GiftCards) column.Item().Text(card); column.Item().Text(totals.RewardPoints); column.Item().Text(totals.OrderTotal); }); } /// /// Compose order notes /// /// Content placement container protected void ComposeOrderNotes(IContainer container) { if (Source.OrderNotes?.Any() != true) return; container.Background(Colors.Grey.Lighten3).Padding(10).Column(column => { column.Spacing(5); column.Item() .BorderBottom(1).BorderColor(Colors.Black) .DefaultTextStyle(style => style.SemiBold()) .Text(t => ComposeLabel(t, x => x.OrderNotes)); foreach (var (createdOn, note) in Source.OrderNotes) { column.Item() .ShowEntire() .DefaultTextStyle(DefaultStyle.FontSize(10)) .PaddingVertical(5) .Row(row => { row.AutoItem().Text(createdOn); row.RelativeItem().PaddingLeft(10) .Text(note); }); } }); } /// /// Compose the footer /// /// Content placement container protected virtual void ComposeFooter(IContainer container) { container.Column(column => { column.Item().Row(row => { row.RelativeItem().Column(col1 => { foreach (var line in Source.FooterTextColumn1) col1.Item().Text(line); }); row.RelativeItem().Column(col2 => { foreach (var line in Source.FooterTextColumn2) col2.Item().Text(line); }); }); column.Item().AlignCenter().PaddingTop(10) .Text(text => text.CurrentPageNumber().Format(p => $"- {p} -")); }); } #endregion #region Methods /// /// Compose document's structure /// /// Content placement container public override void Compose(IDocumentContainer container) { container .Page(page => { var titleStyle = DefaultStyle.FontSize(10).NormalWeight(); page.DefaultTextStyle(titleStyle); if (Source.IsRightToLeft) page.ContentFromRightToLeft(); page.Size(Source.PageSize); page.Margin(35); page.MarginBottom(20); page.Header().Element(ComposeHeader); page.Content().Element(ComposeContent); page.Footer().AlignCenter().Element(ComposeFooter); }); } #endregion }