104 lines
3.5 KiB
Python
104 lines
3.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
合同编号生成规则与生成函数。
|
|
|
|
格式:校准(字)字【年份】第序号号
|
|
各项目类型与“字”的对应:
|
|
- 法律顾问 -> 校准(顾)字【YYYY】第n号
|
|
- 专项服务 -> 校准(专)字【YYYY】第n号
|
|
- 民事案件代理 -> 校准(代)字【YYYY】第n号
|
|
- 刑事案件代理 -> 校准(刑辩)字【YYYY】第n号
|
|
- 行政案件代理 -> 校准(行政)字【YYYY】第n号
|
|
- 法律咨询 -> 校准(询)字【YYYY】第n号
|
|
- 破产程序代理 -> 校准(破产)字【YYYY】第n号
|
|
|
|
每个类别按各自类型+年份独立计数,跨年归零。
|
|
"""
|
|
|
|
# 项目类型 -> 合同编号中的“字”(括号内部分)
|
|
PROJECT_TYPE_SUFFIX = {
|
|
"法律顾问": "顾",
|
|
"专项服务": "专",
|
|
"民事案件代理": "代",
|
|
"刑事案件代理": "刑辩",
|
|
"行政案件代理": "行政",
|
|
"法律咨询": "询",
|
|
"破产程序代理": "破产",
|
|
}
|
|
|
|
|
|
def get_contract_no_suffix(project_type: str):
|
|
"""
|
|
获取项目类型对应的合同编号“字”。
|
|
:param project_type: 项目类型
|
|
:return: 字,如 "顾";未配置则返回 None
|
|
"""
|
|
if not project_type:
|
|
return None
|
|
return PROJECT_TYPE_SUFFIX.get(project_type.strip())
|
|
|
|
|
|
def format_contract_no(project_type: str, year: int, number: int) -> str:
|
|
"""
|
|
格式化为标准合同编号字符串。
|
|
:param project_type: 项目类型
|
|
:param year: 年份,如 2025
|
|
:param number: 序号(从 1 开始)
|
|
:return: 如 "校准(顾)字【2025】第1号";未配置类型则返回空字符串
|
|
"""
|
|
suffix = get_contract_no_suffix(project_type)
|
|
if suffix is None:
|
|
return ""
|
|
return f"校准({suffix})字【{year}】第{number}号"
|
|
|
|
|
|
def generate_next_contract_no(project_type: str, year: int):
|
|
"""
|
|
在**当前事务内**为该类型+年份占用下一个序号并返回合同编号。
|
|
必须在 transaction.atomic() 内调用,内部使用 select_for_update 保证并发安全。
|
|
:param project_type: 项目类型
|
|
:param year: 年份
|
|
:return: 生成的合同编号字符串;若项目类型未配置则返回 None
|
|
"""
|
|
from business.models import ContractCounter
|
|
|
|
suffix = get_contract_no_suffix(project_type)
|
|
if suffix is None:
|
|
return None
|
|
|
|
counter, created = ContractCounter.objects.select_for_update().get_or_create(
|
|
project_type=project_type,
|
|
year=year,
|
|
defaults={"current_number": 0},
|
|
)
|
|
next_number = counter.current_number + 1
|
|
counter.current_number = next_number
|
|
counter.save(update_fields=["current_number", "updated_at"])
|
|
return format_contract_no(project_type, year, next_number)
|
|
|
|
|
|
def get_next_contract_no_preview(project_type: str, year: int = None):
|
|
"""
|
|
仅查询下一个合同编号的预览(不占用序号,不写库)。
|
|
用于前端展示“下一个编号将是 XXX”。
|
|
:param project_type: 项目类型
|
|
:param year: 年份,默认当前年
|
|
:return: (next_number, formatted_string);未配置类型则 (0, "")
|
|
"""
|
|
from datetime import datetime
|
|
from business.models import ContractCounter
|
|
|
|
if year is None:
|
|
year = datetime.now().year
|
|
suffix = get_contract_no_suffix(project_type)
|
|
if suffix is None:
|
|
return 0, ""
|
|
|
|
counter = ContractCounter.objects.filter(
|
|
project_type=project_type,
|
|
year=year,
|
|
).first()
|
|
current = counter.current_number if counter else 0
|
|
next_number = current + 1
|
|
return next_number, format_contract_no(project_type, year, next_number)
|