.NET で PDF 請求書ジェネレーターを作成します

ブログ カテゴリ: PDFOffice.NET

2021/07/02

PDF ドキュメント形式で請求書を作成するタスクが頻繁に発生します。通常、請求書を表す文書には複雑なレイアウトが含まれています。ロゴと会社情報を含むヘッダー、売り手と買い手の情報、注文された品目の表などです。追加情報を含むフッター。

PDF ドキュメントはベクター ドキュメントであり、コンテンツをレイアウトする機能はありません。ベクター グラフィックを使用して PDF ページにドキュメント マークアップを描画するプログラミング コードを記述することは可能ですが、コードが複雑になり、開発に長い時間がかかる可能性があります。

複雑なタスクは、2 つの部分に分割することで簡素化できます。
1. テキスト エディターでドキュメント テンプレートを作成します。
2. テンプレートに動的なデータを入力するプログラミング コードを記述します。

DOCX ドキュメントはマークアップ形式でコンテンツを格納するため、DOCX ドキュメントを使用して、複雑な請求書のテンプレートを作成できます。
VintaSoft Imaging .NET SDK バージョン 10.1 以降では、既存の DOCX および XLSX ドキュメントをプログラムで編集できるようになりました。
この機能を使用すると、PDF ドキュメント形式でシンプルで簡単にカスタマイズできる請求書ジェネレーターを作成できます。

請求書ジェネレーターには、2 つの主要部分を含める必要があります:

DOCX ドキュメント テンプレートに基づいて請求書ジェネレーターを作成する利点は次のとおりです。

DOCX ドキュメントをプログラムで編集する方法の詳細については、こちら をお読みください。


PDF ドキュメント形式で請求書を生成する請求書ジェネレーターを作成するには、次の手順を実行する必要があります。
1. MS Word を使用して、請求書のすべての静的マークアップを含む請求書テンプレート Invoice_template.docx を作成します。
2. DocxDocumentEditor クラスを使用して、請求書に動的データを入力する機能をアプリケーションに実装します。

請求書を作成して PDF ドキュメントに保存する方法を示すコードは次のとおりです。
// The project, which uses this code, must have references to the following assemblies:
// - Vintasoft.Imaging
// - Vintasoft.Imaging.Office.OpenXml
// - Vintasoft.Imaging.Pdf
// - Vintasoft.Barcode

/// <summary>
/// Generates invoice, which is based on DOCX document template.
/// </summary>
public static void GenerateInvoiceUsingDocxTemplate()
{
    // create DOCX document editor and use file "Invoice_template.docx" as document template
    using (Vintasoft.Imaging.Office.OpenXml.Editor.DocxDocumentEditor editor =
        new Vintasoft.Imaging.Office.OpenXml.Editor.DocxDocumentEditor("Invoice_template.docx"))
    {
        // generate test invoice data with 30 items
        InvoiceData testData = GetTestData(30);

        // fill invoice data
        FillInvoiceData(editor, testData);

        // save invoice to a DOCX document if necessary
        //editor.Save("Invoice.docx");

        // export invoice to a PDF document
        editor.Export("Invoice_docx.pdf");
    }
}

