2026-10-13 00:01:48 -04:00
|
|
|
|
"""单据编号生成器(业务 YW / 发货 SH / 回款 HK 共用)。
|
|
|
|
|
|
|
|
|
|
|
|
规则:``{前缀}{YYYYMMDD}-{当日4位序号}``,序号按自然日重置。
|
|
|
|
|
|
并发安全:``SELECT MAX(no) ... FOR UPDATE`` 锁住当日序号区间 —— 后到的
|
|
|
|
|
|
事务会等待先到者提交后再读到新 MAX,序号单调不重;残余竞态(锁等待超时
|
|
|
|
|
|
等极端场景)由服务层捕唯一键冲突(1062)重试一次兜底。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
2026-10-14 02:09:30 -04:00
|
|
|
|
from sqlalchemy import func, select
|
2026-10-13 00:01:48 -04:00
|
|
|
|
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)
|
2026-10-14 02:09:30 -04:00
|
|
|
|
# 排序按「长度优先、同级字典序」:同前缀下序号位数增多(9999→10000)后
|
|
|
|
|
|
# 纯字典序会反转("-10000" < "-9999",'1'<'9'),seq 将恒重算出同一号码、
|
|
|
|
|
|
# 每笔建单都撞唯一键 —— 长度大即数值大,同级同长时字典序与数值序一致
|
2026-10-13 00:01:48 -04:00
|
|
|
|
max_no = session.execute(
|
|
|
|
|
|
select(column)
|
|
|
|
|
|
.where(column.like(f"{like_prefix}%"))
|
2026-10-14 02:09:30 -04:00
|
|
|
|
.order_by(func.length(column).desc(), column.desc())
|
2026-10-13 00:01:48 -04:00
|
|
|
|
.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}"
|