148 lines
4.2 KiB
Python
148 lines
4.2 KiB
Python
"""业务协议号自动生成:XBCC / JZCC + YYYYMMDD + 6 位序号"""
|
||
from datetime import datetime
|
||
from typing import Literal, Optional
|
||
|
||
from sqlalchemy import text
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.utils import generate_uuid
|
||
from app.crud.protocol_donation_convert import assert_biz_no_available
|
||
from app.models.models import BizBizNoSequence
|
||
|
||
PREFIX_COLLECTION = "XBCC"
|
||
PREFIX_DONATION = "JZCC"
|
||
SEQ_DIGITS = 6
|
||
MAX_SEQ = 10**SEQ_DIGITS - 1
|
||
|
||
BizNoKind = Literal["collection", "donation"]
|
||
|
||
|
||
def _prefix_for(kind: BizNoKind) -> str:
|
||
return PREFIX_COLLECTION if kind == "collection" else PREFIX_DONATION
|
||
|
||
|
||
def _format_biz_no(prefix: str, biz_date: str, seq: int) -> str:
|
||
return f"{prefix}{biz_date}{seq:0{SEQ_DIGITS}d}"
|
||
|
||
|
||
def _allocate_sqlite_sequence(
|
||
db: Session,
|
||
*,
|
||
tenant_no: str,
|
||
prefix: str,
|
||
biz_date: str,
|
||
user_id: str,
|
||
now: datetime,
|
||
) -> int:
|
||
"""SQLite 测试环境使用 ORM 分配;生产 MySQL 仍走原子 SQL。"""
|
||
row = db.query(BizBizNoSequence).filter(
|
||
BizBizNoSequence.tenant_no == tenant_no,
|
||
BizBizNoSequence.prefix == prefix,
|
||
BizBizNoSequence.biz_date == biz_date,
|
||
BizBizNoSequence.del_flag == 0,
|
||
).first()
|
||
if row:
|
||
row.last_seq = int(row.last_seq or 0) + 1
|
||
row.update_by = user_id
|
||
row.update_time = now
|
||
else:
|
||
row = BizBizNoSequence(
|
||
id=generate_uuid(),
|
||
tenant_no=tenant_no,
|
||
prefix=prefix,
|
||
biz_date=biz_date,
|
||
last_seq=1,
|
||
order_num=0,
|
||
create_by=user_id,
|
||
create_dept_id="",
|
||
create_time=now,
|
||
update_by=user_id,
|
||
update_time=now,
|
||
del_flag=0,
|
||
)
|
||
db.add(row)
|
||
db.flush()
|
||
return int(row.last_seq)
|
||
|
||
|
||
def allocate_next_biz_no(
|
||
db: Session,
|
||
*,
|
||
tenant_no: str,
|
||
kind: BizNoKind,
|
||
user_id: Optional[str] = None,
|
||
biz_date: Optional[datetime] = None,
|
||
) -> str:
|
||
"""
|
||
原子分配下一个业务协议号。
|
||
须在 create 事务内调用;使用 MySQL LAST_INSERT_ID 保证并发安全。
|
||
"""
|
||
prefix = _prefix_for(kind)
|
||
now = biz_date or datetime.now()
|
||
date_str = now.strftime("%Y%m%d")
|
||
uid = user_id or ""
|
||
|
||
dialect_name = getattr(getattr(db.get_bind(), "dialect", None), "name", "")
|
||
if dialect_name == "sqlite":
|
||
seq_int = _allocate_sqlite_sequence(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
prefix=prefix,
|
||
biz_date=date_str,
|
||
user_id=uid,
|
||
now=now,
|
||
)
|
||
if seq_int > MAX_SEQ:
|
||
raise ValueError(f"当日{prefix}协议号序号已用尽(最大 {MAX_SEQ})")
|
||
biz_no = _format_biz_no(prefix, date_str, seq_int)
|
||
assert_biz_no_available(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
biz_no=biz_no,
|
||
for_donation=(kind == "donation"),
|
||
)
|
||
return biz_no
|
||
|
||
db.execute(
|
||
text(
|
||
"""
|
||
INSERT INTO biz_biz_no_sequence (
|
||
id, tenant_no, prefix, biz_date, last_seq,
|
||
order_num, remark, create_by, create_dept_id, create_time,
|
||
update_by, update_time, del_flag
|
||
) VALUES (
|
||
:id, :tenant_no, :prefix, :biz_date, LAST_INSERT_ID(1),
|
||
0, NULL, :user_id, '', :now,
|
||
:user_id, :now, 0
|
||
)
|
||
ON DUPLICATE KEY UPDATE
|
||
last_seq = LAST_INSERT_ID(last_seq + 1),
|
||
update_by = :user_id,
|
||
update_time = :now
|
||
"""
|
||
),
|
||
{
|
||
"id": generate_uuid(),
|
||
"tenant_no": tenant_no,
|
||
"prefix": prefix,
|
||
"biz_date": date_str,
|
||
"user_id": uid,
|
||
"now": now,
|
||
},
|
||
)
|
||
seq = db.execute(text("SELECT LAST_INSERT_ID()")).scalar()
|
||
if seq is None or int(seq) < 1:
|
||
raise ValueError("协议号序号分配失败")
|
||
seq_int = int(seq)
|
||
if seq_int > MAX_SEQ:
|
||
raise ValueError(f"当日{prefix}协议号序号已用尽(最大 {MAX_SEQ})")
|
||
|
||
biz_no = _format_biz_no(prefix, date_str, seq_int)
|
||
assert_biz_no_available(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
biz_no=biz_no,
|
||
for_donation=(kind == "donation"),
|
||
)
|
||
return biz_no
|