feat:单个编号规则支持c_day按天占位与按天归零;并发生成加工序级advisory锁防重号;流水号宽度按规则解析并对齐排序截取;超上限报错;工序保存时校验编号规则

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-07-23 16:05:05 +08:00
parent 1c203325fc
commit 1584763794
2 changed files with 37 additions and 17 deletions

View File

@ -146,6 +146,14 @@ class ProcessSerializer(CustomModelSerializer):
fields = '__all__' fields = '__all__'
read_only_fields = EXCLUDE_FIELDS read_only_fields = EXCLUDE_FIELDS
def validate_wpr_number_rule(self, value):
if value:
try:
value.format(c_year=2026, c_year2="26", c_month=1, c_day=1, m_model="MODEL", n_count=1)
except (KeyError, IndexError, ValueError) as e:
raise ValidationError(f"编号规则不合法({e}), 可用占位符: c_year, c_year2, c_month, c_day, m_model, n_count")
return value
class RoutePackSerializer(CustomModelSerializer): class RoutePackSerializer(CustomModelSerializer):
material_name = serializers.StringRelatedField( material_name = serializers.StringRelatedField(

View File

@ -1,4 +1,5 @@
import math import math
import re
from django.db import transaction from django.db import transaction
from rest_framework.decorators import action from rest_framework.decorators import action
@ -1012,40 +1013,51 @@ class MlogbInViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, BulkDestroyMode
c_year = handle_date.year c_year = handle_date.year
c_year2 = str(c_year)[-2:] c_year2 = str(c_year)[-2:]
c_month = handle_date.month c_month = handle_date.month
c_day = handle_date.day
m_model = material_out.model m_model = material_out.model
if 'm_model' in rule: if 'm_model' in rule:
if m_model is None: if m_model is None:
raise ParseError("生成编号出错:产品型号不能为空") raise ParseError("生成编号出错:产品型号不能为空")
elif m_model and m_model.islower(): elif m_model and m_model.islower():
raise ParseError("生成编号出错:产品型号不能为小写") raise ParseError("生成编号出错:产品型号不能为小写")
# 按生产日志查询 # 流水号宽度跟随规则中n_count的0填充宽度, 未指定默认4位
w_match = re.search(r"\{n_count:0(\d+)d\}", rule)
cq_w = int(w_match.group(1)) if w_match else 4
process = mlog.mgroup.process
# 同一工序的编号生成串行化, 防止并发取到相同的最大号; 事务结束自动释放
if connection.vendor == "postgresql" and connection.in_atomic_block:
with connection.cursor() as cursor:
cursor.execute("SELECT pg_advisory_xact_lock(hashtextextended(%s, 0))", [f"wpr_number_rule:{process.id}"])
# 按生产日志查询, 流水号归零周期跟随规则中最细的日期占位符
wpr_filter = {
"wpr_mlogbw__mlogb__material_out__isnull": False,
"wpr_mlogbw__mlogb__mlog__mgroup__process": process,
"wpr_mlogbw__mlogb__mlog__is_fix": False,
"wpr_mlogbw__mlogb__mlog__submit_time__isnull": False,
"wpr_mlogbw__mlogb__mlog__handle_date__year": c_year,
"wpr_mlogbw__mlogb__mlog__handle_date__month": c_month,
}
if "c_day" in rule:
wpr_filter["wpr_mlogbw__mlogb__mlog__handle_date__day"] = c_day
wpr = ( wpr = (
Wpr.objects.filter( Wpr.objects.filter(**wpr_filter)
wpr_mlogbw__mlogb__material_out__isnull=False, .annotate(last_seq=Substr("number", Length("number") - (cq_w - 1)))
wpr_mlogbw__mlogb__mlog__mgroup__process=mlog.mgroup.process, .order_by("last_seq")
wpr_mlogbw__mlogb__mlog__is_fix=False,
wpr_mlogbw__mlogb__mlog__submit_time__isnull=False,
wpr_mlogbw__mlogb__mlog__handle_date__year=c_year,
wpr_mlogbw__mlogb__mlog__handle_date__month=c_month,
)
.annotate(last_four=Substr("number", Length("number")-3))
.order_by("last_four")
.last() .last()
) )
cq_w = 4
if "n_count:02d" in rule:
cq_w = 2
n_count = 0 n_count = 0
if wpr: if wpr:
try: try:
n_count = int(wpr.number[-cq_w:].lstrip("0")) n_count = int(wpr.number[-cq_w:])
except Exception as e: except Exception as e:
raise ParseError(f"获取该类产品最后编号错误: {str(e)}") raise ParseError(f"获取该类产品最后编号错误: {str(e)}")
if n_count + gen_count > 10 ** cq_w - 1:
raise ParseError(f"流水号超出{cq_w}位上限, 请调整编号规则")
try: try:
if gen_count == 1: if gen_count == 1:
return rule.format(c_year=c_year, c_month=c_month, m_model=m_model, n_count=n_count + 1, c_year2=c_year2) return rule.format(c_year=c_year, c_month=c_month, c_day=c_day, m_model=m_model, n_count=n_count + 1, c_year2=c_year2)
else: else:
return [rule.format(c_year=c_year, c_month=c_month, m_model=m_model, n_count=n_count + i + 1, c_year2=c_year2) for i in range(gen_count)] return [rule.format(c_year=c_year, c_month=c_month, c_day=c_day, m_model=m_model, n_count=n_count + i + 1, c_year2=c_year2) for i in range(gen_count)]
except Exception as e: except Exception as e:
raise ParseError(f"个号生成错误: {e}") raise ParseError(f"个号生成错误: {e}")