电子发票是电商时代的产物,PDF发票是最常见的电子发票之一。在这篇文章中,我将给大家分享一个免费的动态生成PDF电子发票的C#方案,并在文章末尾附上Java解决方案。

典型的发票包含客户和供应商的名称和地址、发票编号、购买物品的描述、付款金额等信息。为了动态地生成发票,我使用MS Word创建了一个模板,在该模板中设计好想要呈现的大部分内容及文档样式,然后通过代码替换文本和插入新内容,最后保存为PDF文档。用代码操作Word文档的部分需要使用免费版的Spire.Doc for .NET7.1

创建模板
如图1所示,发票模板由两个表组成。表1用于显示买卖双方的信息和订单信息,表2用于陈列卖方向买方提供的产品或服务列表。我们需要做的是替换表1中以#开头的文本,并将客户的购物清单填充至第二个表格。

为了能自动计算总金额,需要在某些单元格添加公式域。例如,单元格E2包含公式“=C2*D2”,该公式将计算单元格B2中商品的总价。当买家购买超过一件商品时,我们需要在表格添加更多行并动态更新一些单元格的公式。

图1 发票模板

如何替换文本
Spire.Doc 有一个IBodyRegion.Replace(string given, string replace, bool caseSensitive, bool wholeWord)方法,可以用于替换文档中的指定字符串。例如,将“#orderNum”替换成“2516595027”,我们可以直接使用下面的代码,代码中doc是Document对象。

  1. doc.Replace("#orderNum", "", true, true);

如何更新表格二

现在,让我们看看如何向现有表格添加行,如何将数据填充至表格,以及如何动态地更新公式。为了使逻辑更清晰,我创建了三个方法,并制作了图2,来展示它们的具体含义以及它们之间的调用关系。

图2 自定义方法的含义及调用关系

下面,看一下这三个方法的代码片段:

1. AddRows()

此方法实际上复制了现有表格的第二行,将复制行依次添加到第二行的下面。新行继承了第二行的单元格格式、字体样式和公式。因此,我们需要依次更新新行中的公式,以及下面随行数增加而变化的公式。

  1. private static void AddRows(Table table, int rowNum)
  2. {
  3.  
  4. for (int i = ; i < rowNum; i++)
  5. {
  6. //将指定个数的第二行的复制行依次添加到第二行下面
  7. table.Rows.Insert( + i, table.Rows[].Clone());
  8.  
  9. //更新“金额”所对应单元格的公式
  10. foreach (var item in table.Rows[ + i].Cells[].Paragraphs[].ChildObjects)
  11. {
  12. if (item is Field)
  13. {
  14. Field field = item as Field;
  15. field.Code = string.Format("=C{0}*D{0}\\# \"0.00\"", + i);
  16. }
  17. break;
  18. }
  19.  
  20. }
  21.  
  22. //更新“折扣金额”对应的单元格的公式
  23. foreach (var item in table.Rows[ + rowNum].Cells[].Paragraphs[].ChildObjects)
  24. {
  25. if (item is Field)
  26. {
  27. Field field = item as Field;
  28. field.Code = string.Format("=E{0}*0.05\\# \"0.00\"", + rowNum);
  29. }
  30. break;
  31. }
  32.  
  33. //更新“总计”对应的单元格的公式
  34. foreach (var item in table.Rows[ + rowNum].Cells[].Paragraphs[].ChildObjects)
  35. {
  36. if (item is Field)
  37. {
  38. Field field = item as Field;
  39. field.Code = string.Format("=E{0}-E{1}\\# \"¥#,##0.00\"", + rowNum, + rowNum);
  40. }
  41. break;
  42. }
  43.  
  44. }

 2. FillTableWithData()

此方法仅用于将sting[][]类型数据从表格的第二行开始写入表格。

  1. private static void FillTableWithData(Table table, string[][] data)
  2. {
  3. for (int r = ; r < data.Length; r++)
  4. {
  5. for (int c = ; c < data[r].Length; c++)
  6. {
  7. //将数据从表格的第二行开始写入表格
  8. table.Rows[r + ].Cells[c].Paragraphs[].Text = data[r][c];
  9. }
  10. }
  11. }

3. WriteDataToDocument()