/// <summary>
/// Fills the invoice data using DOCX document editor.
/// </summary>
/// <param name="documentEditor">The DOCX document editor.</param>
/// <param name="invoiceData">The invoice data.</param>
private static void FillInvoiceData(
    Vintasoft.Imaging.Office.OpenXml.Editor.DocxDocumentEditor documentEditor,
    InvoiceData invoiceData)
{
    // create QR code image 200x200 px
    using (Vintasoft.Imaging.VintasoftImage qrCodeImage = invoiceData.GetBarcodeImage(200))
    {
        // set barcode image to the image element at index 1
        documentEditor.Images[1].SetImage(qrCodeImage);
    }

    // fill the document header
    documentEditor.Body["[company_name]"] = invoiceData.Company.CompanyName;
    documentEditor.Body["[company_address]"] = invoiceData.Company.Address;
    documentEditor.Body["[company_city]"] = invoiceData.Company.City;
    documentEditor.Body["[company_phone]"] = invoiceData.Company.GetPhones();
    documentEditor.Body["[invoice_number]"] = invoiceData.InvoiceNumber;
    documentEditor.Body["[invoice_date]"] = System.DateTime.Now.ToShortDateString();

    // get all tables of document
    Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTable[] tables = documentEditor.Tables;

    // fill the "Customer Information" table
    Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTable customerInformationTable = tables[0];
    SetCompanyInformation(customerInformationTable, "billing", invoiceData.BillingAddress);
    SetCompanyInformation(customerInformationTable, "shipping", invoiceData.ShippingAddress);

    // fill the "Shipping Method" table
    Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTable shippingMethodTable = tables[1];
    shippingMethodTable["[shipping_method]"] = invoiceData.ShippingMethod;

    // fill the "Order Information" table
    Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTable orderInformationTable = tables[2];
    Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTableRow templateRow = orderInformationTable[1];
    int orderItemNumber = 1;
    // for each item in invoice
    foreach (InvoiceItem orderItem in invoiceData.OrderItems)
    {
        // copy template row and insert copy after template row
        Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTableRow currentRow = templateRow;
        templateRow = (Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTableRow)templateRow.InsertCopyAfterSelf();

        // fill data of current row
        currentRow["[p_n]"] = orderItemNumber.ToString();
        currentRow["[p_description]"] = orderItem.Product;
        currentRow["[p_qty]"] = orderItem.Quantity.ToString();
        currentRow["[p_unit_price]"] = invoiceData.GetPriceAsString(orderItem.Price);
        currentRow["[p_price_total]"] = invoiceData.GetPriceAsString(orderItem.TotalPrice);

        orderItemNumber++;
    }
    // remove template row
    templateRow.Remove();

    // fill order information summary fields
    orderInformationTable["[subtotal]"] = invoiceData.GetPriceAsString(invoiceData.Subtotal);
    orderInformationTable["[tax]"] = invoiceData.GetPriceAsString(invoiceData.Tax);
    orderInformationTable["[shipping]"] = invoiceData.GetPriceAsString(invoiceData.Shipping);
    orderInformationTable["[grand_total]"] = invoiceData.GetPriceAsString(invoiceData.GrandTotal);

    // fill the "Notes" table
    Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTable notesTable = tables[3];
    notesTable["[date]"] = System.DateTime.Now.ToShortDateString();
    notesTable["[time]"] = System.DateTime.Now.ToLongTimeString();
}

/// <summary>
/// Sets the company information.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="fieldName">Name of the field.</param>
/// <param name="company">The company.</param>
private static void SetCompanyInformation(
    Vintasoft.Imaging.Office.OpenXml.Editor.OpenXmlDocumentTable table, 
    string fieldName, 
    Company company)
{
    string fieldFormat = string.Format("[{0}_{1}]", fieldName, "{0}");
    table[string.Format(fieldFormat, "company")] = company.CompanyName;
    table[string.Format(fieldFormat, "name")] = company.Name;
    table[string.Format(fieldFormat, "address")] = company.Address;
    table[string.Format(fieldFormat, "phone")] = company.GetPhones();
    table[string.Format(fieldFormat, "city")] = company.City;
}

