模拟发票,发票PDF

from flask import Flask, request, jsonify, send_file

from flask_sqlalchemy import SQLAlchemy

from datetime import datetime

from io import BytesIO

from reportlab.lib.pagesizes import A4

from reportlab.pdfgen import canvas

from reportlab.lib.units import mm

app = Flask(name)

app.config'SQLALCHEMY_DATABASE_URI' = 'sqlite:///tickets.db'

app.config'SQLALCHEMY_TRACK_MODIFICATIONS' = False

db = SQLAlchemy(app)

数据模型

class Ticket(db.Model):

id = db.Column(db.Integer, primary_key=True)

event = db.Column(db.String(200), nullable=False)

buyer_name = db.Column(db.String(200), nullable=False)

buyer_tax_id = db.Column(db.String(100), nullable=True) # 纳税人识别号

quantity = db.Column(db.Integer, default=1)

unit_price = db.Column(db.Float, nullable=False)

total = db.Column(db.Float, nullable=False)

created_at = db.Column(db.DateTime, default=datetime.utcnow)

invoice_no = db.Column(db.String(100), nullable=True) # 发票号码(模拟)

db.create_all()

下单/购买票

@app.route('/buy', methods='POST')

def buy():

data = request.json

required = 'event', 'buyer_name', 'unit_price', 'quantity'

for r in required:

if r not in data:

return jsonify({'error': f'missing {r}'}), 400

quantity = int(data.get('quantity', 1))

unit_price = float(data.get('unit_price'))

total = round(quantity * unit_price, 2)

ticket = Ticket(

event=data'event',

buyer_name=data'buyer_name',

buyer_tax_id=data.get('buyer_tax_id'),

quantity=quantity,

unit_price=unit_price,

total=total

)

db.session.add(ticket)

db.session.commit()

return jsonify({'message': 'order created', 'ticket_id': ticket.id}), 201

查询订单

@app.route('/ticket/<int:ticket_id>', methods='GET')

def get_ticket(ticket_id):

t = Ticket.query.get_or_404(ticket_id)

return jsonify({

'id': t.id,

'event': t.event,

'buyer_name': t.buyer_name,

'buyer_tax_id': t.buyer_tax_id,

'quantity': t.quantity,

'unit_price': t.unit_price,

'total': t.total,

'created_at': t.created_at.isoformat(),

'invoice_no': t.invoice_no

})

生成发票(PDF)并返回

@app.route('/invoice/<int:ticket_id>', methods='GET')

def generate_invoice(ticket_id):

t = Ticket.query.get_or_404(ticket_id)

模拟分配发票号码(真实场景通过税控/第三方服务获取)

if not t.invoice_no:

t.invoice_no = f'INV-{datetime.utcnow().strftime("%Y%m%d%H%M%S")}-{t.id}'

db.session.commit()

buffer = BytesIO()

c = canvas.Canvas(buffer, pagesize=A4)

width, height = A4

绘制发票标题

c.setFont("Helvetica-Bold", 16)

c.drawCentredString(width / 2, height - 30 * mm, "增值税普通发票(示例)")

发票头部信息

c.setFont("Helvetica", 10)

left = 20 * mm

top = height - 40 * mm

line_h = 6 * mm

c.drawString(left, top, f"发票代码: 示例代码")

c.drawString(width - 90 * mm, top, f"发票号码: {t.invoice_no}")

top -= line_h

c.drawString(left, top, f"开票日期: {t.created_at.strftime('%Y-%m-%d %H:%M:%S')}")

c.drawString(width - 90 * mm, top, f"销售方: 示例售票方")

top -= line_h

c.drawString(left, top, f"购买方(抬头): {t.buyer_name}")

c.drawString(width - 90 * mm, top, f"纳税人识别号: {t.buyer_tax_id or '---'}")

表格表头

top -= 12 * mm

c.line(left, top + 4 * mm, width - left, top + 4 * mm)

c.setFont("Helvetica-Bold", 9)

c.drawString(left + 2 * mm, top, "货物或应税劳务、服务名称")

c.drawString(left + 95 * mm, top, "数量")

c.drawString(left + 115 * mm, top, "单价")

c.drawString(left + 145 * mm, top, "金额")

c.line(left, top - 3 * mm, width - left, top - 3 * mm)

