fix(wpm): match historical numbers to active rule
This commit is contained in:
parent
b365e06318
commit
fd4de2bd4b
|
|
@ -29,9 +29,7 @@ class GenNumberWithRuleFilterTests(SimpleTestCase):
|
|||
for rule, expected_dates in cases:
|
||||
with self.subTest(rule=rule):
|
||||
queryset = MagicMock()
|
||||
queryset.annotate.return_value = queryset
|
||||
queryset.order_by.return_value = queryset
|
||||
queryset.last.return_value = None
|
||||
queryset.values_list.return_value.distinct.return_value.iterator.return_value = []
|
||||
|
||||
with patch("apps.wpmw.models.Wpr.objects.filter", return_value=queryset) as mock_filter:
|
||||
MlogbInViewSet.gen_number_with_rule(rule, material, mlog)
|
||||
|
|
@ -47,9 +45,7 @@ class GenNumberWithRuleFilterTests(SimpleTestCase):
|
|||
|
||||
def test_escaped_date_placeholder_text_does_not_add_filter(self):
|
||||
queryset = MagicMock()
|
||||
queryset.annotate.return_value = queryset
|
||||
queryset.order_by.return_value = queryset
|
||||
queryset.last.return_value = None
|
||||
queryset.values_list.return_value.distinct.return_value.iterator.return_value = []
|
||||
material = SimpleNamespace(model=None)
|
||||
mlog = SimpleNamespace(
|
||||
handle_date=date(2026, 8, 4),
|
||||
|
|
@ -62,3 +58,136 @@ class GenNumberWithRuleFilterTests(SimpleTestCase):
|
|||
self.assertFalse(
|
||||
any("handle_date" in key for key in mock_filter.call_args.kwargs)
|
||||
)
|
||||
|
||||
def test_previous_sequence_width_is_compatible(self):
|
||||
queryset = MagicMock()
|
||||
queryset.values_list.return_value.distinct.return_value.iterator.return_value = [
|
||||
"2608P0001",
|
||||
"2608P0003",
|
||||
"2608P0002",
|
||||
]
|
||||
material = SimpleNamespace(model="P")
|
||||
mlog = SimpleNamespace(
|
||||
handle_date=date(2026, 8, 6),
|
||||
mgroup=SimpleNamespace(process=SimpleNamespace(id=123)),
|
||||
)
|
||||
|
||||
with patch("apps.wpmw.models.Wpr.objects.filter", return_value=queryset):
|
||||
number = MlogbInViewSet.gen_number_with_rule(
|
||||
"{c_year2}{c_month:02d}{m_model}{n_count:05d}",
|
||||
material,
|
||||
mlog,
|
||||
)
|
||||
|
||||
self.assertEqual(number, "2608P00004")
|
||||
|
||||
def test_mixed_sequence_widths_use_numeric_maximum(self):
|
||||
queryset = MagicMock()
|
||||
queryset.values_list.return_value.distinct.return_value.iterator.return_value = [
|
||||
"2608P9999",
|
||||
"2608P10000",
|
||||
"历史异常编号",
|
||||
]
|
||||
material = SimpleNamespace(model="P")
|
||||
mlog = SimpleNamespace(
|
||||
handle_date=date(2026, 8, 6),
|
||||
mgroup=SimpleNamespace(process=SimpleNamespace(id=123)),
|
||||
)
|
||||
|
||||
with patch("apps.wpmw.models.Wpr.objects.filter", return_value=queryset):
|
||||
number = MlogbInViewSet.gen_number_with_rule(
|
||||
"{c_year2}{c_month:02d}{m_model}{n_count:05d}",
|
||||
material,
|
||||
mlog,
|
||||
)
|
||||
|
||||
self.assertEqual(number, "2608P10001")
|
||||
|
||||
def test_unrelated_historical_formats_do_not_participate(self):
|
||||
queryset = MagicMock()
|
||||
queryset.values_list.return_value.distinct.return_value.iterator.return_value = [
|
||||
"202505508001",
|
||||
"3p05013",
|
||||
"05002",
|
||||
"3pb003",
|
||||
]
|
||||
material = SimpleNamespace(model="P")
|
||||
mlog = SimpleNamespace(
|
||||
handle_date=date(2026, 8, 7),
|
||||
mgroup=SimpleNamespace(process=SimpleNamespace(id=123)),
|
||||
)
|
||||
|
||||
with patch("apps.wpmw.models.Wpr.objects.filter", return_value=queryset):
|
||||
number = MlogbInViewSet.gen_number_with_rule(
|
||||
"{m_model}{n_count:04d}",
|
||||
material,
|
||||
mlog,
|
||||
)
|
||||
|
||||
self.assertEqual(number, "P0001")
|
||||
|
||||
def test_current_rule_uses_only_matching_model_numbers(self):
|
||||
queryset = MagicMock()
|
||||
queryset.values_list.return_value.distinct.return_value.iterator.return_value = [
|
||||
"P0002",
|
||||
"P0010",
|
||||
"B0099",
|
||||
"3pb100",
|
||||
]
|
||||
material = SimpleNamespace(model="P")
|
||||
mlog = SimpleNamespace(
|
||||
handle_date=date(2026, 8, 7),
|
||||
mgroup=SimpleNamespace(process=SimpleNamespace(id=123)),
|
||||
)
|
||||
|
||||
with patch("apps.wpmw.models.Wpr.objects.filter", return_value=queryset):
|
||||
number = MlogbInViewSet.gen_number_with_rule(
|
||||
"{m_model}{n_count:04d}",
|
||||
material,
|
||||
mlog,
|
||||
)
|
||||
|
||||
self.assertEqual(number, "P0011")
|
||||
|
||||
def test_empty_historical_number_is_ignored(self):
|
||||
queryset = MagicMock()
|
||||
queryset.values_list.return_value.distinct.return_value.iterator.return_value = [
|
||||
None,
|
||||
"",
|
||||
"P0002",
|
||||
]
|
||||
material = SimpleNamespace(model="P")
|
||||
mlog = SimpleNamespace(
|
||||
handle_date=date(2026, 8, 7),
|
||||
mgroup=SimpleNamespace(process=SimpleNamespace(id=123)),
|
||||
)
|
||||
|
||||
with patch("apps.wpmw.models.Wpr.objects.filter", return_value=queryset):
|
||||
number = MlogbInViewSet.gen_number_with_rule(
|
||||
"{m_model}{n_count:04d}",
|
||||
material,
|
||||
mlog,
|
||||
)
|
||||
|
||||
self.assertEqual(number, "P0003")
|
||||
|
||||
def test_repeated_sequence_placeholder_does_not_break_matching(self):
|
||||
queryset = MagicMock()
|
||||
queryset.values_list.return_value.distinct.return_value.iterator.return_value = [
|
||||
"P02-0002",
|
||||
"P03-0004",
|
||||
]
|
||||
material = SimpleNamespace(model="P")
|
||||
mlog = SimpleNamespace(
|
||||
handle_date=date(2026, 8, 7),
|
||||
mgroup=SimpleNamespace(process=SimpleNamespace(id=123)),
|
||||
)
|
||||
|
||||
with patch("apps.wpmw.models.Wpr.objects.filter", return_value=queryset):
|
||||
number = MlogbInViewSet.gen_number_with_rule(
|
||||
"{m_model}{n_count:02d}-{n_count:04d}",
|
||||
material,
|
||||
mlog,
|
||||
)
|
||||
|
||||
self.assertEqual(number, "P03-0003")
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ from django.db.models import Prefetch
|
|||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg import openapi
|
||||
from django.db import connection
|
||||
from django.db.models.functions import Substr, Length
|
||||
from apps.qm.models import FtestDefect, FtestItem
|
||||
|
||||
# Create your views here.
|
||||
|
|
@ -1012,9 +1011,11 @@ class MlogbInViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, BulkDestroyMode
|
|||
def gen_number_with_rule(cls, rule, material_out: Material, mlog: Mlog, gen_count=1):
|
||||
from apps.wpmw.models import Wpr
|
||||
|
||||
formatter = Formatter()
|
||||
rule_parts = list(formatter.parse(rule))
|
||||
rule_fields = {
|
||||
field_name
|
||||
for _, field_name, _, _ in Formatter().parse(rule)
|
||||
for _, field_name, _, _ in rule_parts
|
||||
if field_name
|
||||
}
|
||||
handle_date = mlog.handle_date
|
||||
|
|
@ -1049,18 +1050,51 @@ class MlogbInViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, BulkDestroyMode
|
|||
wpr_filter["wpr_mlogbw__mlogb__mlog__handle_date__month"] = c_month
|
||||
if "c_day" in rule_fields:
|
||||
wpr_filter["wpr_mlogbw__mlogb__mlog__handle_date__day"] = c_day
|
||||
wpr = (
|
||||
Wpr.objects.filter(**wpr_filter)
|
||||
.annotate(last_seq=Substr("number", Length("number") - (cq_w - 1)))
|
||||
.order_by("last_seq")
|
||||
.last()
|
||||
)
|
||||
n_count = 0
|
||||
if wpr:
|
||||
rule_values = {
|
||||
"c_year": c_year,
|
||||
"c_year2": c_year2,
|
||||
"c_month": c_month,
|
||||
"c_day": c_day,
|
||||
"m_model": m_model,
|
||||
}
|
||||
number_pattern_parts = ["^"]
|
||||
sequence_group_names = []
|
||||
for literal_text, field_name, format_spec, conversion in rule_parts:
|
||||
number_pattern_parts.append(re.escape(literal_text))
|
||||
if not field_name:
|
||||
continue
|
||||
if field_name == "n_count":
|
||||
# 流水号宽度可以变化,规则中的其他部分必须与当前上下文一致。
|
||||
group_name = f"n_count_{len(sequence_group_names)}"
|
||||
sequence_group_names.append(group_name)
|
||||
number_pattern_parts.append(fr"(?P<{group_name}>[0-9]+)")
|
||||
continue
|
||||
try:
|
||||
n_count = int(wpr.number[-cq_w:])
|
||||
except Exception as e:
|
||||
raise ParseError(f"获取该类产品最后编号错误: {str(e)}")
|
||||
field_value = rule_values[field_name]
|
||||
if conversion:
|
||||
field_value = formatter.convert_field(field_value, conversion)
|
||||
formatted_value = formatter.format_field(field_value, format_spec)
|
||||
except (KeyError, TypeError, ValueError) as e:
|
||||
raise ParseError(f"个号生成错误: {e}")
|
||||
number_pattern_parts.append(re.escape(formatted_value))
|
||||
number_pattern_parts.append("$")
|
||||
number_pattern = re.compile("".join(number_pattern_parts))
|
||||
n_count = 0
|
||||
# 只从符合当前规则固定部分的历史编号中提取流水号。例如当前规则为
|
||||
# P{n_count:04d}时,3pb003等同工序的旧格式编号不能参与续号;同时
|
||||
# 流水号使用数字匹配,以兼容04d调整为05d后的历史编号。
|
||||
numbers = Wpr.objects.filter(**wpr_filter).values_list("number", flat=True).distinct()
|
||||
for number in numbers.iterator():
|
||||
if not isinstance(number, str):
|
||||
continue
|
||||
sequence_match = number_pattern.fullmatch(number)
|
||||
if sequence_match and sequence_group_names:
|
||||
sequence_values = {
|
||||
int(sequence_match.group(group_name))
|
||||
for group_name in sequence_group_names
|
||||
}
|
||||
if len(sequence_values) == 1:
|
||||
n_count = max(n_count, sequence_values.pop())
|
||||
if n_count + gen_count > 10 ** cq_w - 1:
|
||||
raise ParseError(f"流水号超出{cq_w}位上限, 请调整编号规则")
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in New Issue