ZugferdInvoice.cs
  1. //
  2. // This code is part of Document Solutions for PDF demos.
  3. // Copyright (c) MESCIUS inc. All rights reserved.
  4. //
  5. using System;
  6. using System.IO;
  7. using System.Drawing;
  8. using System.Text;
  9. using System.Data;
  10. using System.Linq;
  11. using System.Collections.Generic;
  12. using GrapeCity.Documents.Pdf;
  13. using GrapeCity.Documents.Text;
  14. using GrapeCity.Documents.Html;
  15. using System.Globalization;
  16. using s2industries.ZUGFeRD;
  17.  
  18. namespace DsPdfWeb.Demos
  19. {
  20. // This sample creates a PDF invoice using the DsNWind sample database,
  21. // and attaches an XML file to it that is created according to the ZUGFeRD 1.x standard rules.
  22. //
  23. // ZUGFeRD is a German e-invoicing standard based around PDF and XML file formats.
  24. // Its poised to change the way invoices are handled and can be used by any sort of business.
  25. // It will make invoice processing more efficient for senders and customers.
  26. // For details please see What is ZUGFeRD?.
  27. //
  28. // This sample uses the ZUGFeRD-csharp package
  29. // to create the ZUGFeRD-compatible XML that is attached to the invoice.
  30. //
  31. // For details on using DsHtml to render HTML to PDF please see HelloWorldHtml.
  32. public class ZugferdInvoice
  33. {
  34. public void CreatePDF(Stream stream)
  35. {
  36. using var ds = new DataSet();
  37.  
  38. ds.ReadXml(Path.Combine("Resources", "data", "DsNWind.xml"));
  39.  
  40. var dtSuppliers = ds.Tables["Suppliers"];
  41. var dtOrders = ds.Tables["OrdersCustomersEmployees"];
  42. var dtOrdersDetails = ds.Tables["EmployeesProductsOrders"];
  43. var culture = CultureInfo.CreateSpecificCulture("en-US");
  44.  
  45. // Collect order data:
  46. var random = Common.Util.NewRandom();
  47.  
  48. var fetchedIndex = random.Next(dtSuppliers.Select().Count());
  49. var supplier =
  50. dtSuppliers.Select()
  51. .Skip(fetchedIndex).Take(1)
  52. .Select(it => new
  53. {
  54. SupplierID = Convert.ToInt32(it["SupplierID"]),
  55. CompanyName = it["CompanyName"].ToString(),
  56. ContactName = it["ContactName"].ToString(),
  57. ContactTitle = it["ContactTitle"].ToString(),
  58. Address = it["Address"].ToString(),
  59. City = it["City"].ToString(),
  60. Region = it["Region"].ToString(),
  61. PostalCode = it["PostalCode"].ToString(),
  62. Country = it["Country"].ToString(),
  63. Phone = it["Phone"].ToString(),
  64. Fax = it["Fax"].ToString(),
  65. HomePage = it["HomePage"].ToString()
  66. }).FirstOrDefault();
  67.  
  68. fetchedIndex = random.Next(dtOrders.Select().Count());
  69. var order = dtOrders.Select()
  70. .Skip(fetchedIndex).Take(1)
  71. .Select(it => new
  72. {
  73. OrderID = Convert.ToInt32(it["OrderID"]),
  74. CompanyName = it["CompanyName"].ToString(),
  75. LastName = it["LastName"].ToString(),
  76. FirstName = it["FirstName"].ToString(),
  77. OrderDate = ConvertToDateTime(it["OrderDate"]),
  78. RequiredDate = ConvertToDateTime(it["RequiredDate"]),
  79. ShippedDate = ConvertToDateTime(it["ShippedDate"]),
  80. ShipVia = Convert.ToInt32(it["ShipVia"]),
  81. Freight = Convert.ToDecimal(it["Freight"]),
  82. ShipName = it["ShipName"].ToString(),
  83. ShipAddress = it["ShipAddress"].ToString(),
  84. ShipCity = it["ShipCity"].ToString(),
  85. ShipRegion = it["ShipRegion"].ToString(),
  86. ShipPostalCode = it["ShipPostalCode"].ToString(),
  87. ShipCountry = it["ShipCountry"].ToString(),
  88. }).FirstOrDefault();
  89.  
  90. var orderDetails = dtOrdersDetails.Select()
  91. .Select(it => new
  92. {
  93. OrderID = Convert.ToInt32(it["OrderID"]),
  94. ItemDescription = it["ProductName"].ToString(),
  95. Rate = Convert.ToDecimal(it["UnitPrice"]),
  96. Quantity = Convert.ToDecimal(it["Quantity"])
  97. })
  98. .Where(it => it.OrderID == order.OrderID)
  99. .OrderBy(it => it.ItemDescription).ToList();
  100.  
  101. decimal orderSubTotal = 0;
  102. var index = 1;
  103. var detailsHtml = new StringBuilder();
  104. orderDetails.ForEach(it =>
  105. {
  106. var total = Math.Round(it.Rate * it.Quantity, 2);
  107. detailsHtml.AppendFormat(c_dataRowFmt, index,
  108. it.ItemDescription,
  109. it.Rate.ToString("C", culture),
  110. it.Quantity,
  111. total.ToString("C", culture));
  112. orderSubTotal += total;
  113. index++;
  114. });
  115. decimal orderTax = Math.Round(orderSubTotal / 4, 2);
  116. decimal allowanceTotalAmount = orderSubTotal;
  117. decimal taxBasisTotalAmount = orderSubTotal;
  118. decimal taxTotalAmount = orderTax;
  119. decimal grandTotalAmount = orderSubTotal;
  120.  
  121. // Build HTML to be converted to PDF:
  122. var html = string.Format(c_tableTpl, detailsHtml.ToString(),
  123. supplier.CompanyName,
  124. $"{supplier.Address}, {supplier.Region} {supplier.PostalCode}, {supplier.City} {supplier.Country}",
  125. supplier.Phone,
  126. supplier.HomePage,
  127. $"{order.FirstName} {order.LastName} {order.CompanyName}",
  128. $"{order.ShipAddress}, {order.ShipRegion} {order.ShipPostalCode}, {order.ShipCity} {order.ShipCountry}",
  129. order.OrderDate.ToString("MM/dd/yyyy", culture),
  130. order.RequiredDate.ToString("MM/dd/yyyy", culture),
  131. orderSubTotal.ToString("C", culture),
  132. orderTax.ToString("C", culture),
  133. (orderSubTotal + orderTax).ToString("C", culture),
  134. c_tableStyles);
  135.  
  136. // Build ZUGFeRD compliant XML to attach to the PDF:
  137. var zugferdDesc = InvoiceDescriptor.CreateInvoice(
  138. order.OrderID.ToString(),
  139. order.OrderDate,
  140. CurrencyCodes.USD);
  141.  
  142. // Fill the invoice buyer info:
  143. zugferdDesc.SetBuyer(
  144. order.ShipName,
  145. order.ShipPostalCode,
  146. order.ShipCity,
  147. order.ShipAddress,
  148. GetCountryCode(order.ShipCountry),
  149. order.CompanyName);
  150. zugferdDesc.SetBuyerContact($"{order.FirstName} {order.LastName}");
  151. // Fill the invoice seller info:
  152. zugferdDesc.SetSeller(
  153. supplier.CompanyName,
  154. supplier.PostalCode,
  155. supplier.City,
  156. supplier.Address,
  157. GetCountryCode(supplier.Country),
  158. supplier.CompanyName);
  159. // Delivery date & totals:
  160. zugferdDesc.ActualDeliveryDate = order.RequiredDate;
  161. zugferdDesc.SetTotals(orderSubTotal, orderTax, allowanceTotalAmount, taxBasisTotalAmount, taxTotalAmount, grandTotalAmount);
  162.  
  163. // Payment positions part:
  164. orderDetails.ForEach(it =>
  165. {
  166. zugferdDesc.AddTradeLineItem(
  167. name: it.ItemDescription,
  168. billedQuantity: it.Quantity,
  169. netUnitPrice: it.Rate,
  170. unitCode: QuantityCodes.C62);
  171. });
  172.  
  173. // Save the invoice info XML:
  174. using var ms = new MemoryStream();
  175.  
  176. zugferdDesc.Save(ms, ZUGFeRDVersion.Version1, Profile.Basic);
  177. ms.Seek(0, SeekOrigin.Begin);
  178.  
  179. var tmpPdf = Path.GetTempFileName();
  180. // Create an instance of GcHtmlBrowser that is used to render HTML:
  181. using var browser = Common.Util.NewHtmlBrowser();
  182. using var htmlPage = browser.NewPage(html);
  183.  
  184. // Set up HTML headers, margins etc (see HtmlSettings):
  185. var pdfOptions = new PdfOptions()
  186. {
  187. Margins = new PdfMargins(0.2f, 1, 0.2f, 1),
  188. DisplayHeaderFooter = true,
  189. HeaderTemplate = "<div style='color:#1a5276; font-size:12px; width:1000px; margin-left:0.2in; margin-right:0.2in'>" +
  190. "<span style='float:left;'>Invoice</span>" +
  191. "<span style='float:right'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></span>" +
  192. "</div>",
  193. FooterTemplate = "<div style='color: #1a5276; font-size:12em; width:1000px; margin-left:0.2in; margin-right:0.2in;'>" +
  194. "<span>(c) MESCIUS inc. All rights reserved.</span>" +
  195. "<span style='float:right'>Generated on <span class='date'></span></span></div>"
  196. };
  197. // Render the source Web page to the temporary file:
  198. htmlPage.SaveAsPdf(tmpPdf, pdfOptions);
  199.  
  200. // Create the result PDF as a PDF/A-3 compliant document with the ZUGFeRD XML attachment:
  201. var doc = new GcPdfDocument();
  202. var tmpZugferd = Path.GetTempFileName();
  203. using (var fs = File.OpenRead(tmpPdf))
  204. {
  205. doc.Load(fs);
  206. // The generated document should contain FileID:
  207. FileID fid = doc.FileID;
  208. if (fid == null)
  209. {
  210. fid = new FileID();
  211. doc.FileID = fid;
  212. }
  213. if (fid.PermanentID == null)
  214. fid.PermanentID = Guid.NewGuid().ToByteArray();
  215. if (fid.ChangingID == null)
  216. fid.ChangingID = fid.PermanentID;
  217.  
  218. var ef1 = EmbeddedFileStream.FromBytes(doc, ms.ToArray());
  219. ef1.ModificationDate = Common.Util.TimeNow();
  220. ef1.MimeType = "text/xml";
  221. // According to the ZUGFeRD 1.x standard naming, the filename should be ZUGFeRD-invoice.xml:
  222. var fspec = FileSpecification.FromEmbeddedStream("ZUGFeRD-invoice.xml", ef1);
  223. fspec.Relationship = AFRelationship.Alternative;
  224. fspec.UnicodeFile.FileName = fspec.File.FileName;
  225. // The embedded file should be associated with a document:
  226. doc.AssociatedFiles.Add(fspec);
  227. // The attachment dictionary key can be anything:
  228. doc.EmbeddedFiles.Add("ZUGfERD-Attachment", fspec);
  229. doc.ConformanceLevel = PdfAConformanceLevel.PdfA3b;
  230. doc.Metadata.PdfA = PdfAConformanceLevel.PdfA3b;
  231. doc.Metadata.CreatorTool = doc.DocumentInfo.Creator;
  232. doc.Metadata.Title = "DsPdf Document";
  233. doc.Save(tmpZugferd);
  234. }
  235.  
  236. // Copy the created PDF from the temp file to target stream:
  237. using (var ts = File.OpenRead(tmpZugferd))
  238. ts.CopyTo(stream);
  239.  
  240. // Clean up:
  241. File.Delete(tmpZugferd);
  242. File.Delete(tmpPdf);
  243. // Done.
  244. }
  245.  
  246. // Some records in our sample database lack some dates:
  247. private static DateTime ConvertToDateTime(object value)
  248. {
  249. if (Convert.IsDBNull(value))
  250. return DateTime.MinValue;
  251. else
  252. return Convert.ToDateTime(value);
  253. }
  254.  
  255. // Provide ZUGFeRD country codes:
  256. private static Dictionary<string, RegionInfo> s_regions = null;
  257.  
  258. private static void InitNames()
  259. {
  260. s_regions = new Dictionary<string, RegionInfo>();
  261. foreach (var culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
  262. {
  263. if (!s_regions.ContainsKey(culture.Name))
  264. s_regions.Add(culture.Name, new RegionInfo(culture.Name));
  265. }
  266. }
  267.  
  268. private static CountryCodes GetCountryCode(string name)
  269. {
  270. if (s_regions == null)
  271. InitNames();
  272.  
  273. name = name.Trim();
  274.  
  275. // 'UK' is not present in s_regions but is used by our sample database:
  276. if (name.Equals("UK", StringComparison.InvariantCultureIgnoreCase))
  277. name = "United Kingdom";
  278.  
  279. var region = s_regions.Values.FirstOrDefault(it =>
  280. it.EnglishName.Equals(name, StringComparison.InvariantCultureIgnoreCase) ||
  281. it.NativeName.Equals(name, StringComparison.InvariantCultureIgnoreCase) ||
  282. it.ThreeLetterISORegionName.Equals(name, StringComparison.InvariantCultureIgnoreCase));
  283. if (region != null)
  284. return new CountryCodes().FromString(region.Name);
  285. else
  286. return CountryCodes.Unknown;
  287. }
  288.  
  289. // HTML styles and templates used to render the invoice:
  290. const string c_tableStyles = @"
  291. <style>
  292. .clearfix:after {
  293. display: table;
  294. clear: both;
  295. }
  296. a {
  297. color: RoyalBlue;
  298. text-decoration: none;
  299. }
  300. body {
  301. position: relative;
  302. margin: 0 auto;
  303. color: #555555;
  304. background: #FFFFFF;
  305. font-family: Arial, sans-serif;
  306. font-size: 14px;
  307. }
  308. header {
  309. padding: 10px 0;
  310. margin-bottom: 20px;
  311. min-height: 60px;
  312. border-bottom: 1px solid #AAAAAA;
  313. }
  314. # company {
  315. float: right;
  316. text-align: right;
  317. }
  318. # details {
  319. margin-bottom: 50px;
  320. }
  321. # client {
  322. padding-left: 6px;
  323. border-left: 6px solid RoyalBlue;
  324. float: left;
  325. }
  326. # client .to {
  327. color: #777777;
  328. }
  329. h2.name {
  330. font-size: 16px;
  331. font-weight: normal;
  332. margin: 0;
  333. }
  334. # invoice {
  335. float: right;
  336. text-align: right;
  337. }
  338. # invoice h1 {
  339. color: RoyalBlue;
  340. font-size: 18px;
  341. line-height: 1em;
  342. font-weight: normal;
  343. margin: 0 0 10px 0;
  344. }
  345. # invoice .date {
  346. font-size: 14px;
  347. color: #777777;
  348. }
  349. table {
  350. width: 100%;
  351. border-collapse: collapse;
  352. border-spacing: 0;
  353. margin-bottom: 20px;
  354. }
  355. table th {
  356. padding: 14px;
  357. color: White !important;
  358. background: #6585e7 !important;
  359. text-align: center;
  360. border-bottom: 1px solid #FFFFFF;
  361. }
  362. table td {
  363. padding: 10px;
  364. background: #EEEEEE;
  365. text-align: center;
  366. border-bottom: 1px solid #FFFFFF;
  367. }
  368. table th {
  369. white-space: nowrap;
  370. font-weight: normal;
  371. }
  372. table td {
  373. text-align: right;
  374. }
  375. table td h3{
  376. color: RoyalBlue;
  377. font-size: 14px;
  378. font-weight: normal;
  379. margin: 0 0 0.2em 0;
  380. }
  381. table .no {
  382. color: #FFFFFF;
  383. font-size: 14px;
  384. background: RoyalBlue;
  385. }
  386. table .desc {
  387. text-align: left;
  388. }
  389. table .unit {
  390. background: #DDDDDD;
  391. }
  392. table .qty {
  393. }
  394. table .total {
  395. background: RoyalBlue;
  396. color: #FFFFFF;
  397. }
  398. table td.unit,
  399. table td.qty,
  400. table td.total {
  401. font-size: 14px;
  402. }
  403. table tbody tr:last-child td {
  404. border: none;
  405. }
  406. table tfoot td {
  407. padding: 10px 20px;
  408. background: #FFFFFF;
  409. border-bottom: none;
  410. font-size: 16px;
  411. white-space: nowrap;
  412. border-top: 1px solid #AAAAAA;
  413. }
  414. table tfoot tr:first-child td {
  415. border-top: none;
  416. }
  417. table tfoot tr:last-child td {
  418. color: RoyalBlue;
  419. font-size: 16px;
  420. border-top: 1px solid RoyalBlue;
  421. }
  422. table tfoot tr td:first-child {
  423. border: none;
  424. }
  425. # thanks{
  426. font-size: 16px;
  427. margin-bottom: 50px;
  428. }
  429. # notes{
  430. padding-left: 6px;
  431. border-left: 6px solid RoyalBlue;
  432. }
  433. # notes .note {
  434. font-size: 16px;
  435. }
  436. footer {
  437. color: #777777;
  438. width: 100%;
  439. height: 30px;
  440. position: absolute;
  441. bottom: 0;
  442. border-top: 1px solid #AAAAAA;
  443. padding: 8px 0;
  444. text-align: center;
  445. }
  446. </style>
  447. ";
  448. const string c_tableTpl = @"
  449. <!DOCTYPE html>
  450. <html lang='en'>
  451. <head><meta charset='utf-8'>{12}</head>
  452. <body>
  453. <header class='clearfix'>
  454. <div id = 'company'>
  455. <h2 class='name'>{1}</h2>
  456. <div>{2}</div>
  457. <div>{3}</div>
  458. <div><a href = '{4}'> {4}</a></div>
  459. </div>
  460. </header>
  461. <main>
  462. <div id='details' class='clearfix'>
  463. <div id='client'>
  464. <div class='to'>INVOICE TO:</div>
  465. <h2 class='name'>{5}</h2>
  466. <div class='address'>{6}</div>
  467. </div>
  468. <div id='invoice'>
  469. <h1>INVOICE</h1>
  470. <div class='date'>Date of Invoice: {7}</div>
  471. <div class='date'>Due Date: {8}</div>
  472. </div>
  473. </div>
  474. <table border='0' cellspacing='0' cellpadding='0'>
  475. <thead>
  476. <tr>
  477. <th class='no'>#</th>
  478. <th class='desc'>DESCRIPTION</th>
  479. <th class='unit'>UNIT PRICE</th>
  480. <th class='qty'>QUANTITY</th>
  481. <th class='total'>TOTAL</th>
  482. </tr>
  483. </thead>
  484. <tbody>
  485. {0}
  486. </tbody>
  487. <tfoot>
  488. <tr>
  489. <td colspan='2'></td>
  490. <td colspan='2'>SUBTOTAL</td>
  491. <td>{9}</td>
  492. </tr>
  493. <tr>
  494. <td colspan='2'></td>
  495. <td colspan='2'>TAX 25%</td>
  496. <td>{10}</td>
  497. </tr>
  498. <tr>
  499. <td colspan='2'></td>
  500. <td colspan='2'>GRAND TOTAL</td>
  501. <td>{11}</td>
  502. </tr>
  503. </tfoot>
  504. </table>
  505. <div id='thanks'>Thank you!</div>
  506. <div id='notes'>
  507. <div>NOTES:</div>
  508. <div class='note'></div>
  509. </div>
  510. </main>
  511. </body>
  512. </html>
  513. ";
  514. const string c_dataRowFmt = @"
  515. <tr>
  516. <td class='no'>{0}</td>
  517. <td class='desc'><h3>{1}</h3></td>
  518. <td class='unit'>{2}</td>
  519. <td class='qty'>{3}</td>
  520. <td class='total'>{4}</td>
  521. </tr>
  522. ";
  523. }
  524. }
  525.