模拟发票,发票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_SECTION[unit_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_NUM[0] + str_ins

else:

zero = False

str_ins = CN_NUM[v] + CN_UNIT[unit_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)、日志与审计等。
相关推荐
KANGBboy4 小时前
anaconda 相关
python·conda
财经资讯数据_灵砚智能4 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年4月11日
大数据·人工智能·python·信息可视化·自然语言处理·ai编程
XiaoQiao6669994 小时前
pytnon中正则表达式小题详解
python·正则表达式
好家伙VCC4 小时前
**基于Colab的高效Python深度学习开发流程:从环境配置到模型部署全流程实战**在当前人工智
java·开发语言·python·深度学习
Lucas_coding4 小时前
【python_并发】requests vs aiohttp vs httpx:HTTP客户端深度对比与实战
python
gCode Teacher 格码致知4 小时前
Python基础教学:正则表达式中的忽略大小写以及符号“-“的问题-由Deepseek产生
python·正则表达式
斯班奇的好朋友阿法法4 小时前
Django 项目打包部署完整指南(适配你的项目,零报错)
python·django·sqlite
林开落L4 小时前
【项目实战】博客系统完整测试报告(含自动化+性能测试)
python·功能测试·jmeter·自动化·postman·性能测试·xmind
JustNow_Man4 小时前
【opencode】使用方法
linux·服务器·网络·人工智能·python
abigale034 小时前
.py 与 .ipynb 的核心差异 + Jupyter 内核缓存坑全解析
python·jupyter