From 1584763794f595cb0f44c14a3443512f206cdc95 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 23 Jul 2026 16:05:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=8D=95=E4=B8=AA=E7=BC=96=E5=8F=B7?= =?UTF-8?q?=E8=A7=84=E5=88=99=E6=94=AF=E6=8C=81c=5Fday=E6=8C=89=E5=A4=A9?= =?UTF-8?q?=E5=8D=A0=E4=BD=8D=E4=B8=8E=E6=8C=89=E5=A4=A9=E5=BD=92=E9=9B=B6?= =?UTF-8?q?;=E5=B9=B6=E5=8F=91=E7=94=9F=E6=88=90=E5=8A=A0=E5=B7=A5?= =?UTF-8?q?=E5=BA=8F=E7=BA=A7advisory=E9=94=81=E9=98=B2=E9=87=8D=E5=8F=B7;?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E5=8F=B7=E5=AE=BD=E5=BA=A6=E6=8C=89=E8=A7=84?= =?UTF-8?q?=E5=88=99=E8=A7=A3=E6=9E=90=E5=B9=B6=E5=AF=B9=E9=BD=90=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=E6=88=AA=E5=8F=96;=E8=B6=85=E4=B8=8A=E9=99=90?= =?UTF-8?q?=E6=8A=A5=E9=94=99;=E5=B7=A5=E5=BA=8F=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E6=97=B6=E6=A0=A1=E9=AA=8C=E7=BC=96=E5=8F=B7=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- apps/mtm/serializers.py | 8 +++++++ apps/wpm/views.py | 46 ++++++++++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/apps/mtm/serializers.py b/apps/mtm/serializers.py index 5af9de02..0bd47eba 100644 --- a/apps/mtm/serializers.py +++ b/apps/mtm/serializers.py @@ -146,6 +146,14 @@ class ProcessSerializer(CustomModelSerializer): fields = '__all__' 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): material_name = serializers.StringRelatedField( diff --git a/apps/wpm/views.py b/apps/wpm/views.py index e49583c4..a9ba977a 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -1,4 +1,5 @@ import math +import re from django.db import transaction from rest_framework.decorators import action @@ -1012,40 +1013,51 @@ class MlogbInViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, BulkDestroyMode c_year = handle_date.year c_year2 = str(c_year)[-2:] c_month = handle_date.month + c_day = handle_date.day m_model = material_out.model if 'm_model' in rule: if m_model is None: raise ParseError("生成编号出错:产品型号不能为空") elif m_model and m_model.islower(): 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.objects.filter( - wpr_mlogbw__mlogb__material_out__isnull=False, - wpr_mlogbw__mlogb__mlog__mgroup__process=mlog.mgroup.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, - ) - .annotate(last_four=Substr("number", Length("number")-3)) - .order_by("last_four") + Wpr.objects.filter(**wpr_filter) + .annotate(last_seq=Substr("number", Length("number") - (cq_w - 1))) + .order_by("last_seq") .last() ) - cq_w = 4 - if "n_count:02d" in rule: - cq_w = 2 n_count = 0 if wpr: try: - n_count = int(wpr.number[-cq_w:].lstrip("0")) + n_count = int(wpr.number[-cq_w:]) except Exception as e: raise ParseError(f"获取该类产品最后编号错误: {str(e)}") + if n_count + gen_count > 10 ** cq_w - 1: + raise ParseError(f"流水号超出{cq_w}位上限, 请调整编号规则") try: 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: - 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: raise ParseError(f"个号生成错误: {e}")