次のコードは、請求書ジェネレーターのテスト データを作成します。
/// <summary>
/// Returns the invoice test data.
/// </summary>
/// <returns>The invoice test data.</returns>
public static InvoiceData GetTestData(int orderItemsCount)
{
    Company vintasoftCompany = new Company();
    vintasoftCompany.CompanyName = "VintaSoft LLC";

    Company billingCompany = new Company();
    billingCompany.CompanyName = "Billing Global Company Inc.";
    billingCompany.Name = "Mr. Q";
    billingCompany.Address = "Address1";
    billingCompany.City = "City1";
    billingCompany.Phones.Add("9876543210");
    billingCompany.Phones.Add("7654321098 (fax)");

    Company shipingCompany = new Company();
    shipingCompany.CompanyName = "Shipping Global Company Inc.";
    shipingCompany.Name = "Mr. Z";
    shipingCompany.Address = "Address2";
    shipingCompany.City = "City2";
    shipingCompany.Phones.Add("1122334455");
    shipingCompany.Phones.Add("5544332211 (fax)");

    InvoiceData data = new InvoiceData();

    System.Random random = new System.Random();
    data.InvoiceNumber = string.Format("{0}-{1}", random.Next(100000, 999999), random.Next(0, 9));

    data.Company = vintasoftCompany;
    data.BillingAddress = billingCompany;
    data.ShippingAddress = shipingCompany;

    InvoiceItem[] availableProducts = new InvoiceItem[] {
        new InvoiceItem("VintaSoft Imaging .NET SDK, Site license for Desktop PCs", 659.95f),
        new InvoiceItem("VintaSoft Annotation .NET Plug-in, Site license for Desktop PCs", 449.95f),
        new InvoiceItem("VintaSoft Office .NET Plug-in, Site license for Desktop PCs", 569.95f),
        new InvoiceItem("VintaSoft PDF .NET Plug-in (Reader+Writer), Site license for Desktop PCs", 1499.95f),
        new InvoiceItem("VintaSoft PDF .NET Plug-in (Reader+Writer+VisualEditor), Site license for Desktop PCs", 2999.95f),
        new InvoiceItem("VintaSoft JBIG2 .NET Plug-in, Site license for Desktop PCs", 1139.95f),
        new InvoiceItem("VintaSoft JPEG2000 .NET Plug-in, Site license for Desktop PCs", 689.95f),
        new InvoiceItem("VintaSoft Document Cleaup .NET Plug-in, Site license for Desktop PCs", 569.95f),
        new InvoiceItem("VintaSoft OCR .NET Plug-in, Site license for Desktop PCs", 509.95f),
        new InvoiceItem("VintaSoft DICOM .NET Plug-in (Codec+MPR), Site license for Desktop PCs", 1199.95f),
        new InvoiceItem("VintaSoft Forms Processing .NET Plug-in, Site license for Desktop PCs", 509.95f),
        new InvoiceItem("VintaSoft Barcode .NET SDK (1D+2D Reader+Writer), Site license for Desktop PCs", 1379.95f),
        new InvoiceItem("VintaSoft Twain .NET SDK, Site license", 539.95f)
    };

    for (int i = 0; i < orderItemsCount; i++)
    {
        int quantity = 1 + random.Next(10);
        int index = random.Next(availableProducts.Length - 1);
        data.OrderItems.Add(new InvoiceItem(availableProducts[index], quantity));
    }

    return data;
}

/// <summary>
/// Represents an information about company.
/// </summary>
public class Company
{

    /// <summary>
    /// The company name.
    /// </summary>
    public string CompanyName;

    /// <summary>
    /// The person name.
    /// </summary>
    public string Name;

    /// <summary>
    /// The company location city.
    /// </summary>
    public string City;

    /// <summary>
    /// The company location adress.
    /// </summary>
    public string Address;

    /// <summary>
    /// The company phone numbers.
    /// </summary>
    public System.Collections.Generic.List<string> Phones = new System.Collections.Generic.List<string>();



    /// <summary>
    /// Returns the phone numbers.
    /// </summary>
    public string GetPhones()
    {
        if (Phones.Count == 1)
            return Phones[0];
        System.Text.StringBuilder result = new System.Text.StringBuilder();
        for (int i = 0; i < Phones.Count - 1; i++)
        {
            result.Append(Phones[i]);
            result.Append(", ");
        }
        result.Append(Phones[Phones.Count - 1]);
        return result.ToString();
    }

}

/// <summary>
/// Represents an invoice order item.
/// </summary>
public class InvoiceItem
{

