359 lines
12 KiB
C#
359 lines
12 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Represents the invoice document
|
|
/// </summary>
|
|
public partial class InvoiceDocument : PdfDocument<InvoiceSource>
|
|
{
|
|
|
|
#region Ctor
|
|
|
|
public InvoiceDocument(InvoiceSource invoiceSource, ILocalizationService localizationService) : base(invoiceSource, localizationService)
|
|
{
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utilities
|
|
|
|
/// <summary>
|
|
/// Compose the invoice
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
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);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose billing address
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
protected void ComposeBillingAddress(IContainer container)
|
|
{
|
|
container.Column(column =>
|
|
{
|
|
column.Spacing(2);
|
|
|
|
column.Item()
|
|
.BorderBottom(1)
|
|
.DefaultTextStyle(style => style.SemiBold())
|
|
.Text(t => ComposeLabel<InvoiceSource>(t, x => x.BillingAddress));
|
|
|
|
column.Item().Column(c => ComposeAddress(c, Source.BillingAddress));
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose shipping address
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
protected void ComposeShippingAddress(IContainer container)
|
|
{
|
|
container.Column(column =>
|
|
{
|
|
column.Spacing(2);
|
|
|
|
column.Item()
|
|
.BorderBottom(1)
|
|
.DefaultTextStyle(style => style.SemiBold())
|
|
.Text(t => ComposeLabel<InvoiceSource>(t, x => x.ShippingAddress));
|
|
|
|
column.Item().Column(c => ComposeAddress(c, Source.ShippingAddress));
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose the header
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
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);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose order products
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
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<ProductItem>(t, x => x.Name));
|
|
|
|
if (Source.ShowSkuInProductList)
|
|
header.Cell().Element(cellStyle).Text(t => ComposeLabel<ProductItem>(t, x => x.Sku));
|
|
|
|
if (Source.ShowVendorInProductList)
|
|
header.Cell().Element(cellStyle).Text(t => ComposeLabel<ProductItem>(t, x => x.VendorName));
|
|
|
|
header.Cell().Element(cellStyle).AlignRight().Text(t => ComposeLabel<ProductItem>(t, x => x.Price));
|
|
header.Cell().Element(cellStyle).AlignRight().Text(t => ComposeLabel<ProductItem>(t, x => x.Quantity));
|
|
header.Cell().Element(cellStyle).AlignRight().Text(t => ComposeLabel<ProductItem>(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();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose an address
|
|
/// </summary>
|
|
/// <param name="address">Address item</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose checkout attributes
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
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);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose the order totals
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
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);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose order notes
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
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<InvoiceSource>(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);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compose the footer
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Compose document's structure
|
|
/// </summary>
|
|
/// <param name="container">Content placement container</param>
|
|
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
|
|
} |