表格内容(一个项目示例)

top -= 10 * mm

c.setFont("Helvetica", 9)

c.drawString(left + 2 * mm, top, t.event)

c.drawRightString(left + 120 * mm, top, str(t.quantity))

c.drawRightString(left + 150 * mm, top, f"{t.unit_price:.2f}")

c.drawRightString(width - left - 2 * mm, top, f"{t.total:.2f}")

合计

top -= 18 * mm

c.setFont("Helvetica-Bold", 10)

c.drawString(left, top, f"合计金额(小写): {t.total:.2f} 元")

top -= 6 * mm

c.drawString(left, top, f"合计金额(大写): {num_to_chinese_upper(t.total)}")

备注与签章位置(示例)

top -= 18 * mm

c.setFont("Helvetica", 9)

c.drawString(left, top, "备注: 本发票为系统示例发票,不作为税务正式凭证。")

c.drawString(width - 60 * mm, top, "开票人: 系统")

c.drawString(width - 30 * mm, top, "收款: ------")

c.showPage()

c.save()

buffer.seek(0)

return send_file(buffer, as_attachment=True, download_name=f"invoice_{t.id}.pdf", mimetype='application/pdf')

简单的数字转大写人民币(仅示例,覆盖到元,不含分)

CN_NUM = "零","壹","贰","叁","肆","伍","陆","柒","捌","玖"

CN_UNIT = "","拾","佰","仟"

CN_SECTION = "","万","亿","兆"

def num_to_chinese_upper(amount):

仅处理非负且小于兆级别的数,保留到元

amount = int(round(amount))

if amount == 0:

return "零元"

parts = \[\]

unit_pos = 0

while amount > 0:

section = amount % 10000

if section != 0:

section_str = section_to_chinese(section) + CN_SECTIONunit_pos

parts.insert(0, section_str)

amount //= 10000

unit_pos += 1

return "".join(parts) + "元"

def section_to_chinese(section):

str_ins = ""

unit_pos = 0

zero = True

while section > 0:

v = section % 10

if v == 0:

if not zero:

zero = True

str_ins = CN_NUM0 + str_ins

else:

zero = False

str_ins = CN_NUMv + CN_UNITunit_pos + str_ins

unit_pos += 1

section //= 10

return str_ins

if name == 'main':

app.run(debug=True)

使用说明(简短):

  • 启动:python app.py
  • 创建订单(POST /buy,JSON body 示例): {"event":"演唱会门票","buyer_name":"张三","buyer_tax_id":"123456789012345","quantity":2,"unit_price":150.0}
  • 查询订单:GET /ticket/
  • 下载发票:GET /invoice/(返回 PDF)

注意与扩展建议:

  • 真实"正规发票"必须通过税务局/税控设备接口(增值税发票服务)开具并取得法律效力,本示例仅生成演示用 PDF 模板。
  • 可扩展:增加用户认证、支付接口、发票项目多行、税额与税率计算、打印样式、发票电子数据格式(如 e-invoice XML)、日志与审计等。
相关推荐
GEO优化小助手19 分钟前
2026临沂GEO优化公司实测解析:3家本土机构适配性参考
大数据·人工智能·python
砚底藏山河36 分钟前
沪深A股:如何获取基金持股数据
java·python·数据分析·maven
goldenrolan1 小时前
学习型红外控制系统稳定性挂测工装专项总结
软件测试·python·stm32·嵌入式·红外
小小龙学IT1 小时前
Apache Airflow 2.x 深度指南:用 Python 编排一切的现代化工作流引擎
开发语言·python·apache
HappyAcmen1 小时前
7.faiss-cpu向量库安装
python·faiss
你是个什么橙2 小时前
Python入门学习2:Python 基础语法全解析——从代码结构到输入输出
开发语言·python·学习
小白学大数据2 小时前
Python + 大模型行业资讯自动化摘要流水线完整工程实现方案
开发语言·python·自动化
beethobe2 小时前
PythonQt 学习之旅(一):从零构建 C++ 与 Python 的桥梁
c++·python·学习
广州智造2 小时前
如何在HyperMesh运行Python脚本及查找Python API帮助
python·仿真·cae·hypermesh·optistruct
cooldog123pp2 小时前
cplex完全安装手册,适配matlab和python!
人工智能·python·matlab·cplex