diff --git a/apps/mtm/migrations/0016_auto_20231016_1648.py b/apps/mtm/migrations/0016_auto_20231016_1648.py new file mode 100644 index 00000000..d9c74921 --- /dev/null +++ b/apps/mtm/migrations/0016_auto_20231016_1648.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.12 on 2023-10-16 08:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0015_mgroup_need_enm'), + ] + + operations = [ + migrations.AddField( + model_name='route', + name='material_in', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='route_material_in', to='mtm.material', verbose_name='主要输入物料'), + ), + migrations.AddField( + model_name='route', + name='material_out', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='route_material_out', to='mtm.material', verbose_name='主要输出物料'), + ), + migrations.AlterField( + model_name='route', + name='material', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='关联产品'), + preserve_default=False, + ), + migrations.AlterField( + model_name='route', + name='process', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.process', verbose_name='工序'), + preserve_default=False, + ), + ] diff --git a/apps/mtm/models.py b/apps/mtm/models.py index b307cc71..34dbd872 100644 --- a/apps/mtm/models.py +++ b/apps/mtm/models.py @@ -1,6 +1,7 @@ from django.db import models from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel from django.db.models import Subquery, OuterRef +from rest_framework.exceptions import ValidationError class Process(CommonBModel): @@ -140,11 +141,15 @@ class Route(CommonAModel): 工艺路线 """ material = models.ForeignKey( - Material, verbose_name='关联产品', on_delete=models.CASCADE, null=True, blank=True) + Material, verbose_name='关联产品', on_delete=models.CASCADE) process = models.ForeignKey( - Process, verbose_name='工序', on_delete=models.CASCADE, null=True, blank=True) + Process, verbose_name='工序', on_delete=models.CASCADE) sort = models.PositiveSmallIntegerField('顺序', default=1) is_autotask = models.BooleanField('是否自动排产', default=False) + material_in = models.ForeignKey( + Material, verbose_name='主要输入物料', on_delete=models.CASCADE, related_name='route_material_in', null=True, blank=True) + material_out = models.ForeignKey( + Material, verbose_name='主要输出物料', on_delete=models.CASCADE, related_name='route_material_out', null=True, blank=True) out_rate = models.FloatField('出材率', default=100, null=True, blank=True) @staticmethod @@ -153,6 +158,11 @@ class Route(CommonAModel): 返回工艺路线带车间 """ kwargs = {'material': material} + # 校验工艺路线是否正常 + rq = Route.objects.filter( + **kwargs).order_by('sort', 'process__sort', 'create_time') + if rq.first().material_in is None or rq.last().material_out is None or rq.last().material_out != rq.last().material: + raise ValidationError('首步缺少输入/最后一步缺少输出') if autotask: kwargs['is_autotask'] = True - return Route.objects.annotate(mgroups=Subquery(Mgroup.objects.filter(process=OuterRef('pk')))).filter(**kwargs).order_by('sort', 'process__sort' 'create_time') + return Route.objects.annotate(mgroups=Subquery(Mgroup.objects.filter(process=OuterRef('pk')))).filter(**kwargs).order_by('sort', 'process__sort', 'create_time') diff --git a/apps/mtm/serializers.py b/apps/mtm/serializers.py index 4b959193..819884bf 100644 --- a/apps/mtm/serializers.py +++ b/apps/mtm/serializers.py @@ -87,7 +87,26 @@ class RouteSerializer(CustomModelSerializer): raise ValidationError('请选择最终产品') return super().validate(attrs) + def gen_material_out(self, instance): + instance.material_out, _ = Material.objects.get_or_create(type=Material.MA_TYPE_HALFGOOD, parent=instance.material, process=instance.process, + defaults={'parent': instance.material, 'process': instance.process, + 'is_hidden': True, 'name': instance.material.name, + 'number': instance.material.number, + 'specification': instance.material.specification, + 'type': Material.MA_TYPE_HALFGOOD, + 'create_by': self.request.user, + 'update_by': self.request.user, + }) + instance.save() + + def create(self, validated_data): + instance = super().create(validated_data) + self.gen_material_out() + return instance + def update(self, instance, validated_data): validated_data.pop('material', None) validated_data.pop('process', None) - return super().update(instance, validated_data) + instance = super().update(instance, validated_data) + self.gen_material_out() + return instance diff --git a/apps/pm/filters.py b/apps/pm/filters.py index 875cf426..64fcfa29 100644 --- a/apps/pm/filters.py +++ b/apps/pm/filters.py @@ -18,9 +18,9 @@ class MtaskFilter(filters.FilterSet): "mgroup__cate": ["exact"], "mgroup__process": ["exact"], "mgroup__process__cate": ["exact"], - "material": ["exact"], - "material__type": ["exact"], - "material__is_hidden": ["exact"], + "material_out": ["exact"], + "material_out__type": ["exact"], + "material_out__is_hidden": ["exact"], "parent": ["exact", "isnull"] } diff --git a/apps/pm/migrations/0006_auto_20231016_1648.py b/apps/pm/migrations/0006_auto_20231016_1648.py new file mode 100644 index 00000000..269c4b20 --- /dev/null +++ b/apps/pm/migrations/0006_auto_20231016_1648.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.12 on 2023-10-16 08:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0016_auto_20231016_1648'), + ('pm', '0005_alter_mtask_state'), + ] + + operations = [ + migrations.RemoveField( + model_name='mtask', + name='material', + ), + migrations.RemoveField( + model_name='mtask', + name='material_before', + ), + migrations.AddField( + model_name='mtask', + name='material_in', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='mtask_material_in', to='mtm.material', verbose_name='领用物'), + ), + migrations.AddField( + model_name='mtask', + name='material_out', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='mtask_material_out', to='mtm.material', verbose_name='产物'), + preserve_default=False, + ), + ] diff --git a/apps/pm/models.py b/apps/pm/models.py index df8ea632..67c016f9 100644 --- a/apps/pm/models.py +++ b/apps/pm/models.py @@ -24,10 +24,10 @@ class Mtask(CommonADModel): number = models.CharField('编号', max_length=50, unique=True) mgroup = models.ForeignKey( Mgroup, verbose_name='工段', on_delete=models.CASCADE) - material_before = models.ForeignKey( - Material, verbose_name='领用物', on_delete=models.CASCADE, related_name='mtask_material_before', null=True, blank=True) - material = models.ForeignKey( - Material, verbose_name='产物', on_delete=models.CASCADE, related_name='mtask_material') + material_in = models.ForeignKey( + Material, verbose_name='领用物', on_delete=models.CASCADE, related_name='mtask_material_in', null=True, blank=True) + material_out = models.ForeignKey( + Material, verbose_name='产物', on_delete=models.CASCADE, related_name='mtask_material_out') count = models.PositiveIntegerField('任务数', default=1) count_real = models.PositiveIntegerField('实际生产数', default=0) count_ok = models.PositiveIntegerField('合格数', default=0) diff --git a/apps/pm/services.py b/apps/pm/services.py index 7379a895..db539c22 100644 --- a/apps/pm/services.py +++ b/apps/pm/services.py @@ -48,12 +48,16 @@ class PmService: rela_days = (end_date - start_date).days + 1 # 获取工艺路线 rqs = Route.get_routes(product) - last_route = rqs.last() # 最后一步是产生产品 # 创建父任务 for ind, val in enumerate(rqs): - material_before = None - if ind > 0: - material_before = rqs[ind-1].material + if val.material_out: + halfgood = val.material_out + else: + raise ParseError(f'第{ind+1}步-无输出物料') + if val.material_in: + material_before = val.material_in + elif ind > 0: + material_before = rqs[ind-1].material_out if val.is_autotask: # 找到工段 mgroups = val.mgroups @@ -61,21 +65,9 @@ class PmService: if mgroups_count == 1: mgroup = mgroups.first() elif mgroups_count == 0: - raise ParseError(f'{val.name}-工段不存在!') + raise ParseError(f'第{ind+1}步-工段不存在!') else: # 后面可能会指定车间 - raise ParseError(f'{val.name}-工段存在多个!') - # 找到存在的半成品 - halfgood, _ = Material.objects.get_or_create(type=Material.MA_TYPE_HALFGOOD, parent=product, mgroup=mgroup, - defaults={'parent': product, 'mgroup': mgroup, - 'is_hidden': True, 'name': product.name, - 'number': product.number, - 'specification': product.specification, - 'type': Material.MA_TYPE_HALFGOOD, - 'create_by': user, - 'update_by': user, - }) - if val == last_route: - halfgood = product + raise ParseError(f'第{ind+1}步-工段存在多个!') task_count = count if val.out_rate: if val.out_rate > 1: diff --git a/apps/pm/views.py b/apps/pm/views.py index 913055d1..9cdf6c0a 100644 --- a/apps/pm/views.py +++ b/apps/pm/views.py @@ -23,7 +23,7 @@ class MtaskViewSet(CustomModelViewSet): queryset = Mtask.objects.all() serializer_class = MtaskSerializer filterset_class = MtaskFilter - select_related_fields = ['material', 'mgroup'] + select_related_fields = ['material_in', 'material_out', 'mgroup'] ordering_fields = ['start_date', 'mgroup__process__sort'] ordering = ['mgroup__process__sort', '-start_date', '-create_time'] diff --git a/apps/wpm/services.py b/apps/wpm/services.py index 01c925f8..d1383fdd 100644 --- a/apps/wpm/services.py +++ b/apps/wpm/services.py @@ -110,27 +110,27 @@ def mlog_confirm(mlog: Mlog): """ mtask = mlog.mtask belong_dept = mtask.belong_dept - material = mtask.material - material_before = mtask.material_before - if material_before and material_before.is_hidden is False: # 需要进行车间库存管理 + material_out = mtask.material_out + material_in = mtask.material_in + if material_in and material_in.is_hidden is False: # 需要进行车间库存管理 # 需要判断领用数是否合理 material_has_qs = WMaterial.objects.filter( - batch=mlog.batch, material=material_before, belong_dept=belong_dept) + batch=mlog.batch, material=material_in, belong_dept=belong_dept) count_x = material_has_qs.count() if count_x == 1: material_has = material_has_qs.first() elif count_x == 0: - raise ParseError(f'{material_before.name}-{mlog.batch}-批次库存不存在!') + raise ParseError(f'{material_in.name}-{mlog.batch}-批次库存不存在!') else: - raise ParseError(f'{material_before.name}-{mlog.batch}-存在多个相同批次!') + raise ParseError(f'{material_in.name}-{mlog.batch}-存在多个相同批次!') if mlog.count_use > material_has.count: - raise ParseError(f'{material_before.name}-{mlog.batch}-该批次车间库存不足!') + raise ParseError(f'{material_in.name}-{mlog.batch}-该批次车间库存不足!') else: material_has.count = material_has.count - mlog.count_use material_has.save() - if material.is_hidden is False: # 需要入库 - wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material, belong_dept=belong_dept, defaults={ - 'batch': mlog.batch, 'material': material, 'belong_dept': belong_dept + if material_out.is_hidden is False: # 需要入库 + wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material_out, belong_dept=belong_dept, defaults={ + 'batch': mlog.batch, 'material': material_out, 'belong_dept': belong_dept }) wmaterial.count = wmaterial.count + mlog.count_ok wmaterial.save()