diff --git a/apps/hrm/serializers.py b/apps/hrm/serializers.py index 544a96a0..d76994b5 100755 --- a/apps/hrm/serializers.py +++ b/apps/hrm/serializers.py @@ -12,7 +12,7 @@ from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, A from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer from django.db import transaction from django.core.cache import cache -from apps.utils.tools import check_id_number_e, get_info_from_id +from apps.utils.tools import check_id_number_strict, get_info_from_id from rest_framework.exceptions import ParseError from django.conf import settings import datetime @@ -54,7 +54,7 @@ class EmployeeSimpleSerializer(CustomModelSerializer): class EmployeeCreateUpdateSerializer(CustomModelSerializer): id_number = serializers.CharField( - label="身份证号", validators=[check_id_number_e]) + label="身份证号", validators=[check_id_number_strict]) class Meta: model = Employee diff --git a/apps/inm/filters.py b/apps/inm/filters.py index 9a83211b..31c2f88f 100644 --- a/apps/inm/filters.py +++ b/apps/inm/filters.py @@ -11,7 +11,9 @@ class MaterialBatchFilter(filters.FilterSet): "material": ["exact"], "material__type": ["exact", "in"], "material__process": ["exact", "in"], - "count": ["exact", "gte", "lte"] + "count": ["exact", "gte", "lte"], + "state": ["exact", "in"], + "defect": ["exact"] } diff --git a/apps/inm/migrations/0029_alter_mioitem_batch.py b/apps/inm/migrations/0029_alter_mioitem_batch.py new file mode 100644 index 00000000..1d9cb796 --- /dev/null +++ b/apps/inm/migrations/0029_alter_mioitem_batch.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2025-03-04 08:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inm', '0028_mioitem_count_tested'), + ] + + operations = [ + migrations.AlterField( + model_name='mioitem', + name='batch', + field=models.TextField(verbose_name='批次号'), + ), + ] diff --git a/apps/inm/models.py b/apps/inm/models.py index 2775c183..78032f08 100644 --- a/apps/inm/models.py +++ b/apps/inm/models.py @@ -116,7 +116,7 @@ class MIOItem(BaseModel): WareHouse, on_delete=models.CASCADE, verbose_name='仓库') material = models.ForeignKey( Material, verbose_name='物料', on_delete=models.CASCADE) - batch = models.CharField('批次号', max_length=50) + batch = models.TextField('批次号') count = models.DecimalField('出入数量', max_digits=12, decimal_places=3) count_tested = models.PositiveIntegerField('已检数', null=True, blank=True) test_date = models.DateField('检验日期', null=True, blank=True) diff --git a/apps/mtm/serializers.py b/apps/mtm/serializers.py index 161bea6e..e65a4943 100644 --- a/apps/mtm/serializers.py +++ b/apps/mtm/serializers.py @@ -144,10 +144,10 @@ class RoutePackSerializer(CustomModelSerializer): fields = '__all__' read_only_fields = EXCLUDE_FIELDS + ['state', 'ticket'] - def update(self, instance, validated_data): - if validated_data['material'] != instance.material: - raise ParseError('不可变更产品') - return super().update(instance, validated_data) + # def update(self, instance, validated_data): + # if validated_data['material'] != instance.material: + # raise ParseError('不可变更产品') + # return super().update(instance, validated_data) class RouteSerializer(CustomModelSerializer): diff --git a/apps/qm/migrations/0045_alter_defect_code.py b/apps/qm/migrations/0045_alter_defect_code.py new file mode 100644 index 00000000..971cc386 --- /dev/null +++ b/apps/qm/migrations/0045_alter_defect_code.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2025-02-28 01:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0044_testitem_cd_expr'), + ] + + operations = [ + migrations.AlterField( + model_name='defect', + name='code', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='标识'), + ), + ] diff --git a/apps/qm/migrations/0046_alter_testitem_description.py b/apps/qm/migrations/0046_alter_testitem_description.py new file mode 100644 index 00000000..87e3870e --- /dev/null +++ b/apps/qm/migrations/0046_alter_testitem_description.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2025-03-03 07:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0045_alter_defect_code'), + ] + + operations = [ + migrations.AlterField( + model_name='testitem', + name='description', + field=models.TextField(blank=True, null=True, verbose_name='描述'), + ), + ] diff --git a/apps/qm/migrations/0047_ftestwork_count_ok_full.py b/apps/qm/migrations/0047_ftestwork_count_ok_full.py new file mode 100644 index 00000000..e3ce222d --- /dev/null +++ b/apps/qm/migrations/0047_ftestwork_count_ok_full.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2025-03-04 07:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0046_alter_testitem_description'), + ] + + operations = [ + migrations.AddField( + model_name='ftestwork', + name='count_ok_full', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='完全合格数量'), + ), + ] diff --git a/apps/qm/models.py b/apps/qm/models.py index f15e433d..dc0c3458 100644 --- a/apps/qm/models.py +++ b/apps/qm/models.py @@ -15,7 +15,7 @@ class Defect(CommonAModel): DEFECT_OK_B = 20 DEFECT_NOTOK = 30 name = models.CharField(max_length=50, verbose_name="名称") - code = models.CharField(max_length=50, verbose_name="标识") + code = models.CharField(max_length=50, verbose_name="标识", null=True, blank=True) cate = models.CharField(max_length=50, verbose_name="分类", choices=(("尺寸", "尺寸"), ("外观", "外观"), ("内质", "内质"))) okcate= models.PositiveSmallIntegerField(verbose_name="不合格分类", choices=((DEFECT_OK, "合格"), (DEFECT_OK_B, "合格B类"), (DEFECT_NOTOK, "不合格")), @@ -130,7 +130,7 @@ class TestItem(CommonAModel): tags = models.JSONField('检测类型', default=list, blank=True) mcate_tags = models.JSONField('物料系列标签', default=list, blank=True) sort = models.FloatField('排序', default=1) - description = models.TextField('描述', default='') + description = models.TextField('描述', null=True, blank=True) readonly = models.BooleanField('只读', default=False) formula = models.TextField('计算公式', null=True, blank=True) affects = models.JSONField('影响项列表', default=list, blank=True) @@ -151,7 +151,7 @@ QC_TRACE_CHOICES = ( class Qct(CommonAModel): name = models.CharField(max_length=50, verbose_name="名称") number = models.CharField(max_length=20, verbose_name="编号") - tags = models.JSONField('检测类型', default=list, blank=True) + tags = models.JSONField('检测类型', default=list, blank=True) # process 和 inm testitems = models.ManyToManyField(TestItem, verbose_name="检测项", blank=True, through='qm.qcttestitem') defects = models.ManyToManyField(Defect, verbose_name="缺陷项", blank=True, through='qm.qctdefect') materials = models.ManyToManyField(Material, verbose_name="物料", blank=True, through='qm.qctmat') @@ -168,6 +168,16 @@ class Qct(CommonAModel): def qct_mats(self): return QctMat.objects.filter(qct=self) + @classmethod + def get(self, material:Material, cate:str): + try: + qctmat = QctMat.objects.get(material=material, qct__is_deleted=False, qct__tags__contains=cate) + except QctMat.DoesNotExist: + qctmat = None + except QctMat.MultipleObjectsReturned: + raise ParseError("存在多个质检表,请手动选择") + return qctmat.qct if qctmat else None + class QctTestItem(BaseModel): qct = models.ForeignKey(Qct, verbose_name="质检模板", on_delete=models.CASCADE, related_name="qcttestitem") testitem = models.ForeignKey(TestItem, verbose_name="检测项", on_delete=models.CASCADE, null=True, blank=True) @@ -225,6 +235,7 @@ class FtestWork(CommonBDModel): count_sampling = models.PositiveIntegerField('抽检数量', default=0) count_sampling_ok = models.PositiveIntegerField('抽检合格数量', default=0) count_ok = models.PositiveIntegerField('合格数量', default=0) + count_ok_full = models.PositiveIntegerField('完全合格数量', null=True, blank=True) count_notok = models.PositiveIntegerField('不合格数量', default=0) need_update_wm = models.BooleanField('是否更新车间库存', default=True) count_notok_json = models.JSONField('不合格项数量统计', default=dict, null=False, blank=True) @@ -250,7 +261,10 @@ class FtestWork(CommonBDModel): def cal_count(self): self.count_notok = FtestworkDefect.objects.filter( ftestwork=self, defect__okcate=30).aggregate(total=Sum("count"))["total"] or 0 + count_notok_full = FtestworkDefect.objects.filter( + ftestwork=self).exclude(defect__okcate=10).aggregate(total=Sum("count"))["total"] or 0 self.count_ok = self.count - self.count_notok + self.count_ok_full = self.count - count_notok_full if self.type2 == 20: #全检 self.count_sampling = self.count self.count_sampling_ok = self.count_ok @@ -306,6 +320,25 @@ class Ftest(CommonBDModel): FtestDefect.objects.create(ftest=ftest, defect=defect) return ftest + # @classmethod + # def cal_count_notok(cls, ftestwork: FtestWork): + # count = Ftest.objects.filter(ftest_work=ftestwork).count() + # mlogb.count_real = count + # count_notok = 0 + # tqs = Mlogbw.objects.filter(mlogb=mlogb, ftest__is_ok=False) + # tqs_a = Mlogbw.objects.filter(mlogb=mlogb, ftest__is_ok=False).values("ftest__defect_main").annotate(xcount=Count('id')) + # defects = {defect.id: defect for defect in Defect.objects.filter(id__in=tqs.values_list("ftest__defect_main", flat=True))} + # md_ids = [] + # for t in tqs_a: + # md, _ = MlogbDefect.objects.get_or_create(mlogb=mlogb, defect=defects[t["ftest__defect_main"]]) + # md.count = t["xcount"] + # md.save() + # md_ids.append(md.id) + # count_notok += t["xcount"] + # MlogbDefect.objects.filter(mlogb=mlogb).exclude(id__in=md_ids).delete() + # mlogb.count_notok = count_notok + # mlogb.count_ok = count - mlogb.count_notok + # mlogb.save() class FtestItem(BaseModel): """ diff --git a/apps/qm/serializers.py b/apps/qm/serializers.py index df6088c8..bb71fbfa 100644 --- a/apps/qm/serializers.py +++ b/apps/qm/serializers.py @@ -16,15 +16,15 @@ class DefectSerializer(CustomModelSerializer): fields = '__all__' read_only_fields = EXCLUDE_FIELDS - def create(self, validated_data): - code = validated_data["code"] - if Defect.objects.get_queryset(all=True).filter(code=code).exists(): - raise ValidationError("缺陷标识已存在") - return super().create(validated_data) + # def create(self, validated_data): + # code = validated_data["code"] + # if Defect.objects.get_queryset(all=True).filter(code=code).exists(): + # raise ValidationError("缺陷标识已存在") + # return super().create(validated_data) - def update(self, instance, validated_data): - validated_data.pop("code", None) - return super().update(instance, validated_data) + # def update(self, instance, validated_data): + # validated_data.pop("code", None) + # return super().update(instance, validated_data) class TestItemSerializer(CustomModelSerializer): process_name = serializers.CharField(source="process.name", read_only=True) @@ -155,6 +155,7 @@ class FtestWorkCreateUpdateSerializer(CustomModelSerializer): def validate(self, attrs): qct = attrs.get("qct", None) + ftestworkdefect = attrs.get("ftestworkdefect", []) type2 = attrs.get('type2', 20) if type2 == 20: # 如果是全检 attrs['count_sampling'] = attrs['count'] @@ -168,7 +169,9 @@ class FtestWorkCreateUpdateSerializer(CustomModelSerializer): attrs['material'] = wm.material attrs['batch'] = wm.batch count_notok_json = attrs.get('count_notok_json', None) - if qct is None: # 没有qct时按原有逻辑处理 + if qct or ftestworkdefect: # 没有qct时按原有逻辑处理 + pass + else: if count_notok_json is None: raise ValidationError('不合格项不能为空') count_notok = 0 @@ -200,23 +203,30 @@ class FtestWorkCreateUpdateSerializer(CustomModelSerializer): with transaction.atomic(): ins: FtestWork = super().create(validated_data) for ftestworkdefect in ftestworkdefect: - FtestworkDefect.objects.create(ftestwork=ins, **ftestworkdefect) - if ftestworkdefect: + if ftestworkdefect['count'] > 0: + FtestworkDefect.objects.create(ftestwork=ins, **ftestworkdefect) + if ins.qct or ftestworkdefect: ins.cal_count() return ins def update(self, instance, validated_data): ftestworkdefect = validated_data.pop("ftestworkdefect", []) - ins = super().update(instance, validated_data) + ins:FtestWork = super().update(instance, validated_data) with transaction.atomic(): + fd_ids = [] for item in ftestworkdefect: - try: - ins = FtestworkDefect.objects.get(ftestwork=ins, defect=item["defect"]) - except FtestworkDefect.DoesNotExist: - raise ParseError("新的缺陷项!") - ins.count = item["count"] - ins.save() - if ftestworkdefect: + if item["count"] > 0: + try: + ins = FtestworkDefect.objects.get(ftestwork=ins, defect=item["defect"]) + except FtestworkDefect.DoesNotExist: + raise ParseError("新的缺陷项!") + except FtestworkDefect.MultipleObjectsReturned: + raise ParseError("缺陷项重复!") + ins.count = item["count"] + ins.save() + fd_ids.append(ins.id) + FtestworkDefect.objects.filter(ftestwork=ins).exclude(id__in=fd_ids).delete() + if ins.qct or ftestworkdefect: ins.cal_count() return ins diff --git a/apps/qm/services.py b/apps/qm/services.py index c2f19360..3a390340 100644 --- a/apps/qm/services.py +++ b/apps/qm/services.py @@ -23,8 +23,9 @@ def ftestwork_submit_validate(ins: FtestWork): def ftestwork_submit(ins:FtestWork, user: User): wm:WMaterial = ins.wm + fwd_qs = FtestworkDefect.objects.filter(ftestwork=ins) if ins.need_update_wm: - if ins.qct is None: + if ins.qct is None or not fwd_qs.exists(): if wm.state == WMaterial.WM_TEST: # 更新对应的车间库存 wm.count = wm.count - ins.count @@ -101,16 +102,20 @@ def ftestwork_submit(ins:FtestWork, user: User): # 此时调用了qct表 for item in FtestworkDefect.objects.filter(ftestwork=ins): item:FtestworkDefect = item - if item.count > 0 and item.defect.okcate == Defect.DEFECT_NOTOK: + if item.count > 0: wm.count = wm.count - item.count if wm.count < 0: raise ParseError("数量不足,扣减失败") + wm.save() + wmstate = WMaterial.WM_OK + if item.defect.okcate == Defect.DEFECT_NOTOK: + wmstate = WMaterial.WM_NOTOK wmx, new_create = WMaterial.objects.get_or_create( material=wm.material, batch=wm.batch, mgroup=wm.mgroup, belong_dept=wm.belong_dept, - state=WMaterial.WM_NOTOK, + state=wmstate, notok_sign=None, defect=item.defect, defaults={ @@ -120,7 +125,6 @@ def ftestwork_submit(ins:FtestWork, user: User): if not new_create: wmx.count = wmx.count + item.count wmx.save() - wm.save() ins.submit_user = user ins.submit_time = timezone.now() ins.save() diff --git a/apps/rpm/serializers.py b/apps/rpm/serializers.py index d1e09878..7bc92f40 100644 --- a/apps/rpm/serializers.py +++ b/apps/rpm/serializers.py @@ -12,7 +12,7 @@ from rest_framework.exceptions import ParseError, ValidationError from django.db import transaction from apps.third.dahua import dhClient from apps.third.tapis import dhapis -from apps.utils.tools import check_id_number_e, check_phone_e +from apps.utils.tools import check_id_number_strict, check_phone_e from apps.wf.serializers import TicketSimpleSerializer from apps.rpm.services import rpj_member_come, rpj_certificate_in from apps.rpm.models import RpjLog @@ -138,7 +138,7 @@ class RemployeeCreateSerializer(CustomModelSerializer): rparty = serializers.PrimaryKeyRelatedField(queryset=Rparty.objects.all(), label='相关方ID', required=False) phone = serializers.CharField(label="手机号", validators=[check_phone_e]) photo = serializers.CharField(label='照片地址', required=True) - id_number = serializers.CharField(label="身份证号", validators=[check_id_number_e], required=True) + id_number = serializers.CharField(label="身份证号", validators=[check_id_number_strict], required=True) class Meta: model = Remployee diff --git a/apps/utils/tools.py b/apps/utils/tools.py index b23e977f..c7c58384 100755 --- a/apps/utils/tools.py +++ b/apps/utils/tools.py @@ -250,6 +250,23 @@ def check_id_number(idcard): return False, Errors[0] +def check_id_number_strict(id_card: str): + """校验 18 位中国身份证号码是否合法""" + if not re.match(r'^\d{17}[\dXx]$', id_card): + return False + + # 系数和校验码 + weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + check_codes = "10X98765432" + + # 计算校验码 + total = sum(int(id_card[i]) * weights[i] for i in range(17)) + expected_check_code = check_codes[total % 11] + + if expected_check_code == id_card[-1].upper(): + return id_card + raise ValidationError('身份证号校验失败') + def check_phone_e(phone): re_phone = r'^1\d{10}$' if not re.match(re_phone, phone): diff --git a/apps/vm/serializers.py b/apps/vm/serializers.py index 3c65f900..dd5ff87c 100644 --- a/apps/vm/serializers.py +++ b/apps/vm/serializers.py @@ -4,7 +4,7 @@ from apps.hrm.serializers import EmployeeSimpleSerializer from apps.system.models import Dept from apps.utils.fields import MyFilePathField from apps.utils.serializers import CustomModelSerializer -from apps.utils.tools import check_id_number_e, check_phone_e +from apps.utils.tools import check_id_number_strict, check_phone_e from apps.vm.models import Visit, Visitor, Vpeople from rest_framework import serializers from rest_framework.exceptions import ParseError @@ -59,7 +59,7 @@ class VisitorCreateSerializer(CustomModelSerializer): phone = serializers.CharField(label="手机号", validators=[check_phone_e]) photo = serializers.CharField(label='照片地址', required=True) id_number = serializers.CharField( - label="身份证号", validators=[check_id_number_e], required=True) + label="身份证号", validators=[check_id_number_strict], required=True) class Meta: model = Visitor diff --git a/apps/wpm/migrations/0089_mlogbdefect.py b/apps/wpm/migrations/0089_mlogbdefect.py new file mode 100644 index 00000000..feaaa309 --- /dev/null +++ b/apps/wpm/migrations/0089_mlogbdefect.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.12 on 2025-02-28 01:11 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0044_testitem_cd_expr'), + ('wpm', '0088_auto_20250224_0938'), + ] + + operations = [ + migrations.CreateModel( + name='MlogbDefect', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('count', models.PositiveIntegerField(default=0, verbose_name='数量')), + ('defect', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='qm.defect', verbose_name='缺陷')), + ('mlogb', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.mlogb', verbose_name='生产记录')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/wpm/migrations/0090_mlogbdefect_floor.py b/apps/wpm/migrations/0090_mlogbdefect_floor.py new file mode 100644 index 00000000..3add2b32 --- /dev/null +++ b/apps/wpm/migrations/0090_mlogbdefect_floor.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2025-03-03 06:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0089_mlogbdefect'), + ] + + operations = [ + migrations.AddField( + model_name='mlogbdefect', + name='floor', + field=models.IntegerField(blank=True, null=True, verbose_name='层数'), + ), + ] diff --git a/apps/wpm/migrations/0091_auto_20250303_1708.py b/apps/wpm/migrations/0091_auto_20250303_1708.py new file mode 100644 index 00000000..d2d49439 --- /dev/null +++ b/apps/wpm/migrations/0091_auto_20250303_1708.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.12 on 2025-03-03 09:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0046_alter_testitem_description'), + ('wpm', '0090_mlogbdefect_floor'), + ] + + operations = [ + migrations.AddField( + model_name='mlog', + name='qct', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='qm.qct', verbose_name='所用质检表'), + ), + migrations.AddField( + model_name='mlogbdefect', + name='count_test', + field=models.IntegerField(blank=True, null=True, verbose_name='抽检数'), + ), + ] diff --git a/apps/wpm/migrations/0092_auto_20250304_1433.py b/apps/wpm/migrations/0092_auto_20250304_1433.py new file mode 100644 index 00000000..f23aea30 --- /dev/null +++ b/apps/wpm/migrations/0092_auto_20250304_1433.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2025-03-04 06:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0091_auto_20250303_1708'), + ] + + operations = [ + migrations.AddField( + model_name='mlog', + name='count_ok_full', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='完全合格数'), + ), + migrations.AddField( + model_name='mlogb', + name='count_ok_full', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='完全合格数'), + ), + ] diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 3b1a9745..a55a8997 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -10,6 +10,7 @@ from apps.pum.models import Supplier from django.db.models import Sum, Subquery from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import ParseError +from django.db.models import Count # Create your models here. @@ -171,6 +172,7 @@ class Mlog(CommonADModel): fill_way = models.PositiveSmallIntegerField("填写方式", default=10, help_text='10:仅二级;20:二三级;30:一二级') mtype = models.PositiveSmallIntegerField('生产类型', default=10, help_text='10:自生产;20:外协生产', choices=((10, '自生产'), (20, '外协生产'))) is_fix = models.BooleanField('是否用于返修', default=False) + qct = models.ForeignKey("qm.qct", verbose_name='所用质检表', on_delete=models.SET_NULL, null=True, blank=True) supplier = models.ForeignKey(Supplier, verbose_name='外协供应商', on_delete=models.SET_NULL, null=True, blank=True) work_start_time = models.DateTimeField('生产开始时间', null=True, blank=True) work_end_time = models.DateTimeField('生产结束时间', null=True, blank=True) @@ -205,6 +207,7 @@ class Mlog(CommonADModel): count_break = models.PositiveIntegerField('加工碎料数', default=0) count_ok = models.PositiveIntegerField('合格数', default=0) + count_ok_full = models.PositiveIntegerField('完全合格数', null=True, blank=True) count_notok = models.PositiveIntegerField('不合格数', default=0) count_break_t = models.PositiveIntegerField('检验碎料数', default=0) @@ -287,7 +290,11 @@ class Mlog(CommonADModel): def audit_ignore_fields(self): return ['create_by', 'update_by', 'create_time', 'update_time', 'id'] - + + @property + def mlogdefect(self): + return MlogbDefect.objects.filter(mlogb__mlog=self) + @classmethod def count_fields(cls): mlog_count_fields = [] @@ -332,6 +339,7 @@ class Mlogb(BaseModel): count_break_t = models.PositiveIntegerField('检验碎料数', default=0) count_real = models.PositiveIntegerField('实际生产数', default=0) count_ok = models.PositiveIntegerField('合格数量', default=0) + count_ok_full = models.PositiveIntegerField('完全合格数', null=True, blank=True) count_notok = models.PositiveIntegerField('不合格数', default=0) count_pn_jgqbl = models.PositiveIntegerField('加工前不良', default=0) @@ -357,6 +365,33 @@ class Mlogb(BaseModel): return "in", self.material_in.tracking elif self.material_out: return "out", self.material_out.tracking + + @property + def mlogbdefect(self): + return MlogbDefect.objects.filter(mlogb=self) + + def cal_count_notok(self): + count_notok = MlogbDefect.objects.filter(defect__okcate=30, mlogb=self).aggregate(total=Sum("count"))["total"] or 0 + count_notok_full = MlogbDefect.objects.filter(mlogb=self).exclude(defect__okcate=10).aggregate(total=Sum("count"))["total"] or 0 + self.count_notok = count_notok + self.count_ok = self.count_real - count_notok + self.count_ok_full = self.count_real - count_notok_full + self.save(update_fields=["count_ok", "count_notok", "count_ok_full"]) + mlog = self.mlog + if mlog: + count_notok = MlogbDefect.objects.filter(defect__okcate=30, mlogb__mlog=mlog).aggregate(total=Sum("count"))["total"] or 0 + count_notok_full = MlogbDefect.objects.filter(mlogb__mlog=mlog).exclude(defect__okcate=10).aggregate(total=Sum("count"))["total"] or 0 + mlog.count_ok_full = self.count_real - count_notok_full + mlog.count_notok = count_notok + mlog.count_ok = self.count_real - count_notok + mlog.save(update_fields=["count_ok", "count_notok", "count_ok_full"]) + +class MlogbDefect(BaseModel): + mlogb = models.ForeignKey(Mlogb, verbose_name='生产记录', on_delete=models.CASCADE) + defect = models.ForeignKey("qm.Defect", verbose_name='缺陷', on_delete=models.CASCADE) + count_test = models.IntegerField("抽检数", null=True, blank=True) + floor = models.IntegerField("层数", null=True, blank=True) + count = models.PositiveIntegerField('数量', default=0) class Mlogbw(BaseModel): """TN: 单个产品生产/检验日志 @@ -372,6 +407,29 @@ class Mlogbw(BaseModel): on_delete=models.PROTECT, null=True, blank=True, related_name="mlogbw_ftest") note = models.TextField('备注', null=True, blank=True) + @classmethod + def cal_count_notok(cls, mlogb: Mlog): + from apps.qm.models import Defect + count = Mlogbw.objects.filter(mlogb=mlogb).count() + if mlogb.material_in: + mlogb.count_use = count + elif mlogb.material_out: + mlogb.count_real = count + count_notok = 0 + tqs = Mlogbw.objects.filter(mlogb=mlogb, ftest__is_ok=False) + tqs_a = Mlogbw.objects.filter(mlogb=mlogb, ftest__is_ok=False).values("ftest__defect_main").annotate(xcount=Count('id')) + defects = {defect.id: defect for defect in Defect.objects.filter(id__in=tqs.values_list("ftest__defect_main", flat=True))} + md_ids = [] + for t in tqs_a: + md, _ = MlogbDefect.objects.get_or_create(mlogb=mlogb, defect=defects[t["ftest__defect_main"]]) + md.count = t["xcount"] + md.save() + md_ids.append(md.id) + count_notok += t["xcount"] + MlogbDefect.objects.filter(mlogb=mlogb).exclude(id__in=md_ids).delete() + mlogb.count_notok = count_notok + mlogb.count_ok = count - mlogb.count_notok + mlogb.save() class Handover(CommonADModel): """ diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index b2c725db..eb642089 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -6,7 +6,7 @@ from datetime import datetime from .models import (SfLog, StLog, SfLogExp, WMaterial, Mlog, Handover, Handoverb, Mlogb, AttLog, - OtherLog, Fmlog, BatchSt, Mlogbw, Handoverbw) + OtherLog, Fmlog, BatchSt, Mlogbw, Handoverbw, MlogbDefect) from apps.system.models import Dept, User from apps.system.serializers import UserSimpleSerializer from apps.pm.models import Mtask, Mtaskb @@ -18,11 +18,13 @@ from django.db import transaction from django.utils import timezone from django.core.cache import cache from django.utils.timezone import localdate -from apps.qm.models import NotOkOption, Ftest +from apps.qm.models import NotOkOption, Qct from apps.wf.serializers import TicketSimpleSerializer from apps.wpmw.models import Wpr from apps.qm.serializers import FtestProcessSerializer import logging +from apps.qm.models import Defect +from apps.utils.snowflake import idWorker mylogger = logging.getLogger("log") class OtherLogSerializer(CustomModelSerializer): @@ -201,6 +203,13 @@ class WMaterialSerializer(CustomModelSerializer): ret['count_cando'] = ret['count'] - ret['count_working'] return ret +class MlogbDefectSerializer(CustomModelSerializer): + defect_name = serializers.CharField(source="defect.name", read_only=True) + defect_okcate = serializers.CharField(source="defect.okcate", read_only=True) + class Meta: + model = MlogbDefect + fields = ["id", "defect_name", "count", "mlogb", "defect", "floor", "count_test", "defect_okcate"] + read_only_fields = EXCLUDE_FIELDS_BASE + ["mlogb"] class MlogbSerializer(CustomModelSerializer): material_out_ = MaterialSimpleSerializer( @@ -215,15 +224,6 @@ class MlogbSerializer(CustomModelSerializer): extra_kwargs = { 'material_out': {'required': True, 'allow_null': False}} -class MlogbDetailSerializer(CustomModelSerializer): - material_out_name = serializers.StringRelatedField( - source='material_out', read_only=True) - material_in_name = serializers.StringRelatedField(source='material_in', read_only=True) - material_out_tracking = serializers.IntegerField(source="material_out.tracking", read_only=True) - - class Meta: - model = Mlogb - fields = '__all__' class MlogListSerializer(CustomModelSerializer): mstate_json = serializers.JSONField(source='mgroup.process.mstate_json', read_only=True) @@ -269,6 +269,18 @@ class MlogListSerializer(CustomModelSerializer): model = Mlog fields = '__all__' + +class MlogbDetailSerializer(CustomModelSerializer): + material_out_name = serializers.StringRelatedField( + source='material_out', read_only=True) + material_in_name = serializers.StringRelatedField(source='material_in', read_only=True) + material_out_tracking = serializers.IntegerField(source="material_out.tracking", read_only=True) + mlogbdefect = MlogbDefectSerializer(many=True, read_only=True) + + class Meta: + model = Mlogb + fields = '__all__' + class MlogSerializer(CustomModelSerializer): mstate_json = serializers.JSONField(source='mgroup.process.mstate_json', read_only=True) supplier_name = serializers.CharField(source='supplier.name', read_only=True) @@ -313,6 +325,7 @@ class MlogSerializer(CustomModelSerializer): ticket_ = TicketSimpleSerializer(source='ticket', read_only=True) test_user_name = serializers.CharField(source='test_user.name', read_only=True) + mlogdefect = MlogbDefectSerializer(many=True) class Meta: model = Mlog fields = '__all__' @@ -327,6 +340,7 @@ class MlogSerializer(CustomModelSerializer): def create(self, validated_data): material_out = validated_data['material_out'] mtask:Mtask = validated_data.get('mtask', None) + mlogdefect = validated_data.pop('mlogdefect', []) if mtask and mtask.state != Mtask.MTASK_ASSGINED: raise ParseError('该任务非下达中不可选择') with transaction.atomic(): @@ -380,15 +394,28 @@ class MlogSerializer(CustomModelSerializer): 'mtask': instance.mtask, 'material_out': instance.material_out, 'count_real': instance.count_real, 'count_ok': instance.count_ok, 'count_notok': instance.count_notok, - 'count_break_t': instance.count_break_t + 'count_break_t': instance.count_break_t, + 'qct': instance.qct } - for f in Mlogb._meta.fields: - if 'count_n_' in f.name: - add_dict_2[f.name] = getattr(instance, f.name) + need_mdfect = False + if instance.qct or mlogdefect: + need_mdfect = True + else: + for f in Mlogb._meta.fields: + if 'count_n_' in f.name: + add_dict_2[f.name] = getattr(instance, f.name) ddict = {} if wm_in: ddict = {"batch_ofrom": wm_in.batch_ofrom, "material_ofrom": wm_in.material_ofrom} - Mlogb.objects.get_or_create(**add_dict_2, defaults=ddict) + mlogb, _ = Mlogb.objects.get_or_create(**add_dict_2, defaults=ddict) + if need_mdfect: + mlogb_defect_objects = [ + MlogbDefect(**{**item, "mlogb": mlogb, "id": idWorker.get_id()}) + for item in mlogdefect if item["count"] > 0 + ] + if mlogb_defect_objects: + MlogbDefect.objects.bulk_create(mlogb_defect_objects) + mlogb.cal_count_notok() return instance def update(self, instance, validated_data): @@ -396,6 +423,7 @@ class MlogSerializer(CustomModelSerializer): raise ParseError('不支持的填写类型') validated_data.pop('mtask', None) validated_data.pop('mgroup', None) + mlogdefect = validated_data.pop('mlogdefect', []) if instance.mtask: validated_data.pop('handle_date', None) # validated_data.pop('handle_user', None) @@ -423,6 +451,7 @@ class MlogSerializer(CustomModelSerializer): minx.count_use = instance.count_use minx.count_break = instance.count_break minx.count_pn_jgqbl = instance.count_pn_jgqbl + minx.qct = instance.qct minx.save() Mlogb.objects.filter(mlog=instance, material_in__isnull=False).exclude(id=minx.id).delete() @@ -452,11 +481,24 @@ class MlogSerializer(CustomModelSerializer): if wm_in: mox.batch_ofrom = wm_in.batch mox.material_ofrom = wm_in.material_ofrom - for f in Mlogb._meta.fields: - if 'count_n_' in f.name: - setattr(mox, f.name, getattr(instance, f.name)) + need_mdefect=False + if instance.qct or mlogdefect: + need_mdefect = True + else: + for f in Mlogb._meta.fields: + if 'count_n_' in f.name: + setattr(mox, f.name, getattr(instance, f.name)) mox.save() Mlogb.objects.filter(mlog=instance, material_out__isnull=False).exclude(id=mox.id).delete() + if need_mdefect: + MlogbDefect.objects.filter(mlogb__mlog=instance).delete() + mlogb_defect_objects = [ + MlogbDefect(**{**item, "mlogb": mox, "id": idWorker.get_id()}) + for item in mlogdefect if item["count"] > 0 + ] + if mlogb_defect_objects: + MlogbDefect.objects.bulk_create(mlogb_defect_objects) + mox.cal_count_notok() return instance def validate(self, attrs): @@ -565,6 +607,8 @@ class MlogInitSerializer(CustomModelSerializer): raise ParseError('外协必须选择外协单位') if attrs.get('work_end_time', None): attrs['handle_date'] = localdate(attrs['work_end_time']) + if attrs["material_out"]: + attrs["qct"] = Qct.get(attrs["material_out"], "process") return attrs class MlogChangeSerializer(CustomModelSerializer): @@ -688,45 +732,89 @@ class MlogbwCreateUpdateSerializer(CustomModelSerializer): return mlogbw class MlogbOutUpdateSerializer(CustomModelSerializer): + mlogbdefect = MlogbDefectSerializer(many=True, required=False) + class Meta: model = Mlogb fields = "__all__" read_only_fields = EXCLUDE_FIELDS_BASE + ['mlog', 'mtask', 'wm_in', 'material_in', 'material_out', - 'count_use', 'count_break', 'count_pn_jgqbl'] + 'count_use', 'count_break', 'count_pn_jgqbl', 'mlogbdefect', "qct"] + + # def create(self, validated_data): + # material_out:Material = validated_data["material_out"] + # mlogbdefect = validated_data.pop("mlogbdefect", []) + # with transaction.atomic(): + # ins = super().create(validated_data) + # if mlogbdefect and material_out.tracking == Material.MA_TRACKING_BATCH: + # count_notok = 0 + # mlogbdefect_new = [item for item in mlogbdefect if item["count"] > 0] + # for item in mlogbdefect_new: + # defect:Defect = item["defect"] + # MlogbDefect.objects.create(mlogb=ins, **item) + # if defect.cate == Defect.DEFECT_NOTOK: + # count_notok +=1 + # ins.count_notok = count_notok + # ins.count_ok = ins.count_real - ins.count_notok + # ins.save() + # else: + # raise ParseError("mlogbdefect仅支持批次件") + # return ins + @transaction.atomic + def update(self, instance, validated_data): + mlogbdefect = validated_data.pop("mlogbdefect", []) + with transaction.atomic(): + ins:Mlogb = super().update(instance, validated_data) + if (ins.qct or mlogbdefect) and ins.material_out.tracking == Material.MA_TRACKING_BATCH: + MlogbDefect.objects.filter(mlogb=ins).delete() + mlogb_defect_objects = [ + MlogbDefect(**{**item, "mlogb": ins, "id": idWorker.get_id()}) + for item in mlogbdefect if item["count"] > 0 + ] + if mlogb_defect_objects: + MlogbDefect.objects.bulk_create(mlogb_defect_objects) + ins.cal_count_notok() + return ins def validate(self, attrs): - count_notok_json = attrs.get('count_notok_json', []) - # count_notok_json字段处理 - if count_notok_json: - # 先置0字段 + mlogbdefect = attrs.get("mlogbdefect", []) + if mlogbdefect: + attrs.pop("count_notok_json", None) for i in attrs: if 'count_n_' in i: - i = 0 - count_notok_dict = {} - for item in count_notok_json: - notok = item['notok'] - count = item['count'] - full_notok = f'count_n_{notok}' - if not hasattr(Mlogb, full_notok): - raise ParseError(f'{notok}-该不合格项不存在') - if full_notok in count_notok_dict: - count_notok_dict[full_notok] = count_notok_dict[full_notok] + count - else: - count_notok_dict[full_notok] = count - for k, v in count_notok_dict.items(): - attrs[k] = v - - count_notok = 0 - for i in attrs: - if 'count_n_' in i: - if not hasattr(Mlogb, i): - raise ParseError(f'{i}不存在') - count_notok = count_notok + attrs[i] - attrs['count_notok'] = count_notok - if attrs['count_real'] >= attrs['count_ok'] + attrs['count_notok']: - pass + attrs.pop(i, None) else: - raise ParseError('生产数量不能小于合格数量') + count_notok_json = attrs.get('count_notok_json', []) + # count_notok_json字段处理 + if count_notok_json: + # 先置0字段 + for i in attrs: + if 'count_n_' in i: + i = 0 + count_notok_dict = {} + for item in count_notok_json: + notok = item['notok'] + count = item['count'] + full_notok = f'count_n_{notok}' + if not hasattr(Mlogb, full_notok): + raise ParseError(f'{notok}-该不合格项不存在') + if full_notok in count_notok_dict: + count_notok_dict[full_notok] = count_notok_dict[full_notok] + count + else: + count_notok_dict[full_notok] = count + for k, v in count_notok_dict.items(): + attrs[k] = v + + count_notok = 0 + for i in attrs: + if 'count_n_' in i: + if not hasattr(Mlogb, i): + raise ParseError(f'{i}不存在') + count_notok = count_notok + attrs[i] + attrs['count_notok'] = count_notok + if attrs['count_real'] >= attrs['count_ok'] + attrs['count_notok']: + pass + else: + raise ParseError('生产数量不能小于合格数量') return attrs class MlogRevertSerializer(serializers.Serializer): @@ -818,7 +906,7 @@ class HandoverSerializer(CustomModelSerializer): # raise ParseError(f'第{ind+1}物料与交接部门不一致') if attrs["material"] != wm.material: raise ParseError(f'第{ind+1}物料与交接物料不一致') - if (wm.notok_sign or wm.defect) and attrs['type'] in [Handover.H_NORMAL, Handover.H_TEST]: + if wm.state != WMaterial.WM_OK and attrs['type'] in [Handover.H_NORMAL, Handover.H_TEST]: raise ParseError(f'第{ind+1}物料不合格,不能进行正常/检验交接') if wm.count_xtest is not None: raise ParseError(f'第{ind+1}物料检验中,不能进行交接') diff --git a/apps/wpm/services.py b/apps/wpm/services.py index e5332027..8ac20a0c 100644 --- a/apps/wpm/services.py +++ b/apps/wpm/services.py @@ -11,7 +11,7 @@ from apps.system.models import User from apps.pm.models import Mtask from apps.mtm.models import Mgroup, Shift, Material, Route, RoutePack, Team, Srule -from .models import SfLog, WMaterial, Mlog, Mlogb, Mlogbw, Handover, Handoverb, Handoverbw +from .models import SfLog, WMaterial, Mlog, Mlogb, Mlogbw, Handover, Handoverb, Handoverbw, MlogbDefect from apps.mtm.services_2 import cal_material_count from apps.wf.models import Ticket from apps.utils.thread import MyThread @@ -170,6 +170,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]): for mi in m_ins.all(): m_ins_list.append((mi.material_in, mi.batch, mi.count_use, mi)) if mi.count_pn_jgqbl > 0: + raise ParseError("暂不支持加工前不良") m_ins_bl_list.append((mi.material_in, mi.batch, mi.count_pn_jgqbl, mi)) else: m_ins_list = [(material_in, mlog.batch, mlog.count_use, mlog)] @@ -232,29 +233,34 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]): process = mgroup.process into_wm_mgroup = process.into_wm_mgroup need_store_notok = process.store_notok - m_outs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False) + mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False) stored_notok = need_store_notok stored_mgroup = need_store_notok - if m_outs.exists(): - m_outs_list = [(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok, mlog.count_real_eweight, None, mo) for mo in m_outs.all()] + if mlogb_out_qs.exists(): + m_outs_list = [(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo) for mo in mlogb_out_qs.all()] if need_store_notok: - for item in m_outs: - if item.material_out.tracking == Material.MA_TRACKING_SINGLE: - # 获取所有主要的不合格项 - bw_qs = Mlogbw.objects.filter(mlogb=item) - defectIds= Ftest.objects.filter(mlogbw_ftest__in=bw_qs).exclude(defect_main=None).values_list("defect_main__id", flat=True).distinct() - defects_map = {d.id: d for d in Defect.objects.filter(id__in=defectIds)} - # 过滤并统计相关数据 - filtered_bw_qs = bw_qs.filter( - ftest__defect_main__id__in=defects_map.keys() - ).values('ftest__defect_main__id').annotate(xcount=Count('id')) - # 整理结果 - for defect_data in filtered_bw_qs: - defect_id = defect_data['ftest__defect_main__id'] - xcount = defect_data['xcount'] - if xcount > 0: - defect = defects_map[defect_id] - m_outs_list.append((item.material_out, item.batch, xcount, 0, defect, item)) + for item in mlogb_out_qs: + mbd_qs = MlogbDefect.objects.filter(mlogb=item) + if item.qct is not None or mbd_qs.exists(): + if item.material_out.tracking == Material.MA_TRACKING_SINGLE: + Mlogbw.cal_count_notok(item) + for itemx in MlogbDefect.objects.filter(mlogb=item): + m_outs_list.append((item.material_out, item.batch, itemx.count, 0, itemx.defect, item)) + # # 获取所有主要的不合格项/先暂时保留 + # bw_qs = Mlogbw.objects.filter(mlogb=item) + # defectIds= Ftest.objects.filter(mlogbw_ftest__in=bw_qs).exclude(defect_main=None).values_list("defect_main__id", flat=True).distinct() + # defects_map = {d.id: d for d in Defect.objects.filter(id__in=defectIds)} + # # 过滤并统计相关数据 + # filtered_bw_qs = bw_qs.filter( + # ftest__defect_main__id__in=defects_map.keys() + # ).values('ftest__defect_main__id').annotate(xcount=Count('id')) + # # 整理结果 + # for defect_data in filtered_bw_qs: + # defect_id = defect_data['ftest__defect_main__id'] + # xcount = defect_data['xcount'] + # if xcount > 0: + # defect = defects_map[defect_id] + # m_outs_list.append((item.material_out, item.batch, xcount, 0, defect, item)) else: for f in Mlogb._meta.fields: if 'count_n_' in f.name and getattr(item, f.name) > 0: @@ -274,9 +280,13 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]): if process.type == Process.PRO_PROD: wm_state = WMaterial.WM_REPAIRED # 返修只有返修完成品 elif process.type == Process.PRO_TEST: - wm_state = WMaterial.WM_OK if notok_sign_or_defect is None else WMaterial.WM_NOTOK + wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or ( + isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B] + ) else WMaterial.WM_NOTOK else: - wm_state = WMaterial.WM_OK if notok_sign_or_defect is None else WMaterial.WM_NOTOK + wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or ( + isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B] + ) else WMaterial.WM_NOTOK lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None, 'notok_sign': None, 'defect': None, 'state': wm_state} if isinstance(notok_sign_or_defect, Defect): @@ -357,29 +367,35 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]): if material_out or is_fix: # 产物退回 # 有多个产物的情况 # 需要考虑不合格品退回的情况 - m_outs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False) - if m_outs.exists(): + mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False) + if mlogb_out_qs.exists(): m_outs_list = [ - (mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok, mlog.count_real_eweight, None, mo) - for mo in m_outs.all()] + (mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo) + for mo in mlogb_out_qs.all()] if stored_notok: - for item in m_outs: - if item.material_out.tracking == Material.MA_TRACKING_SINGLE: - # 获取所有主要的不合格项 - bw_qs = Mlogbw.objects.filter(mlogb=item) - defectIds= Ftest.objects.filter(mlogbw_ftest__in=bw_qs).exclude(defect_main=None).values_list("defect_main__id", flat=True).distinct() - defects_map = {d.id: d for d in Defect.objects.filter(id__in=defectIds)} - # 过滤并统计相关数据 - filtered_bw_qs = bw_qs.filter( - ftest__defect_main__id__in=defects_map.keys() - ).values('ftest__defect_main__id').annotate(xcount=Count('id')) - # 整理结果 - for defect_data in filtered_bw_qs: - defect_id = defect_data['ftest__defect_main__id'] - xcount = defect_data['xcount'] - if xcount > 0: - defect = defects_map[defect_id] - m_outs_list.append((item.material_out, item.batch, xcount, 0, defect, item)) + for item in mlogb_out_qs: + mbd_qs = MlogbDefect.objects.filter(mlogb=item) + if item.qct is not None or mbd_qs.exists(): + if item.material_out.tracking == Material.MA_TRACKING_SINGLE: + Mlogbw.cal_count_notok(item) + for itemx in MlogbDefect.objects.filter(mlogb=item): + m_outs_list.append((item.material_out, item.batch, itemx.count, 0, itemx.defect, item)) + # if item.material_out.tracking == Material.MA_TRACKING_SINGLE: + # # 获取所有主要的不合格项 + # bw_qs = Mlogbw.objects.filter(mlogb=item) + # defectIds= Ftest.objects.filter(mlogbw_ftest__in=bw_qs).exclude(defect_main=None).values_list("defect_main__id", flat=True).distinct() + # defects_map = {d.id: d for d in Defect.objects.filter(id__in=defectIds)} + # # 过滤并统计相关数据 + # filtered_bw_qs = bw_qs.filter( + # ftest__defect_main__id__in=defects_map.keys() + # ).values('ftest__defect_main__id').annotate(xcount=Count('id')) + # # 整理结果 + # for defect_data in filtered_bw_qs: + # defect_id = defect_data['ftest__defect_main__id'] + # xcount = defect_data['xcount'] + # if xcount > 0: + # defect = defects_map[defect_id] + # m_outs_list.append((item.material_out, item.batch, xcount, 0, defect, item)) else: for f in Mlogb._meta.fields: if 'count_n_' in f.name and getattr(item, f.name) > 0: @@ -398,9 +414,13 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]): if process.type == Process.PRO_PROD: wm_state = WMaterial.WM_REPAIRED else: # 检验工序正常生成 - wm_state = WMaterial.WM_OK if notok_sign_or_defect is None else WMaterial.WM_NOTOK + wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or ( + isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B] + ) else WMaterial.WM_NOTOK else: - wm_state = WMaterial.WM_OK if notok_sign_or_defect is None else WMaterial.WM_NOTOK + wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or ( + isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B] + ) else WMaterial.WM_NOTOK lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None, 'notok_sign': None, 'defect': None, 'state': wm_state} if isinstance(notok_sign_or_defect, Defect): lookup['defect'] = notok_sign_or_defect @@ -446,6 +466,7 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]): for mi in m_ins.all(): m_ins_list.append((mi.material_in, mi.batch, mi.count_use, mi.wm_in, mi)) if mi.count_pn_jgqbl > 0: + raise ParseError("暂不支持加工前不良") m_ins_bl_list.append((mi.material_in, mi.batch, mi.count_pn_jgqbl, mi)) else: m_ins_list = [(material_in, mlog.batch, mlog.count_use, mlog.wm_in, mlog)] diff --git a/apps/wpm/services_2.py b/apps/wpm/services_2.py index 355976cf..5911613f 100644 --- a/apps/wpm/services_2.py +++ b/apps/wpm/services_2.py @@ -31,7 +31,7 @@ def get_alldata_with_batch_gx(batch: str): """ 光芯获取batch的统计数据 """ - pass + return None, None def get_alldata_with_batch(batch: str): """ @@ -74,7 +74,10 @@ def get_alldata_with_batch(batch: str): data["棒料成型_出料人"] = ";".join([item.name for item in data["棒料成型_出料人"]]) data["棒料成型_切料人"] = list(set(data["棒料成型_切料人"])) data["棒料成型_切料人"] = ";".join([item.name for item in data["棒料成型_切料人"]]) - data["棒料成型_合格率"] = round((data["棒料成型_count_ok"] * 100/ data["棒料成型_count_real"]), 1) + try: + data["棒料成型_合格率"] = round((data["棒料成型_count_ok"] * 100/ data["棒料成型_count_real"]), 1) + except ZeroDivisionError: + data["棒料成型_合格率"] = 0 # 管料成型数据 mgroup_glcx = Mgroup.objects.get(name="管料成型") @@ -126,7 +129,8 @@ def get_alldata_with_batch(batch: str): data["七车间入库_车间执行人"] = list(set(data["七车间入库_车间执行人"])) data["七车间入库_车间执行人"] = ";".join([item.name for item in data["七车间入库_车间执行人"]]) data["七车间入库_仓库执行人"] = list(set(data["七车间入库_仓库执行人"])) - data["七车间入库_仓库执行人"].remove(None) + if None in data["七车间入库_仓库执行人"]: + data["七车间入库_仓库执行人"].remove(None) data["七车间入库_仓库执行人"] = ";".join([item.name for item in data["七车间入库_仓库执行人"]]) # 十车间入库检验 diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 70ebafd8..de8e883f 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -16,7 +16,7 @@ from apps.utils.mixins import CustomListModelMixin, BulkCreateModelMixin, BulkDe from .filters import StLogFilter, SfLogFilter, WMaterialFilter, MlogFilter, HandoverFilter, MlogbFilter, BatchStFilter from .models import (SfLog, SfLogExp, StLog, WMaterial, Mlog, Handover, Mlogb, - Mlogbw, AttLog, OtherLog, Fmlog, BatchSt) + Mlogbw, AttLog, OtherLog, Fmlog, BatchSt, MlogbDefect) from .serializers import (SflogExpSerializer, SfLogSerializer, StLogSerializer, WMaterialSerializer, MlogRevertSerializer, MlogSerializer, MlogRelatedSerializer, DeptBatchSerializer, HandoverSerializer, @@ -30,10 +30,11 @@ from .services import mlog_submit, handover_submit, mlog_revert from apps.wpm.services import mlog_submit_validate, generate_new_batch from apps.wf.models import State from apps.wpmw.models import Wpr -from apps.qm.models import QctMat, Ftest +from apps.qm.models import Qct, Ftest, TestItem from apps.enm.models import EnStat from django.db.models import Q from apps.utils.tools import convert_ordereddict +from django.db.models import Count # Create your views here. @@ -180,7 +181,20 @@ class MlogViewSet(CustomModelViewSet): 'material_in__number', 'material_in__specification', 'batch', 'material_in__model', 'material_out__name', 'material_out__number', 'material_out__specification', 'material_out__model',] + def add_info_for_item(self, data): + if data.get("oinfo_json", {}): + czx_dict = dict(TestItem.objects.filter(id__in=data.get("oinfo_json", {}).keys()).values_list('id', 'name')) + data["oinfo_json_"] = {czx_dict.get(k, k): v for k, v in data.get("oinfo_json", {}).items()} + return data + def add_info_for_list(self, data): + czx_dict = {} + for item in data: + czx_dict.update(item.get("oinfo_json", {})) + czx_dict = dict(TestItem.objects.filter(id__in=czx_dict.keys()).values_list('id', 'name')) + for item in data: + if item.get("oinfo_json", None): + item["oinfo_json_"] = {czx_dict.get(k, k): v for k, v in item.get("oinfo_json", {}).items()} if self.request.query_params.get('with_mlogb', False): data_dict = {item['id']: {**item, "mlogb_full": [], "mlogb": []} for item in data} mlogb_qs = Mlogb.objects.filter(mlog__id__in=data_dict.keys()).select_related("material_in", "material_out").order_by("create_time") @@ -191,7 +205,7 @@ class MlogViewSet(CustomModelViewSet): data_dict[item_dict["mlog"]]["mlogb_full"].append(item_dict) if item.get("material_out", None): data_dict[item_dict["mlog"]]["mlogb"].append(item_dict) - return list(data_dict.values()) + data = list(data_dict.values()) return data @transaction.atomic @@ -591,13 +605,14 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust d_count_real = mlogbin.count_use d_count_ok = mlogbin.count_use # 找寻质检表 - if material_out.tracking == Material.MA_TRACKING_SINGLE: - if is_fix and mgroup.process.type == Process.PRO_PROD: - # 如果是生产返修,则忽略质检 - pass - else: - qctmat = QctMat.objects.filter(material=material_out, qct__is_deleted=False).order_by("-create_time").first() - mlogbout.qct = qctmat.qct if qctmat else None + if is_fix and mgroup.process.type == Process.PRO_PROD: + # 如果是生产返修,则忽略质检 + pass + elif mlogbout.qct is None: + mlogbout.qct = Qct.get(mlogbout.material_out, "process") if mlog.qct is None else mlog.qct + if mlogbout.qct is not None and mlog.qct is None: + mlog.qct = mlogbout.qct + mlog.save(update_fields=["qct"]) mlogbout.count_real = d_count_real mlogbout.count_ok = d_count_ok mlogbout.save() @@ -671,23 +686,12 @@ class MlogbwViewSet(CustomModelViewSet): raise ParseError('请指定所属消耗/产出明细') return super().filter_queryset(queryset) - def cal_mlogb_count(self, mlogb): - count = Mlogbw.objects.filter(mlogb=mlogb).count() - # 此处先不管检验问题 - if mlogb.material_in: - mlogb.count_use = count - elif mlogb.material_out: - mlogb.count_real = count - mlogb.count_notok = Mlogbw.objects.filter(mlogb=mlogb, ftest__is_ok=False).count() - mlogb.count_ok = count - mlogb.count_notok - mlogb.save() - @transaction.atomic def perform_create(self, serializer): ins:Mlogbw = serializer.save() route:Route = ins.mlogb.mlog.route mlogb:Mlogb = ins.mlogb - self.cal_mlogb_count(mlogb) + Mlogbw.cal_count_notok(mlogb) # 如果是输入且输出追踪到个,需同步创建 material_in:Material = mlogb.material_in if material_in is not None: @@ -704,7 +708,7 @@ class MlogbwViewSet(CustomModelViewSet): @transaction.atomic def perform_update(self, serializer): mlogbw = serializer.save() - self.cal_mlogb_count(mlogbw.mlogb) + Mlogbw.cal_count_notok(mlogbw.mlogb) @transaction.atomic def perform_destroy(self, instance:Mlogbw): @@ -725,4 +729,4 @@ class MlogbwViewSet(CustomModelViewSet): mbws = Mlogbw.objects.filter(Q(wpr=instance.wpr)|Q(number__contains=instance.number), mlogb=mlogb_to) Ftest.objects.filter(id__in=mbws.values_list('ftest__id', flat=True)).delete() mbws.delete() - self.cal_mlogb_count(mlogb_to) + Mlogbw.cal_count_notok(mlogb)