    /// <summary>
    /// Initializes a new instance of the <see cref="InvoiceItem"/> class.
    /// </summary>
    /// <param name="product">The product name.</param>
    /// <param name="price">The product price.</param>
    public InvoiceItem(string product, float price)
    {
        Product = product;
        Quantity = 1;
        Price = price;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="InvoiceItem"/> class.
    /// </summary>
    /// <param name="source">The source <see cref="InvoiceItem"/>.</param>
    /// <param name="quantity">The product quantity.</param>
    public InvoiceItem(InvoiceItem source, float quantity)
    {
        Product = source.Product;
        Price = source.Price;
        Quantity = quantity;
    }



    /// <summary>
    /// The product name.
    /// </summary>
    public string Product;

    /// <summary>
    /// The product quantity.
    /// </summary>
    public float Quantity;

    /// <summary>
    /// The product price.
    /// </summary>
    public float Price;



    /// <summary>
    /// Gets the product total price.
    /// </summary>
    public float TotalPrice
    {
        get
        {
            return Price * Quantity;
        }
    }

}

/// <summary>
/// Represents an invoice data.
/// </summary>
public class InvoiceData
{

    /// <summary>
    /// The list of order items.
    /// </summary>
    public System.Collections.Generic.List<InvoiceItem> OrderItems = new System.Collections.Generic.List<InvoiceItem>();

    /// <summary>
    /// The invoice number.
    /// </summary>
    public string InvoiceNumber;

    /// <summary>
    /// The shipping method.
    /// </summary>
    public string ShippingMethod = "Email";

    /// <summary>
    /// The company billing address.
    /// </summary>
    public Company BillingAddress = new Company();

    /// <summary>
    /// The company shipping address.
    /// </summary>
    public Company ShippingAddress = new Company();

    /// <summary>
    /// The object that represents information about the company.
    /// </summary>
    public Company Company = new Company();

    /// <summary>
    /// The currency to be used in the invoice.
    /// </summary>
    public string Currency = "EUR";

    /// <summary>
    /// Gets or sets the tax value.
    /// </summary>
    public float Tax = 0;

    /// <summary>
    /// Gets or sets the shipping price.
    /// </summary>
    public float Shipping = 0;



    /// <summary>
    /// Gets the subtotal value.
    /// </summary>
    public float Subtotal
    {
        get
        {
            float value = 0;
            for (int i = 0; i < OrderItems.Count; i++)
                value += OrderItems[i].TotalPrice;
            return value;
        }
    }


    /// <summary>
    /// Gets the grandtotal value.
    /// </summary>
    public float GrandTotal
    {
        get
        {
            return Subtotal + Shipping + Tax;
        }
    }



    /// <summary>
    /// Returns the price as a string.
    /// </summary>
    /// <param name="price">The price.</param>
    /// <returns>The price in string representation.</returns>
    public string GetPriceAsString(float price)
    {
        return string.Format("{0} {1}", price.ToString("f2", System.Globalization.CultureInfo.InvariantCulture), Currency);
    }

    /// <summary>
    /// Creates the QR code image.
    /// </summary>
    /// <param name="size">The barcode size.</param>
    /// <returns>An instance o f<see cref="Vintasoft.Imaging.VintasoftImage"/> class that contains QR code image.</returns>
    public Vintasoft.Imaging.VintasoftImage GetBarcodeImage(int size)
    {
        Vintasoft.Barcode.BarcodeWriter writer = new Vintasoft.Barcode.BarcodeWriter();

        writer.Settings.Barcode = Vintasoft.Barcode.BarcodeType.QR;

        writer.Settings.Value = string.Format("INVOICE={0};TOTAL={1}", InvoiceNumber, GetPriceAsString(GrandTotal));

        writer.Settings.SetWidth(size);

        Vintasoft.Imaging.VintasoftImage result = 
            new Vintasoft.Imaging.VintasoftImage(writer.GetBarcodeAsBitmap(), true);
        result.Crop(new System.Drawing.Rectangle(0, 0, result.Width, result.Width));

        return result;
    }
}