由于发票模板已经有一行(第二行)用于显示一项商品,因此我们需要判断是否需要添加更多行。如果客户只购买一项商品,模板文档就可以容纳商品信息并输出结果;否则,我们需要添加行来容纳更多的项目,并动态更新公式以获得正确的总金额。

  1. private static void WriteDataToDocument(Document doc, string[][] purhcaseData)
  2. {
  3. //获取Word模板中的第二个表格
  4. Table table = doc.Sections[].Tables[] as Table;
  5.  
  6. //若购买商品多于一项,则添加purhcaseData.Length - 1个行
  7. if (purhcaseData.Length > )
  8. {
  9. AddRows(table, purhcaseData.Length - );
  10. }
  11.  
  12. //将购买数据填充至表格
  13. FillTableWithData(table, purhcaseData);
  14. }

WriteDataToDocument()方法的参数之一是sting[][]对象,该对象存储了客户的购买信息,它的每个元素都是一个字符串数组,可以这样设置:

  1. string[] product = new string[] { "", "华为 P30 Pro (8G+128G)全网通", "", "" };

string[][]的长度则是商品的项数,如果长度大于1,则需要添加[长度 - 1]个新行。

生成发票

以下是Main函数中用于生成PDF发票的代码。

  1. using Spire.Doc;
  2. using Spire.Doc.Fields;
  3.  
  4. namespace CreatePdfInvoice
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. //加载Word模板文档
  11. Document doc = new Document();
  12. doc.LoadFromFile("Invoice-Template.docx");
  13.  
  14. //替换文档中以#开头的文本
  15. doc.Replace("#customerName", "小伟", true, true);
  16. doc.Replace("#contactNum", "", true, true);
  17. doc.Replace("#shippingAdd", "北京市海淀区幸福小区1幢2单元3号", true, true);
  18. doc.Replace("#orderDate", "2019-05-30", true, true);
  19. doc.Replace("#orderNum", "", true, true);
  20.  
  21. //定义客户购买数据
  22. string[][] purchaseData = {
  23.  
  24. new string[]{"","华为 P30 Pro (8G+128G)全网通","",""},
  25. new string[]{"","华为Watch GT运动版","",""},
  26. new string[]{"","华为无线耳机 FreeBuds 2Pro","",""},
  27. new string[]{"","华为 MateBook 14 (i5 8G 512G)","",""},
  28. };
  29.  
  30. //将购买数据写入模板文档的第二个表格
  31. WriteDataToDocument(doc, purchaseData);
  32.  
  33. //更新域
  34. doc.IsUpdateFields = true;
  35.  
  36. //保存为PDF格式文档
  37. doc.SaveToFile("Invoice.pdf", FileFormat.PDF);
  38. System.Diagnostics.Process.Start("Invoice.pdf");
  39. }
  40. }
  41. }

生成的结果文档如下:

图 3 多项目PDF发票

如果你输入购买数据只有一行,那么你将得到如图4所示的结果文档。

  1. string[][] purchaseData = {new string[]{"","华为 P30 Pro (8G+128G)全网通","",""},};

图4 单项目PDF发票

工程下载

C#工程(含DLL和模板文档)  https://pan.baidu.com/s/1jCI1J5J08hGReGIe0ZzOsA 提取码:zp6n
Java 工程(含Jar和模板文档) https://pan.baidu.com/s/1bthB3gnd0B0JQYzwZDWmVA 提取码:ciey

注:

免费版 Spire.Doc能加载和生成的Word文档不能超过500个段落或25个表格,将Word文档保存为PDF时仅支持前3页。绝大多数发票只有1页或2页,所以该方案适用于大多数情况。

————————————————
版权声明:本文为CSDN博主「ssw_jack」的原创文章。
原文链接:https://blog.csdn.net/ssw_jack/article/details/91379486

