模拟发票,发票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)、日志与审计等。
相关推荐
@我漫长的孤独流浪2 小时前
Python精选480题带解析
python
cyforkk2 小时前
Spring AOP 核心揭秘:ProceedingJoinPoint 与反射机制详解
java·python·spring
2301_810160952 小时前
使用Flask快速搭建轻量级Web应用
jvm·数据库·python
JaydenAI2 小时前
[LangChain智能体本质论]中间件装饰器是如何将函数转换成AgentMiddleware的?
python·langchain·ai编程
2401_891655812 小时前
ZLibrary反爬机制概述
数据库·python
2201_761080192 小时前
Python上下文管理器(with语句)的原理与实践
jvm·数据库·python
研究点啥好呢2 小时前
3月21日GitHub热门项目推荐|攻守兼备,方得圆满
java·c++·python·开源·github
Storynone3 小时前
【Day29】LeetCode:62. 不同路径,63. 不同路径 II,343. 整数拆分,96. 不同的二叉搜索树
python·算法·leetcode
chushiyunen3 小时前
python语法-继承、方法命名、单例等
开发语言·python