Compare commits
2 Commits
bcbb27374f
...
6068f2315d
| Author | SHA1 | Date |
|---|---|---|
|
|
6068f2315d | |
|
|
298a32f744 |
|
|
@ -436,11 +436,18 @@ class InmService:
|
|||
raise ParseError("出入库与明细数量不一致,操作失败")
|
||||
for mioitemw in mioitemws:
|
||||
if mioitemw.wpr:
|
||||
Wpr.change_or_new(wpr=mioitemw.wpr, mb=mb, ftest=mioitemw.ftest)
|
||||
wpr = Wpr.change_or_new(
|
||||
wpr=mioitemw.wpr,
|
||||
mb=mb,
|
||||
)
|
||||
else:
|
||||
wpr = Wpr.change_or_new(number=mioitemw.number, mb=mb, ftest=mioitemw.ftest)
|
||||
wpr = Wpr.change_or_new(
|
||||
number=mioitemw.number,
|
||||
mb=mb,
|
||||
)
|
||||
mioitemw.wpr = wpr
|
||||
mioitemw.save()
|
||||
wpr.apply_ftest(mioitemw.ftest)
|
||||
|
||||
elif in_or_out == -1:
|
||||
mb.count = mb.count - change_count
|
||||
|
|
|
|||
|
|
@ -221,7 +221,9 @@ class RouteSerializer(CustomModelSerializer):
|
|||
|
||||
def validate(self, attrs):
|
||||
if attrs.get('material_out') and attrs.get('material_out_create'):
|
||||
raise ParseError('选择已有输出物料和新建半成品不可同时提交')
|
||||
raise serializers.ValidationError(
|
||||
'选择已有输出物料和新建半成品不可同时提交'
|
||||
)
|
||||
if attrs.get('routepack', None):
|
||||
attrs['material'] = attrs['routepack'].material
|
||||
if 'mgroup' in attrs and attrs['mgroup']:
|
||||
|
|
|
|||
|
|
@ -189,7 +189,10 @@ class BaseModel(models.Model):
|
|||
is_create = False
|
||||
if not self.id:
|
||||
is_create = True
|
||||
self.id = idWorker.get_id()
|
||||
self.id = str(idWorker.get_id())
|
||||
else:
|
||||
# BaseModel 的主键是 CharField,保证新建后内存值与数据库回读值类型一致。
|
||||
self.id = str(self.id)
|
||||
|
||||
old_parent = None
|
||||
need_handle_parent = False
|
||||
|
|
@ -209,7 +212,7 @@ class BaseModel(models.Model):
|
|||
except IntegrityError as e:
|
||||
if is_create:
|
||||
time.sleep(0.01)
|
||||
self.id = idWorker.get_id()
|
||||
self.id = str(idWorker.get_id())
|
||||
ins = super().save(*args, **kwargs)
|
||||
raise e
|
||||
# 处理父级
|
||||
|
|
|
|||
|
|
@ -1,14 +1,38 @@
|
|||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.hrm.models import Employee
|
||||
from apps.hrm.services import HrmService
|
||||
|
||||
from apps.vm.models import Visit
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
|
||||
class VisitTest(TestCase):
|
||||
def test_audit_end(self):
|
||||
v = Visit.objects.get(id='1547401054033940480')
|
||||
ep = Employee.objects.get(id='1547406632055607296')
|
||||
HrmService.sync_dahua_employee(ep, '', v.visit_time, v.leave_time)
|
||||
@patch.object(HrmService, 'sync_dahua_employee')
|
||||
def test_sync_employee_uses_visit_time_window(self, sync_employee):
|
||||
visit_time = timezone.now()
|
||||
leave_time = visit_time + timedelta(hours=2)
|
||||
visit = Visit.objects.create(
|
||||
purpose=Visit.V_PURPOSE_CHOICES[0][0],
|
||||
level=Visit.V_LEVEL_CHOICES[0][0],
|
||||
name='访客同步测试',
|
||||
visit_time=visit_time,
|
||||
leave_time=leave_time,
|
||||
)
|
||||
employee = Employee.objects.create(name='测试访客', type='visitor')
|
||||
|
||||
HrmService.sync_dahua_employee(
|
||||
employee,
|
||||
'',
|
||||
visit.visit_time,
|
||||
visit.leave_time,
|
||||
)
|
||||
|
||||
sync_employee.assert_called_once_with(
|
||||
employee,
|
||||
'',
|
||||
visit_time,
|
||||
leave_time,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -132,7 +132,8 @@ class WMaterialFilter(filters.FilterSet):
|
|||
"notok_sign": ["exact", "in", "isnull"],
|
||||
"count_xtest": ["gte", "isnull"],
|
||||
"supplier": ["exact"],
|
||||
"defect": ["exact", "isnull"]
|
||||
"defect": ["exact", "isnull"],
|
||||
"defect__okcate": ["exact", "in"],
|
||||
}
|
||||
|
||||
class MlogFilter(filters.FilterSet):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.27 on 2026-07-30 01:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wpm', '0136_alter_handover_global_scope_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mlog',
|
||||
name='wpr_defect_snapshot',
|
||||
field=models.JSONField(blank=True, editable=False, null=True, verbose_name='单件缺陷提交前快照'),
|
||||
),
|
||||
]
|
||||
|
|
@ -367,6 +367,12 @@ class Mlog(CommonADModel):
|
|||
'产出库存归属', choices=WmScope.choices, default=WmScope.DEPT)
|
||||
stored_notok = models.BooleanField('不合格品是否已入库', default=False)
|
||||
clear_defect = models.BooleanField('合格B类记为合格', default=False)
|
||||
wpr_defect_snapshot = models.JSONField(
|
||||
'单件缺陷提交前快照',
|
||||
null=True,
|
||||
blank=True,
|
||||
editable=False,
|
||||
)
|
||||
route = models.ForeignKey(Route, verbose_name='生产路线', on_delete=models.SET_NULL, null=True, blank=True)
|
||||
mtask = models.ForeignKey(
|
||||
Mtask, verbose_name='关联任务', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_mtask')
|
||||
|
|
|
|||
|
|
@ -189,12 +189,21 @@ class WMaterialSerializer(CustomModelSerializer):
|
|||
material_origin_name = serializers.StringRelatedField(source='material_origin', read_only=True)
|
||||
notok_sign_name = serializers.SerializerMethodField()
|
||||
defect_name = serializers.CharField(source="defect.name", read_only=True)
|
||||
defect_okcate = serializers.IntegerField(source="defect.okcate", read_only=True)
|
||||
defect_grade = serializers.SerializerMethodField()
|
||||
defect_grade_name = serializers.SerializerMethodField()
|
||||
count_working = serializers.SerializerMethodField(label='在制数量')
|
||||
count_handovering = serializers.SerializerMethodField(label='正在交送的数量')
|
||||
|
||||
def get_notok_sign_name(self, obj):
|
||||
return getattr(NotOkOption, obj.notok_sign, NotOkOption.qt).label if obj.notok_sign else None
|
||||
|
||||
def get_defect_grade(self, obj):
|
||||
return obj.defect.okcate if obj.defect else None
|
||||
|
||||
def get_defect_grade_name(self, obj):
|
||||
return obj.defect.get_okcate_display() if obj.defect else None
|
||||
|
||||
def get_count_working(self, obj):
|
||||
# 列表接口 queryset 已注解(单次聚合); 嵌套等无注解场景回退模型属性
|
||||
# 保留 decimal 精度(原 IntegerField 会截断在途量, 导致可用/可交接量偏大)
|
||||
|
|
@ -347,8 +356,7 @@ class MlogListSerializer(CustomModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Mlog
|
||||
fields = '__all__'
|
||||
# exclude = ["equipments","handle_users", "material_outs"]
|
||||
exclude = ["wpr_defect_snapshot"]
|
||||
|
||||
|
||||
class MlogbDetailSerializer(CustomModelSerializer):
|
||||
|
|
@ -419,7 +427,7 @@ class MlogSerializer(CustomModelSerializer):
|
|||
wm_in_state = serializers.IntegerField(source='wm_in.state', read_only=True)
|
||||
class Meta:
|
||||
model = Mlog
|
||||
fields = '__all__'
|
||||
exclude = ["wpr_defect_snapshot"]
|
||||
read_only_fields = EXCLUDE_FIELDS + \
|
||||
['submit_time', 'submit_user', 'material_outs']
|
||||
extra_kwargs = {
|
||||
|
|
@ -1280,6 +1288,9 @@ class HandoverSerializer(CustomModelSerializer):
|
|||
attrs['type'] = Handover.H_NORMAL
|
||||
|
||||
mtype = attrs["mtype"]
|
||||
clear_defect = bool(attrs.get('clear_defect', False))
|
||||
if clear_defect and mtype != Handover.H_MERGE:
|
||||
raise ParseError('仅合批时可清除批次缺陷')
|
||||
|
||||
new_wm:WMaterial = attrs.get("new_wm", None)
|
||||
if new_wm:
|
||||
|
|
@ -1404,11 +1415,17 @@ class HandoverSerializer(CustomModelSerializer):
|
|||
elif deptOrmgroupId != current_mdept_id:
|
||||
raise ParseError(f'第{ind+1}行-交接物料所属工段/车间不一致')
|
||||
if mtype == Handover.H_MERGE:
|
||||
clear_defect = attrs.get('clear_defect', False)
|
||||
if clear_defect and wm.state != WMaterial.WM_OK:
|
||||
raise ParseError(f'第{ind+1}行-清除缺陷合批仅支持合格品')
|
||||
if clear_defect and new_wm is not None and new_wm.defect is not None:
|
||||
raise ParseError('清除缺陷合批的目标批次不能带缺陷')
|
||||
raise ParseError('清除批次缺陷时目标批次不能带缺陷')
|
||||
if clear_defect and tracking == Material.MA_TRACKING_BATCH:
|
||||
if (
|
||||
wm.state != WMaterial.WM_OK
|
||||
or wm.defect is None
|
||||
or wm.defect.okcate != Defect.DEFECT_OK_B
|
||||
):
|
||||
raise ParseError(
|
||||
f'第{ind+1}行-批次追踪物料仅合格B类可清除批次缺陷'
|
||||
)
|
||||
if next_mat is None:
|
||||
next_mat = wm.material
|
||||
next_state = wm.state
|
||||
|
|
@ -1533,7 +1550,7 @@ class HandoverSerializer(CustomModelSerializer):
|
|||
elif count == wm.count:
|
||||
wpr_qs = Wpr.get_qs_by_wm(wm)
|
||||
for item in wpr_qs:
|
||||
Handoverbw.objects.get_or_create(wpr=item, handoverb=handoverb,
|
||||
Handoverbw.objects.get_or_create(wpr=item, handoverb=hb,
|
||||
defaults={"number": item.number})
|
||||
else:
|
||||
raise ParseError(f'第{ind+1}行-请提供交接物料明细')
|
||||
|
|
|
|||
|
|
@ -165,6 +165,73 @@ def get_pcoal_heat(year_s: int, month_s: int, day_s: int):
|
|||
return 25000
|
||||
|
||||
|
||||
def snapshot_mlog_wpr_defects(mlog: Mlog):
|
||||
"""锁定并保存日志提交前已有单件的缺陷状态。"""
|
||||
wpr_ids = list(Mlogbw.objects.filter(
|
||||
mlogb__mlog=mlog,
|
||||
wpr__isnull=False,
|
||||
).values_list("wpr_id", flat=True).distinct())
|
||||
list(Wpr.objects.select_for_update().filter(
|
||||
id__in=wpr_ids,
|
||||
).values_list("id", flat=True))
|
||||
|
||||
snapshot = {str(wpr_id): [] for wpr_id in wpr_ids}
|
||||
for defect in WprDefect.objects.filter(wpr_id__in=wpr_ids).values(
|
||||
"wpr_id",
|
||||
"defect_id",
|
||||
"is_main",
|
||||
):
|
||||
snapshot[str(defect["wpr_id"])].append({
|
||||
"defect_id": str(defect["defect_id"]),
|
||||
"is_main": defect["is_main"],
|
||||
})
|
||||
mlog.wpr_defect_snapshot = snapshot
|
||||
mlog.save(update_fields=["wpr_defect_snapshot", "update_time"])
|
||||
return snapshot
|
||||
|
||||
|
||||
def restore_mlog_wpr_defects(mlog: Mlog):
|
||||
"""按快照精确恢复日志提交前的单件缺陷。"""
|
||||
snapshot = mlog.wpr_defect_snapshot
|
||||
if snapshot is None:
|
||||
return False
|
||||
|
||||
wpr_ids = list(snapshot)
|
||||
wprs = {
|
||||
str(wpr.id): wpr
|
||||
for wpr in Wpr.objects.select_for_update().filter(id__in=wpr_ids)
|
||||
}
|
||||
WprDefect.objects.filter(wpr_id__in=wpr_ids).delete()
|
||||
restored = []
|
||||
for wpr_id, defects in snapshot.items():
|
||||
wpr = wprs.get(str(wpr_id))
|
||||
if wpr is None:
|
||||
myLogger.warning(
|
||||
"生产日志%s测回时未找到快照中的单件%s",
|
||||
mlog.id,
|
||||
wpr_id,
|
||||
)
|
||||
continue
|
||||
restored.extend(
|
||||
WprDefect(
|
||||
wpr=wpr,
|
||||
defect_id=item["defect_id"],
|
||||
is_main=item.get("is_main", False),
|
||||
)
|
||||
for item in defects
|
||||
)
|
||||
WprDefect.objects.bulk_create(restored)
|
||||
return True
|
||||
|
||||
|
||||
def restore_legacy_wpr_defects_from_wm(wpr: Wpr, wm: WMaterial):
|
||||
"""兼容无快照历史日志的旧测回行为。"""
|
||||
defects = []
|
||||
if wm.defect_id:
|
||||
defects.append({"defect_id": wm.defect_id, "is_main": True})
|
||||
wpr.replace_defects(defects)
|
||||
|
||||
|
||||
def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||
"""
|
||||
生产日志提交后需要执行的操作
|
||||
|
|
@ -200,6 +267,8 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
m_ins_list = []
|
||||
m_ins_bl_list = []
|
||||
|
||||
wpr_defect_snapshot = snapshot_mlog_wpr_defects(mlog)
|
||||
|
||||
if is_fix:
|
||||
can_matoutIds = process.get_canout_mat_ids()
|
||||
# 建立关系链
|
||||
|
|
@ -279,7 +348,8 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
if mlogbws.count() != mi_count:
|
||||
raise ParseError("日志与实际使用明细数量不一致,操作失败")
|
||||
for item in mlogbws:
|
||||
Wpr.change_or_new(wpr=item.wpr, old_wm=wm, ftest=item.ftest)
|
||||
wpr = Wpr.change_or_new(wpr=item.wpr, old_wm=wm)
|
||||
wpr.apply_ftest(item.ftest)
|
||||
|
||||
# 针对加工前不良的暂时额外处理
|
||||
# 合格B类的需要当合格品
|
||||
|
|
@ -312,7 +382,12 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
if mlogbws.count() != count:
|
||||
raise ParseError("日志与加工前不良明细数量不一致,操作失败")
|
||||
for item in mlogbws:
|
||||
Wpr.change_or_new(wpr=item.wpr, wm=wm, old_wm=wm, ftest=item.ftest)
|
||||
wpr = Wpr.change_or_new(
|
||||
wpr=item.wpr,
|
||||
wm=wm,
|
||||
old_wm=wm,
|
||||
)
|
||||
wpr.apply_ftest(item.ftest)
|
||||
|
||||
|
||||
mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
|
||||
|
|
@ -419,18 +494,35 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
raise ParseError("日志与明细数量不一致,操作失败")
|
||||
for item in mlogbws:
|
||||
if item.wpr:
|
||||
Wpr.change_or_new(wpr=item.wpr, wm=wm, ftest=item.ftest)
|
||||
wpr = Wpr.change_or_new(
|
||||
wpr=item.wpr,
|
||||
wm=wm,
|
||||
)
|
||||
else:
|
||||
if not check_wpr_number(item.number):
|
||||
raise ParseError(f'{item.number} 该编号格式有误')
|
||||
wpr_from = None
|
||||
if item.mlogbw_from:
|
||||
wpr_from = item.mlogbw_from.wpr
|
||||
wpr = Wpr.change_or_new(number=item.number,
|
||||
wm=wm, ftest=item.ftest,
|
||||
wpr_from=wpr_from)
|
||||
wpr = Wpr.change_or_new(
|
||||
number=item.number,
|
||||
wm=wm,
|
||||
wpr_from=wpr_from,
|
||||
)
|
||||
item.wpr = wpr
|
||||
item.save()
|
||||
wpr_id = str(wpr.id)
|
||||
if wpr_id not in wpr_defect_snapshot:
|
||||
wpr_defect_snapshot[wpr_id] = [
|
||||
{
|
||||
"defect_id": str(defect["defect_id"]),
|
||||
"is_main": defect["is_main"],
|
||||
}
|
||||
for defect in WprDefect.objects.filter(
|
||||
wpr=wpr,
|
||||
).values("defect_id", "is_main")
|
||||
]
|
||||
wpr.apply_ftest(item.ftest)
|
||||
|
||||
mlog.submit_time = now
|
||||
mlog.submit_user = user
|
||||
|
|
@ -438,6 +530,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
mlog.stored_scope = stored_scope
|
||||
if mlog.work_end_time is None and mlog.work_start_time is not None:
|
||||
mlog.work_end_time = now
|
||||
mlog.wpr_defect_snapshot = wpr_defect_snapshot
|
||||
mlog.save()
|
||||
|
||||
# 更新任务进度
|
||||
|
|
@ -471,6 +564,7 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
stored_scope = mlog.stored_scope
|
||||
stored_location = WmScope.resolve_location(stored_scope, mgroup)
|
||||
is_fix = mlog.is_fix
|
||||
has_wpr_defect_snapshot = mlog.wpr_defect_snapshot is not None
|
||||
|
||||
if is_fix:
|
||||
can_matoutIds = process.get_canout_mat_ids()
|
||||
|
|
@ -585,7 +679,8 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
if mlogbws.count() != mo_count:
|
||||
raise ParseError("日志与明细数量不一致,操作失败")
|
||||
for item in mlogbws:
|
||||
Wpr.change_or_new(wpr=item.wpr, old_wm=wm)
|
||||
wpr = Wpr.change_or_new(wpr=item.wpr, old_wm=wm)
|
||||
wpr.revert_ftest_info(item.ftest)
|
||||
|
||||
# 再生成消耗
|
||||
m_ins_list = []
|
||||
|
|
@ -629,7 +724,9 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
if mlogbws.count() != mi_count:
|
||||
raise ParseError("日志与实际使用明细数量不一致,操作失败")
|
||||
for item in mlogbws:
|
||||
Wpr.change_or_new(wpr=item.wpr, wm=wm)
|
||||
wpr = Wpr.change_or_new(wpr=item.wpr, wm=wm)
|
||||
if not has_wpr_defect_snapshot:
|
||||
restore_legacy_wpr_defects_from_wm(wpr, wm)
|
||||
|
||||
# 针对加工前不良的暂时额外处理
|
||||
if stored_notok:
|
||||
|
|
@ -662,7 +759,18 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
if mlogbws.count() != count:
|
||||
raise ParseError("日志与加工前不良明细数量不一致,操作失败")
|
||||
for item in mlogbws:
|
||||
Wpr.change_or_new(wpr=item.wpr, wm=wm)
|
||||
wpr = Wpr.change_or_new(wpr=item.wpr, wm=wm)
|
||||
if not has_wpr_defect_snapshot:
|
||||
restore_legacy_wpr_defects_from_wm(wpr, wm)
|
||||
|
||||
if has_wpr_defect_snapshot:
|
||||
restore_mlog_wpr_defects(mlog)
|
||||
mlog.wpr_defect_snapshot = None
|
||||
else:
|
||||
myLogger.warning(
|
||||
"生产日志%s无单件缺陷快照,测回按历史兼容逻辑处理,无法精确恢复提交前缺陷",
|
||||
mlog.id,
|
||||
)
|
||||
|
||||
mlog.submit_time = None
|
||||
mlog.submit_user = None
|
||||
|
|
@ -873,14 +981,20 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
WMaterial.decrease(wm_id=wm_from.id, user=user, count=xcount)
|
||||
|
||||
if need_add:
|
||||
target_defect = (
|
||||
merge_target_defect
|
||||
if mtype == Handover.H_MERGE
|
||||
else wm_from.defect
|
||||
)
|
||||
# 开始变动
|
||||
if handover.type == Handover.H_NORMAL:
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
defect_ok = wm_to.defect == wm_from.defect or (
|
||||
merge_clear_defect and wm_to.defect is None and wm_from.state == WMaterial.WM_OK
|
||||
)
|
||||
if wm_to.state != wm_from.state or wm_to.material != wm_from.material or not defect_ok:
|
||||
if (
|
||||
wm_to.state != wm_from.state
|
||||
or wm_to.material != wm_from.material
|
||||
or wm_to.defect != target_defect
|
||||
):
|
||||
raise ParseError("正常合并到的车间库存状态或物料异常")
|
||||
else:
|
||||
wm_to, _ = WMaterial.locked_get_or_create_inventory(
|
||||
|
|
@ -890,7 +1004,7 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
belong_dept=recive_dept,
|
||||
state=wm_from.state,
|
||||
notok_sign=wm_from.notok_sign,
|
||||
defect=merge_target_defect if mtype == Handover.H_MERGE else wm_from.defect,
|
||||
defect=target_defect,
|
||||
defaults={
|
||||
"batch_ofrom": wm_from.batch_ofrom,
|
||||
"material_ofrom": wm_from.material_ofrom,
|
||||
|
|
@ -904,7 +1018,11 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
wm_state = WMaterial.WM_REPAIR
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
if wm_to.state != WMaterial.WM_REPAIR or wm_to.material != wm_from.material or wm_to.defect != wm_from.defect:
|
||||
if (
|
||||
wm_to.state != WMaterial.WM_REPAIR
|
||||
or wm_to.material != wm_from.material
|
||||
or wm_to.defect != target_defect
|
||||
):
|
||||
raise ParseError("返修合并到的车间库存状态或物料异常")
|
||||
elif recive_mgroup:
|
||||
wm_to, _ = WMaterial.locked_get_or_create_inventory(
|
||||
|
|
@ -913,7 +1031,7 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
mgroup=recive_mgroup,
|
||||
belong_dept=recive_dept,
|
||||
notok_sign=wm_from.notok_sign,
|
||||
defect=wm_from.defect,
|
||||
defect=target_defect,
|
||||
material_origin=material,
|
||||
state=wm_state,
|
||||
defaults={
|
||||
|
|
@ -928,7 +1046,11 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
elif handover.type == Handover.H_SCRAP:
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
if wm_to.state != WMaterial.WM_SCRAP or wm_to.material != wm_from.material or wm_to.defect != wm_from.defect:
|
||||
if (
|
||||
wm_to.state != WMaterial.WM_SCRAP
|
||||
or wm_to.material != wm_from.material
|
||||
or wm_to.defect != target_defect
|
||||
):
|
||||
raise ParseError("报废合并到的车间库存状态或物料异常")
|
||||
elif recive_mgroup:
|
||||
wm_to, _ = WMaterial.locked_get_or_create_inventory(
|
||||
|
|
@ -937,7 +1059,7 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
mgroup=recive_mgroup,
|
||||
belong_dept=recive_dept,
|
||||
notok_sign=wm_from.notok_sign,
|
||||
defect=wm_from.defect,
|
||||
defect=target_defect,
|
||||
state=WMaterial.WM_SCRAP,
|
||||
defaults={
|
||||
"batch_ofrom": wm_from.batch_ofrom,
|
||||
|
|
@ -951,7 +1073,11 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
elif handover.type == Handover.H_CHANGE:
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
if wm_to.material != handover.material_changed or wm_to.state != handover.state_changed:
|
||||
if (
|
||||
wm_to.material != handover.material_changed
|
||||
or wm_to.state != handover.state_changed
|
||||
or wm_to.defect != target_defect
|
||||
):
|
||||
raise ParseError("改版合并到的车间库存状态或物料异常")
|
||||
elif handover.recive_mgroup:
|
||||
wm_to, _ = WMaterial.locked_get_or_create_inventory(
|
||||
|
|
@ -961,7 +1087,7 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
mgroup=recive_mgroup,
|
||||
belong_dept=recive_dept,
|
||||
notok_sign=wm_from.notok_sign,
|
||||
defect=wm_from.defect,
|
||||
defect=target_defect,
|
||||
material_origin=None if handover.material_changed == material else material,
|
||||
state_origin=None if handover.state_changed == wm_from.state else wm_from.state,
|
||||
defaults={
|
||||
|
|
@ -978,6 +1104,12 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
raise ParseError("仅合格品支持退回")
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
if (
|
||||
wm_to.state != wm_from.state
|
||||
or wm_to.material != wm_from.material
|
||||
or wm_to.defect != target_defect
|
||||
):
|
||||
raise ParseError("退料合并到的车间库存状态或物料异常")
|
||||
else:
|
||||
wm_to, _ = WMaterial.locked_get_or_create_inventory(
|
||||
batch=batch,
|
||||
|
|
@ -986,7 +1118,7 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
belong_dept=recive_dept,
|
||||
state=wm_from.state,
|
||||
notok_sign=wm_from.notok_sign,
|
||||
defect=merge_target_defect if mtype == Handover.H_MERGE else wm_from.defect,
|
||||
defect=target_defect,
|
||||
defaults={
|
||||
"batch_ofrom": wm_from.batch_ofrom,
|
||||
"material_ofrom": wm_from.material_ofrom,
|
||||
|
|
@ -1011,7 +1143,12 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
raise ParseError(f'{batch}物料明细中存在{len(wm_ids)}个不同物料批次')
|
||||
for item in handoverbws:
|
||||
wpr:Wpr = item.wpr
|
||||
Wpr.change_or_new(wpr=wpr, wm=wm_to, old_wm=wpr.wm, old_mb=wpr.mb)
|
||||
Wpr.change_or_new(
|
||||
wpr=wpr,
|
||||
wm=wm_to,
|
||||
old_wm=wpr.wm,
|
||||
old_mb=wpr.mb,
|
||||
)
|
||||
db_count = WMaterial.objects.filter(id=wm_to.id).values_list("count", flat=True).get()
|
||||
if db_count != Wpr.objects.filter(wm=wm_to).count():
|
||||
raise ParseError("交接与明细数量不一致2,操作失败")
|
||||
|
|
@ -1066,7 +1203,13 @@ def handover_revert(handover:Handover, handler:User=None):
|
|||
wm = WMaterial.objects.get(id=wm.id)
|
||||
for item in handoverbws:
|
||||
wpr:Wpr = item.wpr
|
||||
Wpr.change_or_new(wpr=wpr, wm=wm, old_wm=wpr.wm, old_mb=wpr.mb, add_version=False)
|
||||
Wpr.change_or_new(
|
||||
wpr=wpr,
|
||||
wm=wm,
|
||||
old_wm=wpr.wm,
|
||||
old_mb=wpr.mb,
|
||||
add_version=False,
|
||||
)
|
||||
|
||||
handover.submit_time = None
|
||||
handover.submit_user = None
|
||||
|
|
|
|||
|
|
@ -9,11 +9,18 @@ from django.test import SimpleTestCase, TestCase, TransactionTestCase
|
|||
from unittest import skipUnless
|
||||
|
||||
from apps.mtm.models import Material, Mgroup, WmScope
|
||||
from apps.qm.models import Defect
|
||||
from apps.system.models import Dept, User
|
||||
from apps.wpm.filters import HandoverFilter, WMaterialFilter
|
||||
from apps.wpm.models import Handover, WMaterial
|
||||
from apps.wpm.serializers import HandoverSerializer, WMaterialCreateSerializer
|
||||
from apps.wpm.serializers import (
|
||||
HandoverSerializer,
|
||||
WMaterialCreateSerializer,
|
||||
WMaterialSerializer,
|
||||
)
|
||||
from apps.wpm.services import restore_mlog_wpr_defects, snapshot_mlog_wpr_defects
|
||||
from apps.wpm.views import MlogbwViewSet
|
||||
from apps.wpmw.models import Wpr, WprDefect
|
||||
from rest_framework.exceptions import ParseError
|
||||
|
||||
|
||||
|
|
@ -97,6 +104,87 @@ class HandoverFilterTests(TestCase):
|
|||
])
|
||||
|
||||
|
||||
class WMaterialDefectGradeTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.material = Material.objects.create(name="缺陷等级测试物料")
|
||||
cls.defect_b = Defect.objects.create(
|
||||
name="B类缺陷",
|
||||
cate=Defect.cate_list[0],
|
||||
okcate=Defect.DEFECT_OK_B,
|
||||
)
|
||||
cls.defect_notok = Defect.objects.create(
|
||||
name="不合格缺陷",
|
||||
cate=Defect.cate_list[0],
|
||||
okcate=Defect.DEFECT_NOTOK,
|
||||
)
|
||||
cls.normal_with_notok_defect = WMaterial.objects.create(
|
||||
material=cls.material,
|
||||
batch="NORMAL-NOTOK",
|
||||
count=1,
|
||||
state=WMaterial.WM_OK,
|
||||
defect=cls.defect_notok,
|
||||
)
|
||||
cls.notok_with_b_defect = WMaterial.objects.create(
|
||||
material=cls.material,
|
||||
batch="NOTOK-B",
|
||||
count=1,
|
||||
state=WMaterial.WM_NOTOK,
|
||||
defect=cls.defect_b,
|
||||
)
|
||||
cls.normal_without_defect = WMaterial.objects.create(
|
||||
material=cls.material,
|
||||
batch="NORMAL-NONE",
|
||||
count=1,
|
||||
state=WMaterial.WM_OK,
|
||||
)
|
||||
|
||||
def test_serializer_exposes_nullable_defect_grade_without_using_state(self):
|
||||
normal_notok_data = WMaterialSerializer(
|
||||
self.normal_with_notok_defect
|
||||
).data
|
||||
notok_b_data = WMaterialSerializer(self.notok_with_b_defect).data
|
||||
no_defect_data = WMaterialSerializer(self.normal_without_defect).data
|
||||
|
||||
self.assertEqual(
|
||||
normal_notok_data["defect_grade"], Defect.DEFECT_NOTOK
|
||||
)
|
||||
self.assertEqual(normal_notok_data["defect_grade_name"], "不合格")
|
||||
self.assertEqual(
|
||||
notok_b_data["defect_grade"], Defect.DEFECT_OK_B
|
||||
)
|
||||
self.assertEqual(notok_b_data["defect_grade_name"], "合格B类")
|
||||
self.assertIsNone(no_defect_data["defect_grade"])
|
||||
self.assertIsNone(no_defect_data["defect_grade_name"])
|
||||
|
||||
def test_filtering_state_and_defect_grade_are_independent(self):
|
||||
normal_notok = WMaterialFilter(
|
||||
{
|
||||
"state": WMaterial.WM_OK,
|
||||
"defect__okcate": Defect.DEFECT_NOTOK,
|
||||
},
|
||||
queryset=WMaterial.objects.all(),
|
||||
).qs
|
||||
notok_b = WMaterialFilter(
|
||||
{
|
||||
"state": WMaterial.WM_NOTOK,
|
||||
"defect__okcate": Defect.DEFECT_OK_B,
|
||||
},
|
||||
queryset=WMaterial.objects.all(),
|
||||
).qs
|
||||
|
||||
self.assertQuerySetEqual(
|
||||
normal_notok,
|
||||
[self.normal_with_notok_defect],
|
||||
transform=lambda item: item,
|
||||
)
|
||||
self.assertQuerySetEqual(
|
||||
notok_b,
|
||||
[self.notok_with_b_defect],
|
||||
transform=lambda item: item,
|
||||
)
|
||||
|
||||
|
||||
class MlogbwViewSetTests(SimpleTestCase):
|
||||
@patch("apps.wpm.views.MlogViewSet.lock_and_check_can_update")
|
||||
@patch("apps.wpm.views.Mlogbw.cal_count_notok")
|
||||
|
|
@ -346,6 +434,235 @@ class WMaterialScopeTests(SimpleTestCase):
|
|||
self.assertIs(validated["send_mgroup"], mgroup)
|
||||
self.assertIs(validated["send_dept"], sending_dept)
|
||||
|
||||
def test_batch_tracking_merge_can_clear_ok_b_batch_defects(self):
|
||||
material = Material(tracking=Material.MA_TRACKING_BATCH)
|
||||
defect_a = Defect(id="1", okcate=Defect.DEFECT_OK_B)
|
||||
defect_b = Defect(id="2", okcate=Defect.DEFECT_OK_B)
|
||||
wm_a = WMaterial(
|
||||
id="10", material=material, batch="B-001", count=1,
|
||||
state=WMaterial.WM_OK, defect=defect_a,
|
||||
)
|
||||
wm_b = WMaterial(
|
||||
id="20", material=material, batch="B-002", count=1,
|
||||
state=WMaterial.WM_OK, defect=defect_b,
|
||||
)
|
||||
|
||||
validated = HandoverSerializer().validate({
|
||||
"wm": wm_a,
|
||||
"handoverb": [
|
||||
{"wm": wm_a, "count": 1},
|
||||
{"wm": wm_b, "count": 1},
|
||||
],
|
||||
"new_batch": "B-MERGED",
|
||||
"clear_defect": True,
|
||||
"type": Handover.H_NORMAL,
|
||||
"mtype": Handover.H_MERGE,
|
||||
})
|
||||
|
||||
self.assertTrue(validated["clear_defect"])
|
||||
self.assertEqual(validated["count"], 2)
|
||||
|
||||
def test_batch_tracking_merge_cannot_clear_notok_defect(self):
|
||||
material = Material(tracking=Material.MA_TRACKING_BATCH)
|
||||
defect = Defect(id="1", okcate=Defect.DEFECT_NOTOK)
|
||||
wm = WMaterial(
|
||||
id="10", material=material, batch="N-001", count=1,
|
||||
state=WMaterial.WM_NOTOK, defect=defect,
|
||||
)
|
||||
|
||||
with self.assertRaisesMessage(
|
||||
ParseError,
|
||||
"批次追踪物料仅合格B类可清除批次缺陷",
|
||||
):
|
||||
HandoverSerializer().validate({
|
||||
"wm": wm,
|
||||
"handoverb": [{"wm": wm, "count": 1}],
|
||||
"new_batch": "N-MERGED",
|
||||
"clear_defect": True,
|
||||
"type": Handover.H_NORMAL,
|
||||
"mtype": Handover.H_MERGE,
|
||||
})
|
||||
|
||||
def test_single_tracking_merge_can_clear_notok_batch_defect(self):
|
||||
material = Material(tracking=Material.MA_TRACKING_SINGLE)
|
||||
defect = Defect(id="1", okcate=Defect.DEFECT_NOTOK)
|
||||
wm = WMaterial(
|
||||
id="10", material=material, batch="S-001", count=1,
|
||||
state=WMaterial.WM_NOTOK, defect=defect,
|
||||
)
|
||||
wm_b = WMaterial(
|
||||
id="20", material=material, batch="S-002", count=1,
|
||||
state=WMaterial.WM_NOTOK, defect=defect,
|
||||
)
|
||||
|
||||
validated = HandoverSerializer().validate({
|
||||
"wm": wm,
|
||||
"handoverb": [
|
||||
{"wm": wm, "count": 1},
|
||||
{"wm": wm_b, "count": 1},
|
||||
],
|
||||
"new_batch": "S-MERGED",
|
||||
"clear_defect": True,
|
||||
"type": Handover.H_NORMAL,
|
||||
"mtype": Handover.H_MERGE,
|
||||
})
|
||||
|
||||
self.assertTrue(validated["clear_defect"])
|
||||
self.assertEqual(validated["count"], 2)
|
||||
|
||||
|
||||
class WprDefectLifecycleTests(SimpleTestCase):
|
||||
@patch("apps.wpmw.models.WprDefect.objects.filter")
|
||||
def test_change_inventory_never_changes_individual_defects(self, defect_filter):
|
||||
material = Material(id="1", tracking=Material.MA_TRACKING_SINGLE)
|
||||
old_wm = WMaterial(
|
||||
id="10", material=material, state=WMaterial.WM_OK,
|
||||
)
|
||||
target_wm = WMaterial(
|
||||
id="20", material=material, state=WMaterial.WM_OK,
|
||||
defect=None,
|
||||
)
|
||||
wpr = SimpleNamespace(
|
||||
wm=old_wm,
|
||||
mb=None,
|
||||
material=material,
|
||||
state=WMaterial.WM_OK,
|
||||
version=1,
|
||||
save=MagicMock(),
|
||||
)
|
||||
|
||||
Wpr.change_or_new(
|
||||
wpr=wpr,
|
||||
wm=target_wm,
|
||||
old_wm=old_wm,
|
||||
)
|
||||
|
||||
defect_filter.assert_not_called()
|
||||
self.assertIs(wpr.wm, target_wm)
|
||||
|
||||
@patch.object(Wpr, "replace_defects")
|
||||
@patch("apps.wpmw.models.FtestDefect.objects.filter")
|
||||
@patch("apps.wpmw.models.FtestItem.objects.filter")
|
||||
def test_apply_ftest_replaces_defects_and_carries_info(
|
||||
self,
|
||||
ftest_item_filter,
|
||||
ftest_defect_filter,
|
||||
replace_defects,
|
||||
):
|
||||
ftest = object()
|
||||
ftest_defect_filter.return_value.values.return_value = [
|
||||
{"defect_id": "30", "is_main": False},
|
||||
{"defect_id": "31", "is_main": True},
|
||||
]
|
||||
ftest_item_filter.return_value.select_related.return_value = [
|
||||
SimpleNamespace(
|
||||
id="40",
|
||||
test_val_json={"value": 1},
|
||||
testitem=SimpleNamespace(name="尺寸"),
|
||||
),
|
||||
]
|
||||
wpr = Wpr(
|
||||
oinfo={"existing": {"value": 2}},
|
||||
)
|
||||
wpr.save = MagicMock()
|
||||
|
||||
wpr.apply_ftest(ftest)
|
||||
|
||||
ftest_defect_filter.assert_called_once_with(ftest=ftest, has=True)
|
||||
replace_defects.assert_called_once_with([
|
||||
{"defect_id": "30", "is_main": False},
|
||||
{"defect_id": "31", "is_main": True},
|
||||
])
|
||||
self.assertEqual(
|
||||
wpr.oinfo["40"],
|
||||
{"name": "尺寸", "val": {"value": 1}},
|
||||
)
|
||||
|
||||
@patch.object(Wpr, "replace_defects")
|
||||
def test_apply_no_ftest_clears_defects(self, replace_defects):
|
||||
wpr = Wpr(oinfo={"existing": True})
|
||||
|
||||
wpr.apply_ftest(None)
|
||||
|
||||
replace_defects.assert_called_once_with([])
|
||||
self.assertEqual(wpr.oinfo, {"existing": True})
|
||||
|
||||
@patch("apps.wpmw.models.FtestItem.objects.filter")
|
||||
def test_revert_ftest_info_only_removes_matching_keys(self, item_filter):
|
||||
item_filter.return_value.values_list.return_value = ["10", "11"]
|
||||
wpr = Wpr(oinfo={"10": {"v": 1}, "other": {"v": 2}})
|
||||
wpr.save = MagicMock()
|
||||
|
||||
wpr.revert_ftest_info(object())
|
||||
|
||||
self.assertEqual(wpr.oinfo, {"other": {"v": 2}})
|
||||
wpr.save.assert_called_once()
|
||||
|
||||
|
||||
class MlogWprDefectSnapshotTests(SimpleTestCase):
|
||||
@patch("apps.wpm.services.WprDefect.objects.filter")
|
||||
@patch("apps.wpm.services.Wpr.objects.select_for_update")
|
||||
@patch("apps.wpm.services.Mlogbw.objects.filter")
|
||||
def test_snapshot_keeps_multiple_defects_and_empty_wpr(
|
||||
self,
|
||||
mlogbw_filter,
|
||||
select_for_update,
|
||||
defect_filter,
|
||||
):
|
||||
mlogbw_filter.return_value.values_list.return_value.distinct.return_value = [
|
||||
"1",
|
||||
"2",
|
||||
]
|
||||
select_for_update.return_value.filter.return_value.values_list.return_value = [
|
||||
"1",
|
||||
"2",
|
||||
]
|
||||
defect_filter.return_value.values.return_value = [
|
||||
{"wpr_id": "1", "defect_id": "10", "is_main": True},
|
||||
{"wpr_id": "1", "defect_id": "11", "is_main": False},
|
||||
]
|
||||
mlog = SimpleNamespace(id="100", save=MagicMock())
|
||||
|
||||
snapshot = snapshot_mlog_wpr_defects(mlog)
|
||||
|
||||
self.assertEqual(snapshot["2"], [])
|
||||
self.assertEqual(len(snapshot["1"]), 2)
|
||||
select_for_update.return_value.filter.return_value.values_list.assert_called_once_with(
|
||||
"id",
|
||||
flat=True,
|
||||
)
|
||||
|
||||
@patch("apps.wpm.services.WprDefect.objects.bulk_create")
|
||||
@patch("apps.wpm.services.WprDefect.objects.filter")
|
||||
@patch("apps.wpm.services.Wpr.objects.select_for_update")
|
||||
def test_restore_replaces_current_defects_exactly(
|
||||
self,
|
||||
select_for_update,
|
||||
defect_filter,
|
||||
bulk_create,
|
||||
):
|
||||
wpr = Wpr(id="1")
|
||||
select_for_update.return_value.filter.return_value = [wpr]
|
||||
mlog = SimpleNamespace(
|
||||
id="100",
|
||||
wpr_defect_snapshot={
|
||||
"1": [
|
||||
{"defect_id": "10", "is_main": True},
|
||||
{"defect_id": "11", "is_main": False},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
restored = restore_mlog_wpr_defects(mlog)
|
||||
|
||||
self.assertTrue(restored)
|
||||
defect_filter.return_value.delete.assert_called_once_with()
|
||||
created = bulk_create.call_args.args[0]
|
||||
self.assertEqual(
|
||||
[(str(item.defect_id), item.is_main) for item in created],
|
||||
[("10", True), ("11", False)],
|
||||
)
|
||||
|
||||
|
||||
@skipUnless(
|
||||
connection.vendor == "postgresql",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ class Wpr(BaseModel):
|
|||
pre_info = models.JSONField(verbose_name="预处理信息", default=dict, blank=True, null=True)
|
||||
|
||||
@classmethod
|
||||
def change_or_new(cls, wpr=None, number=None, mb=None, wm=None, old_mb=None, old_wm=None, ftest=None, wpr_from=None, add_version=True, number_out=None):
|
||||
def change_or_new(
|
||||
cls, wpr=None, number=None, mb=None, wm=None, old_mb=None,
|
||||
old_wm=None, wpr_from=None, add_version=True, number_out=None,
|
||||
):
|
||||
is_create = False
|
||||
if wpr is None and number is None:
|
||||
raise ParseError("id和number不能同时为空")
|
||||
|
|
@ -109,25 +112,9 @@ class Wpr(BaseModel):
|
|||
ins.state = wm.state
|
||||
if is_create:
|
||||
ins.material_start = wm.material
|
||||
WprDefect.objects.filter(wpr=ins).delete()
|
||||
if add_version:
|
||||
ins.version = ins.version + 1
|
||||
ins.save()
|
||||
if wm:
|
||||
if wm.defect:
|
||||
WprDefect.objects.create(wpr=ins, defect=wm.defect, is_main=True)
|
||||
if ftest:
|
||||
# 通过检验变更其缺陷项
|
||||
WprDefect.objects.filter(wpr=ins).delete()
|
||||
for ftestdefect in FtestDefect.objects.filter(ftest=ftest, has=True):
|
||||
WprDefect.objects.create(wpr=ins, defect=ftestdefect.defect, is_main=ftestdefect.is_main)
|
||||
# 携带某些检测信息
|
||||
oinfo = {}
|
||||
for ftestitem in FtestItem.objects.filter(ftest=ftest, addto_wpr=True):
|
||||
if ftestitem.test_val_json:
|
||||
oinfo[ftestitem.id] = {"name": ftestitem.testitem.name, "val": ftestitem.test_val_json}
|
||||
ins.oinfo = update_dict(ins.oinfo, oinfo)
|
||||
ins.save()
|
||||
# if ins.mb and ins.wm:
|
||||
# raise ParseError("所属仓库批次和车间批次不可同时存在2")
|
||||
if number_out:
|
||||
|
|
@ -138,6 +125,62 @@ class Wpr(BaseModel):
|
|||
raise ParseError(f"{ins.number}-出库编号-{number_out}-已存在")
|
||||
return ins
|
||||
|
||||
def replace_defects(self, defects):
|
||||
"""用给定缺陷列表完整替换单件缺陷。"""
|
||||
WprDefect.objects.filter(wpr=self).delete()
|
||||
WprDefect.objects.bulk_create([
|
||||
WprDefect(
|
||||
wpr=self,
|
||||
defect_id=item["defect_id"],
|
||||
is_main=item.get("is_main", False),
|
||||
)
|
||||
for item in defects
|
||||
])
|
||||
|
||||
def apply_ftest(self, ftest):
|
||||
"""应用一次明确的单件检验;无检验结果时缺陷为空。"""
|
||||
defects = []
|
||||
if ftest is not None:
|
||||
defects = list(FtestDefect.objects.filter(
|
||||
ftest=ftest,
|
||||
has=True,
|
||||
).values("defect_id", "is_main"))
|
||||
self.replace_defects(defects)
|
||||
|
||||
if ftest is None:
|
||||
return
|
||||
|
||||
oinfo = {}
|
||||
for ftestitem in FtestItem.objects.filter(
|
||||
ftest=ftest,
|
||||
addto_wpr=True,
|
||||
).select_related("testitem"):
|
||||
if ftestitem.test_val_json:
|
||||
oinfo[str(ftestitem.id)] = {
|
||||
"name": ftestitem.testitem.name,
|
||||
"val": ftestitem.test_val_json,
|
||||
}
|
||||
if oinfo:
|
||||
self.oinfo = update_dict(self.oinfo, oinfo)
|
||||
self.save(update_fields=["oinfo", "update_time"])
|
||||
|
||||
def revert_ftest_info(self, ftest):
|
||||
"""仅撤销指定检验写入单件携带信息的字段。"""
|
||||
if ftest is None or not self.oinfo:
|
||||
return
|
||||
item_ids = FtestItem.objects.filter(
|
||||
ftest=ftest,
|
||||
addto_wpr=True,
|
||||
).values_list("id", flat=True)
|
||||
changed = False
|
||||
oinfo = dict(self.oinfo)
|
||||
for item_id in item_ids:
|
||||
if oinfo.pop(str(item_id), None) is not None:
|
||||
changed = True
|
||||
if changed:
|
||||
self.oinfo = oinfo
|
||||
self.save(update_fields=["oinfo", "update_time"])
|
||||
|
||||
@classmethod
|
||||
def clear(cls, number_list):
|
||||
cls.objects.filter(number__in=number_list).update(mb=None, wm=None)
|
||||
|
|
@ -180,4 +223,4 @@ class WprDefect(BaseModel):
|
|||
"""TN:产物缺陷项关联表"""
|
||||
wpr = models.ForeignKey(Wpr, verbose_name="关联产物", on_delete=models.CASCADE)
|
||||
defect = models.ForeignKey("qm.defect", verbose_name="关联缺陷项", on_delete=models.CASCADE)
|
||||
is_main = models.BooleanField("是否主要缺陷", default=False)
|
||||
is_main = models.BooleanField("是否主要缺陷", default=False)
|
||||
|
|
|
|||
Loading…
Reference in New Issue