C#/Java 动态生成电子发票的更多相关文章

  1. 利用Java动态生成 PDF 文档

    利用Java动态生成 PDF 文档,则需要开源的API.首先我们先想象需求,在企业应用中,客户会提出一些复杂的需求,比如会针对具体的业务,构建比较典型的具备文档性质的内容,一般会导出PDF进行存档.那 ...

  2. Java 动态生成复杂 Word

    Java 动态生成复杂 Word 阅读目录 1. 制作 Word 模版,将你需要动态生成的字段用${}替换.2. 将 Word文档保存为 xml .3. 引入项目. 项目中需要用 java 程序生成d ...

  3. Java 动态生成 PDF 文件

    每片文章前来首小诗:   今日夕阳伴薄雾,印着雪墙笑开颜.我心仿佛出窗前,浮在半腰望西天.  --泥沙砖瓦浆木匠 需求: 项目里面有需要java动态生成 PDF 文件,提供下载.今天我找了下有关了,系 ...

  4. java动态生成HTML文件

    在eclipse中,用java动态生成html文件. //用于存储html字符串 StringBuilder stringHtml = new StringBuilder(); try{ //打开文件 ...

  5. java动态生成带下拉框的Excel导入模板

    在实际开发中,由于业务需要,常常需要进行Excel导入导出操作.以前做一些简单的导入时,先准备一个模板,再进行导入,单有十几. 二十几个导入模板时,往往要做十几.二十几个模板.而且,当在模板中需要有下 ...

  6. Java 动态生成 复杂 .doc文件

    阅读目录 1.word 里面调整好排版,包括你想生成的动态部分,还有一些不用生成的规则性的文字 2. 将 word 文档保存为 xml 3.用 Firstobject free XML edito 打 ...

  7. 【原】如何获取Java动态生成类?

    写作目的:Java大部分框架,如Spring,Hibernate等都会利用动态代理在程序运行的时候生成新的类, 有的时候为了学习,或者深入了解动态代理,想查看动态生成类的源代码究竟长怎么个样子, 通过 ...

  8. Java动态生成类以及动态添加属性

    有个技术实现需求:动态生成类,其中类中的属性来自参数对象中的全部属性以及来自参数对象properties文件. 那么技术实现支持:使用CGLib代理. 具体的实现步骤: 1.配置Maven文件: &l ...

  9. [转载]Java动态生成word文档(图文并茂)

    很多情况下,软件开发者需要从数据库读取数据,然后将数据动态填充到手工预先准备好的Word模板文档里,这对于大批量生成拥有相同格式排版的正式文件非常有用,这个功能应用PageOffice的基本动态填充功 ...

随机推荐

  1. Spark第一周

    Why Scala 在数据集不是很大的时候,开发人员可以使用python.R.MATLAB等语言在单机上处理数据集.但是在大数据时代,数据集少说都是TB.PB级别,此时便需要分布式地处理.相较于上述语 ...

  2. springboot+mongodb 按日期分组分页查询

    List<Integer> types = new ArrayList<>(); types.add("条件1"); types.add("条件2 ...

  3. VMWare虚拟机:三台虚拟机互通且连网

    虚拟机:三台虚拟机互通且连网 目录 一.虚拟机 相关软件 虚拟机安装 Linux系统安装 1) 使用三个Linux虚拟机 多台虚拟机互通且上网 1) 多台配置注意事项 2) 虚拟机软件的配置 3) W ...

  4. thymeleaf介绍

    作者:纯洁的微笑出处:http://www.ityouknow.com/  增加了一小部分内容 简单说, Thymeleaf 是一个跟 Velocity.FreeMarker 类似的模板引擎,它可以完 ...

  5. sealos2.0使用教程,最简单kubernetesHA方案

    kubernetes集群三步安装 概述 本文教你如何用一条命令构建k8s高可用集群且不依赖haproxy和keepalived,也无需ansible.通过内核ipvs对apiserver进行负载均衡, ...

  6. 0 MapReduce实现Reduce Side Join操作

    一.准备两张表以及对应的数据 (1)m_ys_lab_jointest_a(以下简称表A) 建表语句: create table if not exists m_ys_lab_jointest_a ( ...

  7. Java核心技术(卷一)读书笔记——第一章(概述)

    1.Java不提供多重继承,通过接口来实现.一个类只能继承一个父类,但是可以同时实现多个接口. 2.Java中的int类型的大小是固定的32位,以避免代码移植时候的不兼容问题.唯一的限制是int类型的 ...

  8. 【Android】Error:Execution failed for task ':app:lint'

    详细信息如下: Error:Execution failed for task ':app:lint'. > Lint found errors in the project; aborting ...

  9. 【iOS】this class is not key value coding-compliant for the key ...

    一般此问题 都是由 interface build 与代码中 IBOutlet 的连接所引起的. 可能是在代码中对 IBOutlet 的名称进行了修改,导致 interface build 中的连接实 ...

  10. 【iOS】NSLog 打印 BOOL 类型值

    这个问题以前没在意,刚偶然打印,发现有些问题,上网查了下,发现是这么搞的: NSLog(@"%@", isEqual?@"YES":@"NO" ...