''
'' This code is part of Document Solutions for PDF demos.
'' Copyright (c) MESCIUS inc. All rights reserved.
''
Imports System.IO
Imports System.Drawing
Imports System.Text
Imports System.Data
Imports System.Linq
Imports System.Collections.Generic
Imports GrapeCity.Documents.Pdf
Imports GrapeCity.Documents.Text
Imports GrapeCity.Documents.Html
Imports System.Globalization
Imports s2industries.ZUGFeRD
'' This sample creates a PDF invoice using the DsNWind sample database,
'' And attaches an XML file to it that Is created according to the ZUGFeRD 1.x standard rules.
''
'' ZUGFeRD Is a German e-invoicing standard based around PDF And XML file formats.
'' Its poised to change the way invoices are handled And can be used by any sort of business.
'' It will make invoice processing more efficient for senders And customers.
'' For details please see What Is ZUGFeRD?.
''
'' This sample uses the ZUGFeRD-csharp package
'' to create the ZUGFeRD-compatible XML that Is attached to the invoice.
''
'' For details on using DsHtml to render HTML to PDF please see HelloWorldHtml.
Public Class ZugferdInvoice
Public Sub CreatePDF(ByVal stream As Stream)
Using ds = New DataSet()
ds.ReadXml(Path.Combine("Resources", "data", "DsNWind.xml"))
Dim dtSuppliers = ds.Tables("Suppliers")
Dim dtOrders = ds.Tables("OrdersCustomersEmployees")
Dim dtOrdersDetails = ds.Tables("EmployeesProductsOrders")
Dim culture = CultureInfo.CreateSpecificCulture("en-US")
'' Collect order data:
Dim random = Util.NewRandom()
Dim fetchedIndex = random.Next(dtSuppliers.Select().Count())
Dim supplier =
dtSuppliers.Select().
Skip(fetchedIndex).Take(1).
Select(Function(it) New With {
.SupplierID = Convert.ToInt32(it("SupplierID")),
.CompanyName = it("CompanyName").ToString(),
.ContactName = it("ContactName").ToString(),
.ContactTitle = it("ContactTitle").ToString(),
.Address = it("Address").ToString(),
.City = it("City").ToString(),
.Region = it("Region").ToString(),
.PostalCode = it("PostalCode").ToString(),
.Country = it("Country").ToString(),
.Phone = it("Phone").ToString(),
.Fax = it("Fax").ToString(),
.HomePage = it("HomePage").ToString()
}).FirstOrDefault()
fetchedIndex = random.Next(dtOrders.Select().Count())
Dim order =
dtOrders.Select().
Skip(fetchedIndex).Take(1).
Select(Function(it) New With {
.OrderID = Convert.ToInt32(it("OrderID")),
.CompanyName = it("CompanyName").ToString(),
.LastName = it("LastName").ToString(),
.FirstName = it("FirstName").ToString(),
.OrderDate = ConvertToDateTime(it("OrderDate")),
.RequiredDate = ConvertToDateTime(it("RequiredDate")),
.ShippedDate = ConvertToDateTime(it("ShippedDate")),
.ShipVia = Convert.ToInt32(it("ShipVia")),
.Freight = Convert.ToDecimal(it("Freight")),
.ShipName = it("ShipName").ToString(),
.ShipAddress = it("ShipAddress").ToString(),
.ShipCity = it("ShipCity").ToString(),
.ShipRegion = it("ShipRegion").ToString(),
.ShipPostalCode = it("ShipPostalCode").ToString(),
.ShipCountry = it("ShipCountry").ToString()
}).FirstOrDefault()
Dim orderDetails = dtOrdersDetails.Select().
Select(Function(it) New With {
.OrderID = Convert.ToInt32(it("OrderID")),
.ItemDescription = it("ProductName").ToString(),
.Rate = Convert.ToDecimal(it("UnitPrice")),
.Quantity = Convert.ToDecimal(it("Quantity"))
}).Where(Function(it) it.OrderID = order.OrderID).
OrderBy(Function(it) it.ItemDescription).ToList()
Dim orderSubTotal As Decimal = 0
Dim index = 1
Dim detailsHtml = New StringBuilder()
orderDetails.ForEach(
Sub(it)
Dim total = Math.Round(it.Rate * it.Quantity, 2)
detailsHtml.AppendFormat(c_dataRowFmt, index,
it.ItemDescription,
it.Rate.ToString("C", culture),
it.Quantity,
total.ToString("C", culture))
orderSubTotal += total
index += 1
End Sub)
Dim orderTax As Decimal = Math.Round(orderSubTotal / 4, 2)
Dim allowanceTotalAmount As Decimal = orderSubTotal
Dim taxBasisTotalAmount As Decimal = orderSubTotal
Dim taxTotalAmount As Decimal = orderTax
Dim grandTotalAmount As Decimal = orderSubTotal
'' Build HTML to be converted to PDF:
Dim html = String.Format(
c_tableTpl, detailsHtml.ToString(),
supplier.CompanyName,
$"{supplier.Address}, {supplier.Region} {supplier.PostalCode}, {supplier.City} {supplier.Country}",
supplier.Phone,
supplier.HomePage,
$"{order.FirstName} {order.LastName} {order.CompanyName}",
$"{order.ShipAddress}, {order.ShipRegion} {order.ShipPostalCode}, {order.ShipCity} {order.ShipCountry}",
order.OrderDate.ToString("MM/dd/yyyy", culture),
order.RequiredDate.ToString("MM/dd/yyyy", culture),
orderSubTotal.ToString("C", culture),
orderTax.ToString("C", culture),
(orderSubTotal + orderTax).ToString("C", culture),
c_tableStyles)
'' Build ZUGFeRD compliant XML to attach to the PDF:
Dim zugferdDesc = InvoiceDescriptor.CreateInvoice(
order.OrderID.ToString(),
order.OrderDate,
CurrencyCodes.USD)
'' Fill the invoice buyer info
zugferdDesc.SetBuyer(
order.ShipName,
order.ShipPostalCode,
order.ShipCity,
order.ShipAddress,
GetCountryCode(order.ShipCountry),
order.CompanyName)
zugferdDesc.SetBuyerContact($"{order.FirstName} {order.LastName}")
'' Fill the invoice seller info:
zugferdDesc.SetSeller(
supplier.CompanyName,
supplier.PostalCode,
supplier.City,
supplier.Address,
GetCountryCode(supplier.Country),
supplier.CompanyName)
'' Delivery date & totals:
zugferdDesc.ActualDeliveryDate = order.RequiredDate
zugferdDesc.SetTotals(orderSubTotal, orderTax, allowanceTotalAmount, taxBasisTotalAmount, taxTotalAmount, grandTotalAmount)
'' Payment positions part:
orderDetails.ForEach(
Sub(it)
zugferdDesc.AddTradeLineItem(name:=it.ItemDescription,
billedQuantity:=it.Quantity,
netUnitPrice:=it.Rate,
unitCode:=QuantityCodes.C62)
End Sub)
'' Save the invoice info XML:
Using ms = New MemoryStream()
zugferdDesc.Save(ms, ZUGFeRDVersion.Version1, Profile.Basic)
ms.Seek(0, SeekOrigin.Begin)
Dim tmpPdf = Path.GetTempFileName()
'' Create an instance of GcHtmlBrowser that Is used to render HTML
Using browser = Util.NewHtmlBrowser(), htmlPage = browser.NewPage(html)
'' Set up HTML headers, margins etc (see HtmlSettings):
Dim pdfOptions = New PdfOptions() With
{
.Margins = New PdfMargins(0.2F, 1, 0.2F, 1),
.DisplayHeaderFooter = True,
.HeaderTemplate = "<div style='color:#1a5276; font-size:12px; width:1000px; margin-left:0.2in; margin-right:0.2in'>" +
"<span style='float:left;'>Invoice</span>" +
"<span style='float:right'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></span>" +
"</div>",
.FooterTemplate = "<div style='color: #1a5276; font-size:12em; width:1000px; margin-left:0.2in; margin-right:0.2in;'>" +
"<span>(c) MESCIUS inc. All rights reserved.</span>" +
"<span style='float:right'>Generated on <span class='date'></span></span></div>"
}
'' Render the source Web page to the temporary file:
htmlPage.SaveAsPdf(tmpPdf, pdfOptions)
End Using
'' Create the result PDF as a PDF/A-3 compliant document with the ZUGFeRD XML attachment:
Dim doc = New GcPdfDocument()
Dim tmpZugferd = Path.GetTempFileName()
Using fs = File.OpenRead(tmpPdf)
doc.Load(fs)
'' The generated document should contain FileID
Dim fid As FileID = doc.FileID
If fid Is Nothing Then
fid = New FileID()
doc.FileID = fid
End If
If fid.PermanentID Is Nothing Then
fid.PermanentID = Guid.NewGuid().ToByteArray()
End If
If fid.ChangingID Is Nothing Then
fid.ChangingID = fid.PermanentID
End If
Dim ef1 = EmbeddedFileStream.FromBytes(doc, ms.ToArray())
ef1.ModificationDate = Util.TimeNow()
ef1.MimeType = "text/xml"
'' According to the ZUGFeRD 1.x standard naming, the filename should be ZUGFeRD-invoice.xml:
Dim fspec = FileSpecification.FromEmbeddedStream("ZUGFeRD-invoice.xml", ef1)
fspec.Relationship = AFRelationship.Alternative
fspec.UnicodeFile.FileName = fspec.File.FileName
'' The embedded file should be associated with a document
doc.AssociatedFiles.Add(fspec)
'' The attachment dictionary key can be anything:
doc.EmbeddedFiles.Add("ZUGfERD-Attachment", fspec)
doc.ConformanceLevel = PdfAConformanceLevel.PdfA3a
doc.Metadata.PdfA = PdfAConformanceLevel.PdfA3b
doc.Metadata.CreatorTool = doc.DocumentInfo.Creator
doc.Metadata.Title = "DsPdf Document"
doc.Save(tmpZugferd)
End Using
'' Copy the created PDF from the temp file to target stream:
Using ts = File.OpenRead(tmpZugferd)
ts.CopyTo(stream)
End Using
'' Clean up:
File.Delete(tmpZugferd)
File.Delete(tmpPdf)
End Using
End Using
'' Done.
End Sub
'' Some records in our sample database lack some dates:
Private Shared Function ConvertToDateTime(ByVal value As Object) As DateTime
If (Convert.IsDBNull(value)) Then
Return DateTime.MinValue
Else
Return Convert.ToDateTime(value)
End If
End Function
'' Provide ZUGFeRD country codes:
Private Shared s_regions As Dictionary(Of String, RegionInfo) = Nothing
Private Shared Sub InitNames()
s_regions = New Dictionary(Of String, RegionInfo)()
For Each culture In CultureInfo.GetCultures(CultureTypes.SpecificCultures)
If Not s_regions.ContainsKey(culture.Name) Then
s_regions.Add(culture.Name, New RegionInfo(culture.Name))
End If
Next
End Sub
Private Shared Function GetCountryCode(ByVal name As String) As CountryCodes
If s_regions Is Nothing Then
InitNames()
End If
name = name.Trim()
'' 'UK' is not present in s_regions but is used by our sample database:
If name.Equals("UK", StringComparison.InvariantCultureIgnoreCase) Then
name = "United Kingdom"
End If
Dim region = s_regions.Values.FirstOrDefault(Function(it) it.EnglishName.Equals(name, StringComparison.InvariantCultureIgnoreCase) OrElse
it.NativeName.Equals(name, StringComparison.InvariantCultureIgnoreCase) OrElse
it.ThreeLetterISORegionName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
If region IsNot Nothing Then
Return New CountryCodes().FromString(region.Name)
Else
Return CountryCodes.Unknown
End If
End Function
'' HTML styles and templates used to render the invoice:
Const c_tableStyles = "
<style>
.clearfix:after {
display: table;
clear: both;
}
a {
color: RoyalBlue;
text-decoration: none;
}
body {
position: relative;
margin: 0 auto;
color: #555555;
background: #FFFFFF;
font-family: Arial, sans-serif;
font-size: 14px;
}
header {
padding: 10px 0;
margin-bottom: 20px;
min-height: 60px;
border-bottom: 1px solid #AAAAAA;
}
# company {
float: right;
text-align: right;
}
# details {
margin-bottom: 50px;
}
# client {
padding-left: 6px;
border-left: 6px solid RoyalBlue;
float: left;
}
# client .to {
color: #777777;
}
h2.name {
font-size: 16px;
font-weight: normal;
margin: 0;
}
# invoice {
float: right;
text-align: right;
}
# invoice h1 {
color: RoyalBlue;
font-size: 18px;
line-height: 1em;
font-weight: normal;
margin: 0 0 10px 0;
}
# invoice .date {
font-size: 14px;
color: #777777;
}
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
margin-bottom: 20px;
}
table th {
padding: 14px;
color: White !important;
background: #6585e7 !important;
text-align: center;
border-bottom: 1px solid #FFFFFF;
}
table td {
padding: 10px;
background: #EEEEEE;
text-align: center;
border-bottom: 1px solid #FFFFFF;
}
table th {
white-space: nowrap;
font-weight: normal;
}
table td {
text-align: right;
}
table td h3{
color: RoyalBlue;
font-size: 14px;
font-weight: normal;
margin: 0 0 0.2em 0;
}
table .no {
color: #FFFFFF;
font-size: 14px;
background: RoyalBlue;
}
table .desc {
text-align: left;
}
table .unit {
background: #DDDDDD;
}
table .qty {
}
table .total {
background: RoyalBlue;
color: #FFFFFF;
}
table td.unit,
table td.qty,
table td.total {
font-size: 14px;
}
table tbody tr:last-child td {
border: none;
}
table tfoot td {
padding: 10px 20px;
background: #FFFFFF;
border-bottom: none;
font-size: 16px;
white-space: nowrap;
border-top: 1px solid #AAAAAA;
}
table tfoot tr:first-child td {
border-top: none;
}
table tfoot tr:last-child td {
color: RoyalBlue;
font-size: 16px;
border-top: 1px solid RoyalBlue;
}
table tfoot tr td:first-child {
border: none;
}
# thanks{
font-size: 16px;
margin-bottom: 50px;
}
# notes{
padding-left: 6px;
border-left: 6px solid RoyalBlue;
}
# notes .note {
font-size: 16px;
}
footer {
color: #777777;
width: 100%;
height: 30px;
position: absolute;
bottom: 0;
border-top: 1px solid #AAAAAA;
padding: 8px 0;
text-align: center;
}
</style>
"
Const c_tableTpl = "
<!DOCTYPE html>
<html lang='en'>
<head><meta charset='utf-8'>{12}</head>
<body>
<header class='clearfix'>
<div id = 'company'>
<h2 class='name'>{1}</h2>
<div>{2}</div>
<div>{3}</div>
<div><a href = '{4}'> {4}</a></div>
</div>
</header>
<main>
<div id='details' class='clearfix'>
<div id='client'>
<div class='to'>INVOICE TO:</div>
<h2 class='name'>{5}</h2>
<div class='address'>{6}</div>
</div>
<div id='invoice'>
<h1>INVOICE</h1>
<div class='date'>Date of Invoice: {7}</div>
<div class='date'>Due Date: {8}</div>
</div>
</div>
<table border='0' cellspacing='0' cellpadding='0'>
<thead>
<tr>
<th class='no'>#</th>
<th class='desc'>DESCRIPTION</th>
<th class='unit'>UNIT PRICE</th>
<th class='qty'>QUANTITY</th>
<th class='total'>TOTAL</th>
</tr>
</thead>
<tbody>
{0}
</tbody>
<tfoot>
<tr>
<td colspan='2'></td>
<td colspan='2'>SUBTOTAL</td>
<td>{9}</td>
</tr>
<tr>
<td colspan='2'></td>
<td colspan='2'>TAX 25%</td>
<td>{10}</td>
</tr>
<tr>
<td colspan='2'></td>
<td colspan='2'>GRAND TOTAL</td>
<td>{11}</td>
</tr>
</tfoot>
</table>
<div id='thanks'>Thank you!</div>
<div id='notes'>
<div>NOTES:</div>
<div class='note'></div>
</div>
</main>
</body>
</html>
"
Const c_dataRowFmt = "
<tr>
<td class='no'>{0}</td>
<td class='desc'><h3>{1}</h3></td>
<td class='unit'>{2}</td>
<td class='qty'>{3}</td>
<td class='total'>{4}</td>
</tr>
"
End Class