"""单据编号生成器(业务 YW / 发货 SH / 回款 HK 共用)。 规则:``{前缀}{YYYYMMDD}-{当日4位序号}``,序号按自然日重置。 并发安全:``SELECT MAX(no) ... FOR UPDATE`` 锁住当日序号区间 —— 后到的 事务会等待先到者提交后再读到新 MAX,序号单调不重;残余竞态(锁等待超时 等极端场景)由服务层捕唯一键冲突(1062)重试一次兜底。 """ from datetime import datetime from sqlalchemy import select from sqlalchemy.orm import Session def today_prefix(prefix: str) -> str: """当日编号前缀(如 YW20261013-),LIKE 匹配与拼号共用同一来源防漂移。""" return f"{prefix}{datetime.now().strftime('%Y%m%d')}-" def next_no( session: Session, *, model: type, column_name: str, prefix: str, seq_width: int = 4, ) -> str: """生成下一个编号。 :param model: 编号所在的 ORM 模型(businesses/shipments/receipts) :param column_name: 编号列名(business_no/shipment_no/receipt_batch_no) :param prefix: 单据前缀(NO_PREFIX_* 常量) :param seq_width: 序号位数(默认 4);超出宽度的序号按实际位数输出, VARCHAR(32) 余量充足、唯一性不受影响 """ like_prefix = today_prefix(prefix) column = getattr(model, column_name) max_no = session.execute( select(column) .where(column.like(f"{like_prefix}%")) .order_by(column.desc()) .limit(1) .with_for_update() ).scalar() seq = int(max_no.rsplit("-", 1)[1]) + 1 if max_no else 1 return f"{like_prefix}{seq:0{seq_width}d}"