Files
jyls_django/business/contract_no.py
2026-02-04 14:03:15 +08:00

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)