diff --git a/hb_client/src/views/monitor/service.vue b/hb_client/src/views/monitor/service.vue index 8c9fcdd..b9473ff 100644 --- a/hb_client/src/views/monitor/service.vue +++ b/hb_client/src/views/monitor/service.vue @@ -66,7 +66,7 @@ - +
日志列表
diff --git a/hb_server/apps/inm/migrations/0011_rename_fifodetail_fifoitem.py b/hb_server/apps/inm/migrations/0011_rename_fifodetail_fifoitem.py new file mode 100644 index 0000000..46f6dc7 --- /dev/null +++ b/hb_server/apps/inm/migrations/0011_rename_fifodetail_fifoitem.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.6 on 2021-11-08 01:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0028_auto_20211102_1707'), + ('qm', '0003_testrecord'), + ('inm', '0010_auto_20211102_1631'), + ] + + operations = [ + migrations.RenameModel( + old_name='FIFODetail', + new_name='FIFOItem', + ), + ] diff --git a/hb_server/apps/inm/models.py b/hb_server/apps/inm/models.py index eefa002..8446014 100644 --- a/hb_server/apps/inm/models.py +++ b/hb_server/apps/inm/models.py @@ -53,11 +53,15 @@ class FIFO(CommonAModel): """ 出入库记录 """ + FIFO_TYPE_DO_OUT = 1 # 生产领料 + FIFO_TYPE_SALE_OUT = 2 + FIFO_TYPE_PUR_IN = 3 + FIFO_TYPE_DO_IN = 4 type_choices = ( - (1, '生产领料'), - (2, '销售提货'), - (3, '采购入库'), - (4, '生产入库') + (FIFO_TYPE_DO_OUT, '生产领料'), + (FIFO_TYPE_SALE_OUT, '销售提货'), + (FIFO_TYPE_PUR_IN, '采购入库'), + (FIFO_TYPE_DO_IN, '生产入库') ) type = models.IntegerField('出入库类型', default=1) is_audited = models.BooleanField('是否审核', default=False) @@ -68,9 +72,9 @@ class FIFO(CommonAModel): remark = models.CharField('备注', max_length=1000, default='') -class FIFODetail(BaseModel): +class FIFOItem(BaseModel): """ - 出入库详细记录 + 出入库详细条目 """ is_tested = models.BooleanField('是否已检测', default=False) is_testok = models.BooleanField('是否检测合格', default=False) diff --git a/hb_server/apps/inm/serializers.py b/hb_server/apps/inm/serializers.py index 0087fae..2f444d6 100644 --- a/hb_server/apps/inm/serializers.py +++ b/hb_server/apps/inm/serializers.py @@ -1,9 +1,10 @@ from rest_framework import serializers -from apps.inm.models import FIFO, FIFODetail, IProduct, MaterialBatch, WareHouse,Inventory +from apps.inm.models import FIFO, FIFOItem, IProduct, MaterialBatch, WareHouse,Inventory from apps.system.serializers import UserSimpleSerializer from apps.mtm.serializers import MaterialSimpleSerializer +from django.db import transaction class WareHouseSerializer(serializers. ModelSerializer): create_by_=UserSimpleSerializer('create_by', read_only=True) @@ -42,10 +43,10 @@ class FIFOListSerializer(serializers.ModelSerializer): model=FIFO fields = '__all__' -class FIFODetailSerializer(serializers.ModelSerializer): +class FIFOItemSerializer(serializers.ModelSerializer): material_= MaterialSimpleSerializer(source='material', read_only=True) class Meta: - model=FIFODetail + model= FIFOItem fields = '__all__' class IProductInPurSerializer(serializers.ModelSerializer): @@ -56,7 +57,7 @@ class IProductInPurSerializer(serializers.ModelSerializer): class FIFODetailInPurSerializer(serializers.ModelSerializer): details = IProductInPurSerializer(many=True, required=False) class Meta: - model = FIFODetail + model = FIFOItem fields = ['material', 'count', 'batch', 'details'] class MaterialBatchQuerySerializer(serializers.Serializer): @@ -91,28 +92,29 @@ class FIFOInPurSerializer(serializers.ModelSerializer): pass # 创建采购入库 - validated_data['type'] = 3 - obj = FIFO(**validated_data) - obj.save() - for i in details: - if 'details' in i: - p_details = i.pop('details') - if len(p_details) != i['count']: - raise serializers.ValidationError('数目对不上') + with transaction.atomic(): + validated_data['type'] = FIFO.FIFO_TYPE_PUR_IN + obj = FIFO(**validated_data) + obj.save() + for i in details: + if 'details' in i: + p_details = i.pop('details') + if len(p_details) != i['count']: + raise serializers.ValidationError('数目对不上') + else: + i['fifo'] = obj + fifod = FIFOItem.objects.create(**i) + p_list = [] + for x in p_details: + x['state'] = 1 + x['material'] = i['material'] + x['warehouse'] = validated_data['warehouse'] + x['batch'] = i['batch'] + x['fifos'] = [fifod.id] + p_list.append(IProduct(**x)) + IProduct.objects.bulk_create(p_list) else: i['fifo'] = obj - fifod = FIFODetail.objects.create(**i) - p_list = [] - for x in p_details: - x['state'] = 1 - x['material'] = i['material'] - x['warehouse'] = validated_data['warehouse'] - x['batch'] = i['batch'] - x['fifos'] = [fifod.id] - p_list.append(IProduct(**x)) - IProduct.objects.bulk_create(p_list) - else: - i['fifo'] = obj - FIFODetail.objects.create(**i) + FIFOItem.objects.create(**i) return obj diff --git a/hb_server/apps/inm/signals.py b/hb_server/apps/inm/signals.py index c4b74ee..57235a6 100644 --- a/hb_server/apps/inm/signals.py +++ b/hb_server/apps/inm/signals.py @@ -1,7 +1,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver -from apps.inm.models import Inventory, MaterialBatch, FIFO, FIFODetail +from apps.inm.models import Inventory, MaterialBatch, FIFO, FIFOItem def update_inm(instance:FIFO, type:int=1): @@ -9,9 +9,9 @@ def update_inm(instance:FIFO, type:int=1): 更新库存(正反) """ warehouse = instance.warehouse - if instance.type in [3]: # 采购入库 + if instance.type in [FIFO.FIFO_TYPE_PUR_IN]: # 采购入库 # 更新相关表 - for i in FIFODetail.objects.filter(fifo=instance): + for i in FIFOItem.objects.filter(fifo=instance): material = i.material o1, _ = Inventory.objects.get_or_create(material=material, warehouse=warehouse, \ defaults={'material':material, 'warehouse':warehouse, 'count':0}) @@ -23,9 +23,9 @@ def update_inm(instance:FIFO, type:int=1): o2.save() material.count = material.count + i.count material.save() - elif instance.type in [1]: # 生产领料 + elif instance.type in [FIFO.FIFO_TYPE_DO_OUT]: # 生产领料 # 更新相关表 - for i in FIFODetail.objects.filter(fifo=instance): + for i in FIFOItem.objects.filter(fifo=instance): material = i.material o1 = Inventory.objects.get(material=material, warehouse=warehouse) o1.count = o1.count - i.count diff --git a/hb_server/apps/inm/urls.py b/hb_server/apps/inm/urls.py index 4b968ff..f5d68de 100644 --- a/hb_server/apps/inm/urls.py +++ b/hb_server/apps/inm/urls.py @@ -1,6 +1,6 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.inm.views import FIFODetailViewSet, FIFOViewSet, MaterialBatchViewSet, WarehouseViewSet,InventoryViewSet +from apps.inm.views import FIFOItemViewSet, FIFOViewSet, MaterialBatchViewSet, WarehouseViewSet,InventoryViewSet from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -9,7 +9,7 @@ router.register('warehouse', WarehouseViewSet, basename='warehouse') router.register('inventory', InventoryViewSet, basename='inventory') router.register('materialbatch', MaterialBatchViewSet, basename='materialbatch') router.register('fifo', FIFOViewSet, basename='fifo'), -router.register('fifodetail', FIFODetailViewSet, basename='fifodetail') +router.register('fifodetail', FIFOItemViewSet, basename='fifodetail') urlpatterns = [ path('', include(router.urls)), ] diff --git a/hb_server/apps/inm/views.py b/hb_server/apps/inm/views.py index 0fd15d8..4d88b8f 100644 --- a/hb_server/apps/inm/views.py +++ b/hb_server/apps/inm/views.py @@ -1,16 +1,17 @@ from django.shortcuts import render from rest_framework import serializers from rest_framework.exceptions import APIException -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin +from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin from rest_framework.viewsets import GenericViewSet, ModelViewSet from apps.inm.filters import MbFilterSet -from apps.inm.models import FIFO, FIFODetail, MaterialBatch, WareHouse,Inventory -from apps.inm.serializers import FIFODetailSerializer, FIFOInPurSerializer, FIFOListSerializer, MaterialBatchQuerySerializer, MaterialBatchSerializer, WareHouseSerializer, WareHouseCreateUpdateSerializer,InventorySerializer +from apps.inm.models import FIFO, FIFOItem, MaterialBatch, WareHouse,Inventory +from apps.inm.serializers import FIFOItemSerializer, FIFOInPurSerializer, FIFOListSerializer, MaterialBatchQuerySerializer, MaterialBatchSerializer, WareHouseSerializer, WareHouseCreateUpdateSerializer,InventorySerializer from apps.inm.signals import update_inm from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action from rest_framework.response import Response +from django.db import transaction # Create your views here. class WarehouseViewSet(CreateUpdateModelAMixin, ModelViewSet): @@ -63,18 +64,23 @@ class MaterialBatchViewSet(ListModelMixin, GenericViewSet): queryset = self.queryset.filter(warehouse__id=data['warehouse'], material__id__in=data['materials']) return Response(MaterialBatchSerializer(instance=queryset, many=True).data) -class FIFODetailViewSet(ListModelMixin, GenericViewSet): +class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): """ 出入库记录详情表 """ perms_map = {'*': '*'} - queryset = FIFODetail.objects.select_related('material', 'fifo').all() - serializer_class = FIFODetailSerializer + queryset = FIFOItem.objects.select_related('material', 'fifo').all() + serializer_class = FIFOItemSerializer filterset_fields = ['material', 'fifo'] search_fields = [] ordering_fields = ['create_time'] ordering = ['-create_time'] - + + def perform_destroy(self, instance): + if instance.fifo.is_audited: + raise APIException('该出入库记录已通过审核, 无法删除') + return super().perform_destroy(instance) + class FIFOViewSet(ListModelMixin, GenericViewSet): """ 出入库记录 @@ -108,11 +114,14 @@ class FIFOViewSet(ListModelMixin, GenericViewSet): 审核通过 """ obj = self.get_object() - for i in FIFODetail.objects.filter(fifo=obj): + for i in FIFOItem.objects.filter(fifo=obj): if not i.is_testok: raise APIException('未检验通过, 不可审核') - obj.is_audited = True - obj.save() - update_inm(obj) # 更新库存 + if obj.is_audited: + raise APIException('该入库记录已审核通过') + with transaction.atomic(): + obj.is_audited = True + obj.save() + update_inm(obj) # 更新库存 return Response() \ No newline at end of file diff --git a/hb_server/apps/mtm/migrations/0029_step_type.py b/hb_server/apps/mtm/migrations/0029_step_type.py new file mode 100644 index 0000000..d370806 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0029_step_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-08 09:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0028_auto_20211102_1707'), + ] + + operations = [ + migrations.AddField( + model_name='step', + name='type', + field=models.IntegerField(choices=[(1, '普通'), (2, '分割'), (3, '结合')], default=1, verbose_name='操作类型'), + ), + ] diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index ce615ac..99a19e3 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -12,13 +12,19 @@ class Material(CommonAModel): """ 物料 """ + MA_TYPE_GOOD = 1 + MA_TYPE_HALFGOOD = 2 + MA_TYPE_MAINSO = 3 + MA_TYPE_HELPSO = 4 + MA_TYPE_TOOL = 5 + MA_TYPE_HELPTOOL = 6 type_choices=( - (1, '成品'), - (2, '半成品'), - (3, '主要原料'), - (4, '辅助材料') , - (5, '加工工具'), - (6, '辅助工装') + (MA_TYPE_GOOD, '成品'), + (MA_TYPE_HALFGOOD, '半成品'), + (MA_TYPE_MAINSO, '主要原料'), + (MA_TYPE_HELPSO, '辅助材料') , + (MA_TYPE_TOOL, '加工工具'), + (MA_TYPE_HELPTOOL, '辅助工装') ) unit_choices =( ('块', '块'), @@ -62,6 +68,15 @@ class Step(CommonAModel): """ 工序步骤 """ + STEP_TYPE_NOM = 1 + STEP_TYPE_DIV = 2 + STEP_TYPE_COMB = 3 + step_type_choices=( + (STEP_TYPE_NOM, '普通'), + (STEP_TYPE_DIV, '分割'), + (STEP_TYPE_COMB, '结合') + ) + type = models.IntegerField('操作类型', choices=step_type_choices, default=1) process = models.ForeignKey(Process, on_delete=models.CASCADE, verbose_name='所属工序', related_name='step_process') name = models.CharField('工序步骤名称', max_length=100) number = models.CharField('步骤编号', max_length=100, null=True, blank=True) @@ -80,9 +95,11 @@ class RecordForm(CommonAModel): """ 记录表格 """ + RF_TYPE_DO = 1 + RF_TYPE_TEST = 2 type_choices=( - (1, '生产记录'), - (2, '检验记录') + (RF_TYPE_DO, '生产记录'), + (RF_TYPE_TEST, '检验记录') ) name = models.CharField('表格名称', max_length=100, unique=True) type = models.IntegerField('表格类型', choices=type_choices, default=1) @@ -164,10 +181,13 @@ class SubprodctionMaterial(CommonAModel): """ 输入/输出物料/工具工装 """ + SUB_MA_TYPE_IN = 1 + SUB_MA_TYPE_OUT = 2 + SUB_MA_TYPE_TOOL = 3 type_choices=( - (1, '输入物料'), - (2, '输出物料'), - (3, '工具工装') + (SUB_MA_TYPE_IN, '输入物料'), + (SUB_MA_TYPE_OUT, '输出物料'), + (SUB_MA_TYPE_TOOL, '工具工装') ) material = models.ForeignKey(Material, verbose_name='物料', on_delete=models.CASCADE, related_name='subplan_material') is_main = models.BooleanField('是否主产出', default=False) # 以该产品完成度计算进度 diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 49f4b87..0d9b82f 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -61,6 +61,11 @@ class StepDetailSerializer(serializers.ModelSerializer): queryset = queryset.prefetch_related('equipments') return queryset +class SubProductionSimpleSerializer(serializers.ModelSerializer): + class Meta: + model = SubProduction + fields = ['id', 'name'] + class SubProductionSerializer(serializers.ModelSerializer): process_ = ProcessSimpleSerializer(source='process', read_only=True) class Meta: @@ -90,9 +95,9 @@ class InputMaterialSerializer(serializers.ModelSerializer): fields = ['count', 'sort', 'material', 'subproduction'] def create(self, validated_data): - if SubprodctionMaterial.objects.filter(material=validated_data['material'], subproduction=validated_data['subproduction'], is_deleted=False, type=1).exists(): + if SubprodctionMaterial.objects.filter(material=validated_data['material'], subproduction=validated_data['subproduction'], is_deleted=False, type=SubprodctionMaterial.SUB_MA_TYPE_IN).exists(): raise ValidationError('该物料已存在') - validated_data['type']=1 + validated_data['type']=SubprodctionMaterial.SUB_MA_TYPE_IN return super().create(validated_data) class InputMaterialUpdateSerializer(serializers.ModelSerializer): @@ -106,11 +111,11 @@ class OutputMaterialSerializer(serializers.ModelSerializer): fields = ['count', 'sort', 'material', 'subproduction', 'is_main'] def create(self, validated_data): - if SubprodctionMaterial.objects.filter(subproduction=validated_data['subproduction'], is_deleted=False, is_main=True, type=2).exists(): + if SubprodctionMaterial.objects.filter(subproduction=validated_data['subproduction'], is_deleted=False, is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT).exists(): raise ValidationError('主产出只能有1个') - if SubprodctionMaterial.objects.filter(material=validated_data['material'], subproduction=validated_data['subproduction'], is_deleted=False, type=2).exists(): + if SubprodctionMaterial.objects.filter(material=validated_data['material'], subproduction=validated_data['subproduction'], is_deleted=False, type=SubprodctionMaterial.SUB_MA_TYPE_OUT).exists(): raise ValidationError('该物料已存在') - validated_data['type']=2 + validated_data['type']=SubprodctionMaterial.SUB_MA_TYPE_OUT return super().create(validated_data) class OutputMaterialUpdateSerializer(serializers.ModelSerializer): diff --git a/hb_server/apps/mtm/views.py b/hb_server/apps/mtm/views.py index 0d7f90b..679f7d2 100644 --- a/hb_server/apps/mtm/views.py +++ b/hb_server/apps/mtm/views.py @@ -85,7 +85,7 @@ class InputMaterialViewSet(CreateUpdateModelAMixin, ModelViewSet): 输入物料-增删改查 """ perms_map = {'*':'*'} - queryset = SubprodctionMaterial.objects.select_related('material').filter(type=1) + queryset = SubprodctionMaterial.objects.select_related('material').filter(type=SubprodctionMaterial.SUB_MA_TYPE_IN) serializer_class = InputMaterialSerializer filterset_fields = ['subproduction'] ordering = ['sort', '-create_time'] @@ -102,7 +102,7 @@ class OutputMaterialViewSet(CreateUpdateModelAMixin, ModelViewSet): 输出物料-增删改查 """ perms_map = {'*':'*'} - queryset = SubprodctionMaterial.objects.select_related('material').filter(type=2) + queryset = SubprodctionMaterial.objects.select_related('material').filter(type=SubprodctionMaterial.SUB_MA_TYPE_OUT) serializer_class = OutputMaterialSerializer filterset_fields = ['subproduction'] ordering = ['sort', '-create_time'] @@ -119,7 +119,7 @@ class OtherMaterialViewSet(CreateUpdateModelAMixin, ListModelMixin, DestroyModel 其他物料-增删改查 """ perms_map = {'*':'*'} - queryset = SubprodctionMaterial.objects.select_related('material').filter(type=3) + queryset = SubprodctionMaterial.objects.select_related('material').filter(type=SubprodctionMaterial.SUB_MA_TYPE_TOOL) serializer_class = OutputMaterialSerializer filterset_fields = ['subproduction'] ordering = ['sort', '-create_time'] diff --git a/hb_server/apps/pm/apps.py b/hb_server/apps/pm/apps.py index c0c4b31..7fa3289 100644 --- a/hb_server/apps/pm/apps.py +++ b/hb_server/apps/pm/apps.py @@ -5,3 +5,5 @@ class SamConfig(AppConfig): verbose_name = '生产计划管理' + def ready(self): + import apps.pm.signals \ No newline at end of file diff --git a/hb_server/apps/pm/migrations/0010_auto_20211103_1700.py b/hb_server/apps/pm/migrations/0010_auto_20211103_1700.py new file mode 100644 index 0000000..ba5b40c --- /dev/null +++ b/hb_server/apps/pm/migrations/0010_auto_20211103_1700.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.6 on 2021-11-03 09:00 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0028_auto_20211102_1707'), + ('pm', '0009_auto_20211029_1017'), + ] + + operations = [ + migrations.AddField( + model_name='subproductionplan', + name='main_count', + field=models.IntegerField(default=1, verbose_name='应产出数'), + preserve_default=False, + ), + migrations.AddField( + model_name='subproductionplan', + name='main_count_real', + field=models.IntegerField(default=0, verbose_name='实际产出数'), + ), + migrations.AddField( + model_name='subproductionplan', + name='main_product', + field=models.ForeignKey(default=19, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='主要产品'), + preserve_default=False, + ), + migrations.AddField( + model_name='subproductionprogress', + name='count_pick', + field=models.IntegerField(default=0, verbose_name='实际领用数'), + ), + migrations.AddField( + model_name='subproductionprogress', + name='is_main', + field=models.BooleanField(default=False, verbose_name='是否主产出'), + ), + migrations.AlterField( + model_name='subproductionprogress', + name='count', + field=models.IntegerField(verbose_name='应出入数'), + ), + migrations.AlterField( + model_name='subproductionprogress', + name='count_real', + field=models.IntegerField(default=0, verbose_name='实际消耗/产出数'), + ), + ] diff --git a/hb_server/apps/pm/migrations/0011_auto_20211104_1006.py b/hb_server/apps/pm/migrations/0011_auto_20211104_1006.py new file mode 100644 index 0000000..fafca63 --- /dev/null +++ b/hb_server/apps/pm/migrations/0011_auto_20211104_1006.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.6 on 2021-11-04 02:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0028_auto_20211102_1707'), + ('pm', '0010_auto_20211103_1700'), + ] + + operations = [ + migrations.AlterField( + model_name='subproductionplan', + name='main_count', + field=models.IntegerField(default=0, verbose_name='应产出数'), + ), + migrations.AlterField( + model_name='subproductionplan', + name='main_product', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='主要产品'), + ), + ] diff --git a/hb_server/apps/pm/migrations/0012_alter_subproductionprogress_type.py b/hb_server/apps/pm/migrations/0012_alter_subproductionprogress_type.py new file mode 100644 index 0000000..cddd556 --- /dev/null +++ b/hb_server/apps/pm/migrations/0012_alter_subproductionprogress_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-08 09:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0011_auto_20211104_1006'), + ] + + operations = [ + migrations.AlterField( + model_name='subproductionprogress', + name='type', + field=models.IntegerField(default=((1, '输入物料'), (2, '输出物料'), (3, '工具工装')), verbose_name='物料应用类型'), + ), + ] diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py index 2a00e1a..bc59ec9 100644 --- a/hb_server/apps/pm/models.py +++ b/hb_server/apps/pm/models.py @@ -7,7 +7,7 @@ import django.utils.timezone as timezone from django.db.models.query import QuerySet from utils.model import SoftModel, BaseModel -from apps.mtm.models import Material, Process, SubProduction +from apps.mtm.models import Material, Process, SubProduction, SubprodctionMaterial from apps.sam.models import Order class ProductionPlan(CommonAModel): @@ -43,9 +43,16 @@ class SubProductionPlan(CommonAModel): subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE) start_date = models.DateField('计划开工日期') end_date = models.DateField('计划完工日期') + workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE) + + main_product = models.ForeignKey(Material, verbose_name='主要产品', on_delete=models.CASCADE, null=True, blank=True) + main_count = models.IntegerField('应产出数', default=0) + main_count_real = models.IntegerField('实际产出数', default=0) + steps = models.JSONField('工艺步骤', default=list) + state = models.IntegerField('状态', default=0) start_date_real = models.DateField('实际开工日期', null=True, blank=True) end_date_real = models.DateField('实际完工日期', null=True, blank=True) @@ -58,13 +65,10 @@ class SubProductionProgress(BaseModel): """ 子计划生产进度统计表/物料消耗 """ - type_choices=( - (1, '输入物料'), - (2, '输出物料'), - (3, '工具工装') - ) subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE, related_name='progress_subplan') material = models.ForeignKey(Material, verbose_name='关联物料', on_delete=models.CASCADE) - type = models.IntegerField('物料应用类型', default=1) - count = models.FloatField('应出入数') - count_real = models.IntegerField('实际出入数', default=0) + is_main = models.BooleanField('是否主产出', default=False) + type = models.IntegerField('物料应用类型', default=SubprodctionMaterial.type_choices) + count = models.IntegerField('应出入数') + count_pick = models.IntegerField('实际领用数', default=0) + count_real = models.IntegerField('实际消耗/产出数', default=0) diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py index 8b170a5..735b0cc 100644 --- a/hb_server/apps/pm/serializers.py +++ b/hb_server/apps/pm/serializers.py @@ -1,7 +1,7 @@ from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress from rest_framework import serializers from apps.sam.serializers import OrderSerializer -from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer +from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, SubProductionSimpleSerializer from apps.system.serializers import OrganizationSimpleSerializer @@ -27,10 +27,15 @@ class ResourceCalListSerializer(serializers.ListSerializer): class SubProductionPlanListSerializer(serializers.ModelSerializer): workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True) process_ = ProcessSimpleSerializer(source='process', read_only=True) + subproduction_ = SubProductionSimpleSerializer(source='subproduction', read_only=True) + main_product_ = MaterialSimpleSerializer(source='main_product', read_only=True) class Meta: model=SubProductionPlan fields = '__all__' + + + class SubProductionPlanUpdateSerializer(serializers.ModelSerializer): class Meta: model = SubProductionPlan diff --git a/hb_server/apps/pm/signals.py b/hb_server/apps/pm/signals.py new file mode 100644 index 0000000..69aa69c --- /dev/null +++ b/hb_server/apps/pm/signals.py @@ -0,0 +1,18 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver +from apps.pm.models import SubProductionProgress + +@receiver(post_save, sender=SubProductionProgress) +def update_subplan_main(sender, instance, created, **kwargs): + """ + 根据生产进度统计表更新子计划进度表相关字段 + """ + if instance.is_main: + subplan = instance.subproduction_plan + if created: + subplan.main_product = instance.material + subplan.main_count = instance.count + subplan.main_count_real = instance.count_real + subplan.save() + + diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index ac35f84..682e59d 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -87,8 +87,11 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel start_date=production_plan.start_date, end_date=production_plan.end_date, workshop=i.process.workshop, process=i.process, create_by=request.user, steps = list(steps)) + # 生成子计划物料需求/进度 for m in SubprodctionMaterial.objects.filter(subproduction=i, is_deleted=False).order_by('sort'): - SubProductionProgress.objects.create(material=m.material, type=m.type, count=m.count*production_plan.count, subproduction_plan=instance) + spro = SubProductionProgress.objects.create(material=m.material, type=m.type, + is_main=m.is_main, + count=m.count*production_plan.count, subproduction_plan=instance) production_plan.is_planed=True production_plan.save() return Response() @@ -98,10 +101,10 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo 子生产计划-列表/修改 """ perms_map = {'*': '*'} - queryset = SubProductionPlan.objects.select_related('process', 'workshop') + queryset = SubProductionPlan.objects.select_related('process', 'workshop', 'subproduction', 'main_product') search_fields = [] serializer_class = SubProductionPlanListSerializer - filterset_fields = ['production_plan', 'process', 'state'] + filterset_fields = ['production_plan', 'process', 'state', 'main_product', 'workshop'] ordering_fields = ['process__number'] ordering = ['process__number'] @@ -152,7 +155,7 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo 领料需求清单 """ obj = self.get_object() - instance = SubProductionProgress.objects.filter(subproduction_plan=obj, type=1) + instance = SubProductionProgress.objects.filter(subproduction_plan=obj, type=SubprodctionMaterial.SUB_MA_TYPE_IN) serializer = SubProductionProgressSerializer(instance=instance, many=True) return Response(serializer.data) @@ -162,7 +165,7 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo 领料需求清单/库存数 """ obj = self.get_object() - instance = SubProductionProgress.objects.filter(subproduction_plan=obj, type=1) + instance = SubProductionProgress.objects.filter(subproduction_plan=obj, type=SubprodctionMaterial.SUB_MA_TYPE_IN) serializer = SubProductionProgressSerializer(instance=instance, many=True) need = serializer.data materials = [] diff --git a/hb_server/apps/qm/migrations/0004_auto_20211108_0951.py b/hb_server/apps/qm/migrations/0004_auto_20211108_0951.py new file mode 100644 index 0000000..853a26e --- /dev/null +++ b/hb_server/apps/qm/migrations/0004_auto_20211108_0951.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.6 on 2021-11-08 01:51 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0028_auto_20211102_1707'), + ('qm', '0003_testrecord'), + ] + + operations = [ + migrations.RenameField( + model_name='testrecord', + old_name='fifo_detail', + new_name='fifo_item', + ), + migrations.CreateModel( + name='TestRecordItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('field_name', models.CharField(max_length=50, verbose_name='字段名')), + ('field_key', models.CharField(max_length=50, verbose_name='字段标识')), + ('field_type', models.IntegerField(choices=[(1, '生产记录'), (2, '检验记录')], verbose_name='字段类型')), + ('field_value', models.JSONField(blank=True, default=dict, verbose_name='录入值')), + ('need_judge', models.BooleanField(default=False, verbose_name='是否需要判定')), + ('is_testok', models.BooleanField(default=True, verbose_name='是否合格')), + ('form_field', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='mtm.recordformfield', verbose_name='关联字段')), + ('test_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='qm.testrecord', verbose_name='关联的检测记录')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/hb_server/apps/qm/migrations/0005_auto_20211108_1102.py b/hb_server/apps/qm/migrations/0005_auto_20211108_1102.py new file mode 100644 index 0000000..428eae4 --- /dev/null +++ b/hb_server/apps/qm/migrations/0005_auto_20211108_1102.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.6 on 2021-11-08 03:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0004_auto_20211108_0951'), + ] + + operations = [ + migrations.RemoveField( + model_name='testrecord', + name='record_data', + ), + migrations.AlterField( + model_name='testrecorditem', + name='test_record', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_test_record', to='qm.testrecord', verbose_name='关联的检测记录'), + ), + ] diff --git a/hb_server/apps/qm/migrations/0006_testrecorditem_sort.py b/hb_server/apps/qm/migrations/0006_testrecorditem_sort.py new file mode 100644 index 0000000..b57ff52 --- /dev/null +++ b/hb_server/apps/qm/migrations/0006_testrecorditem_sort.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-08 03:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0005_auto_20211108_1102'), + ] + + operations = [ + migrations.AddField( + model_name='testrecorditem', + name='sort', + field=models.IntegerField(default=1, verbose_name='排序号'), + ), + ] diff --git a/hb_server/apps/qm/migrations/0007_alter_testrecorditem_field_type.py b/hb_server/apps/qm/migrations/0007_alter_testrecorditem_field_type.py new file mode 100644 index 0000000..70d454a --- /dev/null +++ b/hb_server/apps/qm/migrations/0007_alter_testrecorditem_field_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-08 05:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0006_testrecorditem_sort'), + ] + + operations = [ + migrations.AlterField( + model_name='testrecorditem', + name='field_type', + field=models.CharField(choices=[(1, '生产记录'), (2, '检验记录')], max_length=50, verbose_name='字段类型'), + ), + ] diff --git a/hb_server/apps/qm/models.py b/hb_server/apps/qm/models.py index 062f5c9..0174d40 100644 --- a/hb_server/apps/qm/models.py +++ b/hb_server/apps/qm/models.py @@ -1,5 +1,8 @@ from django.db import models +from django.db.models.enums import Choices +from apps.mtm.models import RecordForm, RecordFormField from apps.system.models import CommonAModel, File +from utils.model import BaseModel # Create your models here. class Standard(CommonAModel): """ @@ -44,9 +47,21 @@ class TestRecord(CommonAModel): """ 检验记录 """ - form = models.ForeignKey('mtm.recordform', verbose_name='所用表格', on_delete=models.CASCADE) - record_data = models.JSONField('记录数据', default=dict, blank=True) is_testok = models.BooleanField('是否合格', default=True) - fifo_detail = models.ForeignKey('inm.fifodetail', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True) - \ No newline at end of file + fifo_item = models.ForeignKey('inm.fifoitem', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True) + + +class TestRecordItem(BaseModel): + """ + 记录表格字段值 + """ + form_field = models.ForeignKey(RecordFormField, verbose_name='关联字段', on_delete=models.CASCADE, db_constraint=False) + field_name = models.CharField('字段名', max_length=50) + field_key = models.CharField('字段标识', max_length=50) + field_type = models.CharField('字段类型', choices=RecordForm.type_choices, max_length=50) + field_value = models.JSONField('录入值', default=dict, blank=True) + need_judge = models.BooleanField('是否需要判定', default=False) + sort = models.IntegerField('排序号', default=1) + is_testok = models.BooleanField('是否合格', default=True) + test_record = models.ForeignKey(TestRecord, verbose_name='关联的检测记录', on_delete=models.CASCADE, related_name='item_test_record') \ No newline at end of file diff --git a/hb_server/apps/qm/serializers.py b/hb_server/apps/qm/serializers.py index 51d5cb7..2ff328b 100644 --- a/hb_server/apps/qm/serializers.py +++ b/hb_server/apps/qm/serializers.py @@ -2,7 +2,7 @@ from rest_framework import serializers from apps.mtm.models import RecordForm, RecordFormField from apps.mtm.serializers import RecordFormFieldSerializer, RecordFormSimpleSerializer from apps.system.serializers import FileSimpleSerializer -from .models import Standard, TestItem, TestRecord +from .models import Standard, TestItem, TestRecord, TestRecordItem class StandardCreateUpdateSerializer(serializers.ModelSerializer): class Meta: @@ -35,15 +35,21 @@ class AnalysisItemSerializer(serializers.ModelSerializer): pass +class TestRecordItemCreateSerializer(serializers.ModelSerializer): + class Meta: + model = TestRecordItem + fields = ['form_field', 'field_value'] + +class TestRecordItemSerializer(serializers.ModelSerializer): + class Meta: + model = TestRecordItem + fields = '__all__' + class TestRecordCreateSerializer(serializers.ModelSerializer): + record_data = TestRecordItemCreateSerializer(many=True) class Meta: model = TestRecord - fields = ['form', 'record_data', 'is_testok', 'fifo_detail'] - - def create(self, validated_data): - if 'is_testok' not in validated_data: - raise serializers.ValidationError('未填写检测结论') - return super().create(validated_data) + fields = ['form', 'record_data', 'is_testok', 'fifo_item'] class TestRecordListSerializer(serializers.ModelSerializer): class Meta: @@ -53,21 +59,21 @@ class TestRecordListSerializer(serializers.ModelSerializer): class TestRecordDetailSerializer(serializers.ModelSerializer): form_ = RecordFormSimpleSerializer(source='form', read_only=True) - record_data_ = serializers.SerializerMethodField() + record_data_ = TestRecordItemSerializer(source='item_test_record', read_only=True, many=True) class Meta: model = TestRecord fields = '__all__' @staticmethod def setup_eager_loading(queryset): - queryset = queryset.select_related('form','fifo_detail') + queryset = queryset.select_related('form','fifo_item') return queryset - def get_record_data_(self, obj): - record_data = obj.record_data - all_fields = RecordFormField.objects.filter(form=obj.form, is_deletd=False).order_by('sort') - all_fields_l = RecordFormFieldSerializer(instance=all_fields, many=True).data - for i in all_fields_l: - key = i['field_key'] - i['field_value'] = record_data.get(key, None) - return all_fields_l \ No newline at end of file + # def get_record_data_(self, obj): + # record_data = obj.record_data + # all_fields = RecordFormField.objects.filter(form=obj.form, is_deletd=False).order_by('sort') + # all_fields_l = RecordFormFieldSerializer(instance=all_fields, many=True).data + # for i in all_fields_l: + # key = i['field_key'] + # i['field_value'] = record_data.get(key, None) + # return all_fields_l \ No newline at end of file diff --git a/hb_server/apps/qm/views.py b/hb_server/apps/qm/views.py index 41133f5..cc59ab8 100644 --- a/hb_server/apps/qm/views.py +++ b/hb_server/apps/qm/views.py @@ -1,8 +1,12 @@ from apps.qm.serializers import StandardCreateUpdateSerializer, StandardSerializer, TestItemCreateUpdateSerializer, TestItemSerializer, TestRecordCreateSerializer, TestRecordDetailSerializer, TestRecordListSerializer -from apps.qm.models import Standard, TestItem, TestRecord +from apps.qm.models import Standard, TestItem, TestRecord, TestRecordItem from django.shortcuts import render from rest_framework.viewsets import ModelViewSet from apps.system.mixins import CreateUpdateModelAMixin +from rest_framework.exceptions import APIException +from rest_framework.response import Response +from rest_framework import status +from django.db import transaction # Create your views here. class StandardViewSet(CreateUpdateModelAMixin, ModelViewSet): """ @@ -43,7 +47,7 @@ class TestRecordViewSet(ModelViewSet): 检测记录 """ perms_map = {'*': '*'} - queryset = TestRecord.objects.select_related('fifo_detail', 'form').all() + queryset = TestRecord.objects.select_related('fifo_item', 'form').all() serializer_class = TestRecordListSerializer ordering = ['-id'] @@ -56,10 +60,33 @@ class TestRecordViewSet(ModelViewSet): return TestRecordDetailSerializer return super().get_serializer_class() - def perform_create(self, serializer): - obj = serializer.save(create_by = self.request.user) - # 如果检测合格 - if obj.fifo_detail: - obj.fifo_detail.is_testok = True if obj.is_testok else False - obj.fifo_detail.is_tested = True - obj.fifo_detail.save() \ No newline at end of file + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + record_data = vdata.pop('record_data') + if 'is_testok' not in vdata: + raise APIException('未填写检测结论') + with transaction.atomic(): + obj = serializer.save(create_by = self.request.user) + tris = [] + for m in record_data: # 保存记录详情 + form_field = m['form_field'] + m['field_name'] = form_field.field_name + m['field_key'] = form_field.field_key + m['field_type'] = form_field.field_type + m['field_value'] = m['field_value'] + m['sort'] = form_field.sort + m['need_judge'] = form_field.need_judge + m['is_testok'] = m['is_testok'] if 'is_testok' in m else True + m['test_record'] = obj + tris.append(TestRecordItem(**m)) + TestRecordItem.objects.bulk_create(tris) + + # 如果检测合格 + if obj.fifo_item: + obj.fifo_item.is_testok = True if obj.is_testok else False + obj.fifo_item.is_tested = True + obj.fifo_item.save() + + return Response(status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/hb_server/apps/wpm/migrations/0001_initial.py b/hb_server/apps/wpm/migrations/0001_initial.py index 75511e7..6a6fb00 100644 --- a/hb_server/apps/wpm/migrations/0001_initial.py +++ b/hb_server/apps/wpm/migrations/0001_initial.py @@ -30,7 +30,6 @@ class Migration(migrations.Migration): ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproduct_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('m_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='所属物料状态')), ('p_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.step', verbose_name='所在步骤')), - ('parent', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='wpm.wproduct', verbose_name='上一级')), ('subproduction_plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pm.subproductionplan', verbose_name='关联子生产计划')), ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproduct_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), ], diff --git a/hb_server/apps/wpm/migrations/0004_auto_20211104_0914.py b/hb_server/apps/wpm/migrations/0004_auto_20211104_0914.py new file mode 100644 index 0000000..85eb696 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0004_auto_20211104_0914.py @@ -0,0 +1,61 @@ +# Generated by Django 3.2.6 on 2021-11-04 01:14 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0010_auto_20211103_1700'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mtm', '0028_auto_20211102_1707'), + ('wpm', '0003_auto_20211102_0935'), + ] + + operations = [ + migrations.CreateModel( + name='WProductRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('data', models.JSONField(blank=True, default=dict, verbose_name='记录的数据')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('record_form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.recordform', verbose_name='所用表格')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.RemoveField( + model_name='wmaterial', + name='process', + ), + migrations.RemoveField( + model_name='wmaterial', + name='workshop', + ), + migrations.RemoveField( + model_name='wproductflow', + name='wproduct', + ), + migrations.AddField( + model_name='wmaterial', + name='subproduction_plan', + field=models.ForeignKey(default=68, on_delete=django.db.models.deletion.CASCADE, to='pm.subproductionplan', verbose_name='关联子计划'), + preserve_default=False, + ), + migrations.AddField( + model_name='wproductflow', + name='wproducts', + field=models.JSONField(default=list, verbose_name='关联产品'), + ), + migrations.DeleteModel( + name='WProductForm', + ), + ] diff --git a/hb_server/apps/wpm/migrations/0005_auto_20211108_0901.py b/hb_server/apps/wpm/migrations/0005_auto_20211108_0901.py new file mode 100644 index 0000000..bc8dd8b --- /dev/null +++ b/hb_server/apps/wpm/migrations/0005_auto_20211108_0901.py @@ -0,0 +1,109 @@ +# Generated by Django 3.2.6 on 2021-11-08 01:01 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mtm', '0028_auto_20211102_1707'), + ('wpm', '0004_auto_20211104_0914'), + ] + + operations = [ + migrations.CreateModel( + name='WProductAction', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('wproducts', models.JSONField(blank=True, default=list, verbose_name='关联产品ID列表')), + ('remark', models.CharField(blank=True, max_length=200, null=True, verbose_name='操作备注')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductaction_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('m_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='操作时的物料状态')), + ('p_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.step', verbose_name='操作步骤')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductaction_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='WProductMaterial', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('type', models.IntegerField(choices=[(1, '消耗'), (2, '产出')], default=0, verbose_name='类型')), + ('count', models.IntegerField(verbose_name='消耗或产出数量')), + ('material', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='可能产出的副产品')), + ('wmaterial', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.wmaterial', verbose_name='关联的车间物料')), + ('wproduct_action', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.wproductaction', verbose_name='关联的生产操作')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='WProductRecordDetail', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('field_name', models.CharField(max_length=50, verbose_name='字段名')), + ('field_key', models.CharField(max_length=50, verbose_name='字段标识')), + ('field_type', models.IntegerField(choices=[(1, '生产记录'), (2, '检验记录')], verbose_name='字段类型')), + ('field_value', models.JSONField(blank=True, default=dict, verbose_name='录入值')), + ('sort', models.IntegerField(default=1, verbose_name='排序号')), + ('form_field', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='mtm.recordformfield', verbose_name='关联字段')), + ], + options={ + 'abstract': False, + }, + ), + migrations.DeleteModel( + name='WProductFlow', + ), + migrations.RemoveField( + model_name='wproductrecord', + name='data', + ), + migrations.RemoveField( + model_name='wproductrecord', + name='record_form', + ), + migrations.AddField( + model_name='wproduct', + name='is_executed', + field=models.BooleanField(default=False, verbose_name='子工序是否已执行'), + ), + migrations.AddField( + model_name='wproductrecord', + name='form', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.recordform', verbose_name='所用的生产记录表格'), + preserve_default=False, + ), + migrations.AlterField( + model_name='wproduct', + name='act_state', + field=models.IntegerField(choices=[(1, '生产中'), (2, '待检测'), (3, '已合格')], default=0, verbose_name='进行状态'), + ), + migrations.AddField( + model_name='wproductrecorddetail', + name='wproduct_record', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.wproductrecord', verbose_name='关联的生产记录'), + ), + migrations.AddField( + model_name='wproductrecord', + name='wproduct_action', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='wpm.wproductaction', verbose_name='关联的生产操作'), + preserve_default=False, + ), + ] diff --git a/hb_server/apps/wpm/migrations/0006_rename_wproductrecorddetail_wproductrecorditem.py b/hb_server/apps/wpm/migrations/0006_rename_wproductrecorddetail_wproductrecorditem.py new file mode 100644 index 0000000..b1346c6 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0006_rename_wproductrecorddetail_wproductrecorditem.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-08 01:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0028_auto_20211102_1707'), + ('wpm', '0005_auto_20211108_0901'), + ] + + operations = [ + migrations.RenameModel( + old_name='WProductRecordDetail', + new_name='WProductRecordItem', + ), + ] diff --git a/hb_server/apps/wpm/migrations/0007_alter_wproductrecorditem_field_type.py b/hb_server/apps/wpm/migrations/0007_alter_wproductrecorditem_field_type.py new file mode 100644 index 0000000..47c78c2 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0007_alter_wproductrecorditem_field_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-08 05:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0006_rename_wproductrecorddetail_wproductrecorditem'), + ] + + operations = [ + migrations.AlterField( + model_name='wproductrecorditem', + name='field_type', + field=models.CharField(choices=[(1, '生产记录'), (2, '检验记录')], max_length=50, verbose_name='字段类型'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0008_wproduct_is_hidden.py b/hb_server/apps/wpm/migrations/0008_wproduct_is_hidden.py new file mode 100644 index 0000000..36cb2bd --- /dev/null +++ b/hb_server/apps/wpm/migrations/0008_wproduct_is_hidden.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-08 09:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0007_alter_wproductrecorditem_field_type'), + ] + + operations = [ + migrations.AddField( + model_name='wproduct', + name='is_hidden', + field=models.BooleanField(default=False, verbose_name='是否隐藏'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0009_wproduct_parent.py b/hb_server/apps/wpm/migrations/0009_wproduct_parent.py new file mode 100644 index 0000000..5e7b88a --- /dev/null +++ b/hb_server/apps/wpm/migrations/0009_wproduct_parent.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-11-08 15:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0008_wproduct_is_hidden'), + ] + + operations = [ + migrations.AddField( + model_name='wproduct', + name='parent', + field=models.JSONField(blank=True, default=list, verbose_name='父'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0010_auto_20211108_2335.py b/hb_server/apps/wpm/migrations/0010_auto_20211108_2335.py new file mode 100644 index 0000000..4a67281 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0010_auto_20211108_2335.py @@ -0,0 +1,136 @@ +# Generated by Django 3.2.6 on 2021-11-08 15:35 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0029_step_type'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('wpm', '0009_wproduct_parent'), + ] + + operations = [ + migrations.CreateModel( + name='Operation', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('wproducts', models.JSONField(blank=True, default=list, verbose_name='关联产品ID列表')), + ('remark', models.CharField(blank=True, max_length=200, null=True, verbose_name='操作备注')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='operation_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('m_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='操作时的物料状态')), + ('p_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.step', verbose_name='操作步骤')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='operation_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='OperationMaterial', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('type', models.IntegerField(choices=[(1, '消耗'), (2, '产出')], default=0, verbose_name='类型')), + ('count', models.IntegerField(verbose_name='消耗或产出数量')), + ('material', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='可能产出的副产品')), + ('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.operation', verbose_name='关联的生产操作')), + ('wmaterial', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.wmaterial', verbose_name='关联的车间物料')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='OperationRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='operationrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.recordform', verbose_name='所用的生产记录表格')), + ('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.operation', verbose_name='关联的生产操作')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='operationrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='OperationRecordItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('field_name', models.CharField(max_length=50, verbose_name='字段名')), + ('field_key', models.CharField(max_length=50, verbose_name='字段标识')), + ('field_type', models.CharField(choices=[(1, '生产记录'), (2, '检验记录')], max_length=50, verbose_name='字段类型')), + ('field_value', models.JSONField(blank=True, default=dict, verbose_name='录入值')), + ('sort', models.IntegerField(default=1, verbose_name='排序号')), + ('form_field', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='mtm.recordformfield', verbose_name='关联字段')), + ('operation_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.operation', verbose_name='关联的生产记录')), + ], + options={ + 'abstract': False, + }, + ), + migrations.RemoveField( + model_name='wproductmaterial', + name='material', + ), + migrations.RemoveField( + model_name='wproductmaterial', + name='wmaterial', + ), + migrations.RemoveField( + model_name='wproductmaterial', + name='wproduct_action', + ), + migrations.RemoveField( + model_name='wproductrecord', + name='create_by', + ), + migrations.RemoveField( + model_name='wproductrecord', + name='form', + ), + migrations.RemoveField( + model_name='wproductrecord', + name='update_by', + ), + migrations.RemoveField( + model_name='wproductrecord', + name='wproduct_action', + ), + migrations.RemoveField( + model_name='wproductrecorditem', + name='form_field', + ), + migrations.RemoveField( + model_name='wproductrecorditem', + name='wproduct_record', + ), + migrations.DeleteModel( + name='WProductAction', + ), + migrations.DeleteModel( + name='WProductMaterial', + ), + migrations.DeleteModel( + name='WProductRecord', + ), + migrations.DeleteModel( + name='WProductRecordItem', + ), + ] diff --git a/hb_server/apps/wpm/migrations/0011_alter_operationrecorditem_operation_record.py b/hb_server/apps/wpm/migrations/0011_alter_operationrecorditem_operation_record.py new file mode 100644 index 0000000..daea66b --- /dev/null +++ b/hb_server/apps/wpm/migrations/0011_alter_operationrecorditem_operation_record.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.6 on 2021-11-09 05:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0010_auto_20211108_2335'), + ] + + operations = [ + migrations.AlterField( + model_name='operationrecorditem', + name='operation_record', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.operationrecord', verbose_name='关联的生产记录'), + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index 97dfa62..1b3c8c3 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -6,14 +6,13 @@ from apps.pm.models import ProductionPlan, SubProductionPlan from apps.system.models import CommonAModel, CommonBModel, Organization, User, Dict, File from utils.model import SoftModel, BaseModel from simple_history.models import HistoricalRecords -from apps.mtm.models import Material, Process, Step, RecordForm +from apps.mtm.models import Material, Process, RecordFormField, Step, RecordForm class WMaterial(BaseModel): """ 车间生产物料 """ - workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE) - process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE) + subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子计划', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='关联物料', on_delete=models.CASCADE) batch = models.CharField('批次号', max_length=100, null=True, blank=True) count = models.IntegerField('当前数量', default=0) @@ -22,30 +21,66 @@ class WProduct(CommonAModel): """ 半成品/成品 """ + WPR_ACT_STATE_DOING = 1 + WPR_ACT_STATE_TOTEST = 2 + WPR_ACT_STATE_OK = 3 act_state_choices=( - (0, '待执行'), - (1, '进行中'), - (2, '已完成') + (WPR_ACT_STATE_DOING, '生产中'), + (WPR_ACT_STATE_TOTEST, '待检测'), + (WPR_ACT_STATE_OK, '已合格') ) number = models.CharField('物品编号', unique=True, null=True, blank=True, max_length=50) m_state = models.ForeignKey(Material, verbose_name='所属物料状态', on_delete=models.CASCADE) p_state = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True) - act_state = models.IntegerField('进行状态', default=0) - parent = models.ForeignKey('self', verbose_name='上一级', on_delete=models.CASCADE, db_constraint=False) + act_state = models.IntegerField('进行状态', default=0, choices=act_state_choices) + is_executed = models.BooleanField('子工序是否已执行', default=False) + is_hidden = models.BooleanField('是否隐藏', default=False) + parent = models.JSONField('父', default=list, blank=True) remark = models.CharField('备注', max_length=200, null=True, blank=True) subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE) production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE) -class WProductForm(CommonAModel): + +class Operation(CommonAModel): + """ + 生产操作 + """ + wproducts = models.JSONField('关联产品ID列表', default=list, blank=True) + m_state = models.ForeignKey(Material, verbose_name='操作时的物料状态', on_delete=models.CASCADE, null=True, blank=True) + p_state = models.ForeignKey(Step, verbose_name='操作步骤', on_delete=models.CASCADE, null=True, blank=True) + remark = models.CharField('操作备注', max_length=200, null=True, blank=True) + +class OperationMaterial(BaseModel): + """ + 车间生产物料消耗产出表 + """ + type_choices=( + (1, '消耗'), + (2, '产出') + ) + type = models.IntegerField('类型', default=0, choices=type_choices) + operation = models.ForeignKey(Operation, verbose_name='关联的生产操作', on_delete=models.CASCADE) + wmaterial = models.ForeignKey(WMaterial, verbose_name='关联的车间物料', on_delete=models.CASCADE, null=True, blank=True) + material = models.ForeignKey(Material, verbose_name='可能产出的副产品', on_delete=models.CASCADE, null=True, blank=True) + count = models.IntegerField('消耗或产出数量') + +class OperationRecord(CommonAModel): """ 记录表格 """ - record_form = models.ForeignKey(RecordForm, verbose_name='所用表格', on_delete=models.CASCADE) - data = models.JSONField('记录的数据', default=dict, blank=True) + form = models.ForeignKey(RecordForm, verbose_name='所用的生产记录表格', on_delete=models.CASCADE) + operation = models.ForeignKey(Operation, verbose_name='关联的生产操作', on_delete=models.CASCADE) -class WProductFlow(BaseModel): +class OperationRecordItem(BaseModel): """ - 产品流转日志 + 记录表格字段值 """ - wproduct = models.ForeignKey(WProduct, verbose_name='产品', on_delete=models.CASCADE) \ No newline at end of file + form_field = models.ForeignKey(RecordFormField, verbose_name='关联字段', on_delete=models.CASCADE, db_constraint=False) + field_name = models.CharField('字段名', max_length=50) + field_key = models.CharField('字段标识', max_length=50) + field_type = models.CharField('字段类型', choices=RecordForm.type_choices, max_length=50) + field_value = models.JSONField('录入值', default=dict, blank=True) + sort = models.IntegerField('排序号', default=1) + operation_record = models.ForeignKey(OperationRecord, verbose_name='关联的生产记录', on_delete=models.CASCADE) + diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index bec720e..6d98c3d 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -1,14 +1,16 @@ from rest_framework import serializers from rest_framework.serializers import ModelSerializer -from apps.inm.models import FIFO, FIFODetail, MaterialBatch, WareHouse +from apps.inm.models import FIFO, FIFOItem, MaterialBatch, WareHouse from apps.inm.signals import update_inm -from apps.mtm.models import Material -from apps.mtm.serializers import MaterialSimpleSerializer +from apps.mtm.models import Material, RecordForm, Step +from apps.mtm.serializers import MaterialSimpleSerializer, StepSimpleSerializer from apps.pm.models import SubProductionPlan, SubProductionProgress from django.utils import timezone - -from apps.wpm.models import WMaterial +from django.utils.translation import gettext_lazy as _ +from apps.system.serializers import UserSimpleSerializer +from apps.wpm.models import Operation, WMaterial, WProduct, OperationRecord, OperationRecordItem +from django.db import transaction class PickDetailSerializer(serializers.Serializer): material = serializers.PrimaryKeyRelatedField(queryset=Material.objects.all(), label="物料ID") @@ -23,6 +25,8 @@ class PickSerializer(serializers.Serializer): def create(self, validated_data): picks = validated_data.pop('picks') sp = validated_data['subproduction_plan'] + if sp.state not in [1,2]: + raise serializers.ValidationError('该子计划状态错误') if sp.is_picked: raise serializers.ValidationError('该子计划已领料') for i in picks: @@ -33,38 +37,41 @@ class PickSerializer(serializers.Serializer): except: raise serializers.ValidationError('物料不存在') # 创建出库记录 - operator = self.context['request'].user - validated_data['create_by'] = operator - validated_data['operator'] = operator - validated_data['type'] = 1 - validated_data['inout_date'] = timezone.now() - fifo = FIFO.objects.create(**validated_data) - for i in picks: - # 更新出库详情 - i['fifo'] = fifo - i['count'] = i.pop('pick_count') - FIFODetail.objects.create(**i) - # 更新车间物料 - wm, _ = WMaterial.objects.get_or_create(material=i['material'], batch=i['batch'], \ - process=sp.process,defaults={ - 'material':i['material'], - 'batch':i['batch'], - 'process':sp.process, - 'workshop':sp.workshop, - 'count':0 - }) - wm.count = wm.count + i['count'] - wm.save() - # 更新子计划物料情况 - spp = SubProductionProgress.objects.get(material=i['material'], subproduction_plan=sp, type=1) - spp.count_real = spp.count_real + i['count'] - spp.save() - sp.is_picked=True - sp.save() - # 更新库存 - fifo.is_audited = True - fifo.save() - update_inm(fifo) + with transaction.atomic(): + operator = self.context['request'].user + validated_data['create_by'] = operator + validated_data['operator'] = operator + validated_data['type'] = FIFO.FIFO_TYPE_DO_OUT + validated_data['inout_date'] = timezone.now() + fifo = FIFO.objects.create(**validated_data) + for i in picks: + # 更新出库详情 + i['fifo'] = fifo + i['count'] = i.pop('pick_count') + i['is_testok'] = True # 默认检测合格 + FIFOItem.objects.create(**i) + # 更新车间物料 + wm, _ = WMaterial.objects.get_or_create(material=i['material'], batch=i['batch'], \ + subproduction_plan=sp,defaults={ + 'material':i['material'], + 'batch':i['batch'], + 'subproduction_plan':sp, + 'count':0 + }) + wm.count = wm.count + i['count'] + wm.save() + # 更新子计划物料情况 + spp = SubProductionProgress.objects.get(material=i['material'], subproduction_plan=sp, type=1) + spp.count_pick = spp.count_pick + i['count'] + spp.save() + sp.is_picked=True + sp.state = 3 #生产中 + sp.state_date_real = timezone.now() #实际开工日期 + sp.save() + # 更新库存 + fifo.is_audited = True + fifo.save() + update_inm(fifo) return fifo class WMaterialListSerializer(serializers.ModelSerializer): @@ -74,4 +81,99 @@ class WMaterialListSerializer(serializers.ModelSerializer): material_ = MaterialSimpleSerializer(source='material', read_only=True) class Meta: model = WMaterial - fields = '__all__' \ No newline at end of file + fields = '__all__' + +class WProductListSerializer(serializers.ModelSerializer): + """ + 半成品列表 + """ + m_state_ = MaterialSimpleSerializer(source='m_state', read_only=True) + p_state_ = StepSimpleSerializer(source='p_state', read_only=True) + class Meta: + model = WProduct + fields = '__all__' + +class OperationDetailSerializer(serializers.ModelSerializer): + wproducts_ = serializers.SerializerMethodField() + create_by_ = UserSimpleSerializer(source='create_by', read_only=True) + m_state_ = MaterialSimpleSerializer(source='m_state', read_only=True) + p_state_ = StepSimpleSerializer(source='p_state', read_only=True) + class Meta: + model = Operation + fields = '__all__' + + def get_wproducts_(self, obj): + return list(WProduct.objects.filter(id__in=obj.wproducts).values('id', 'number')) + +class OperationListSerializer(serializers.ModelSerializer): + create_by_ = UserSimpleSerializer(source='create_by', read_only=True) + m_state_ = MaterialSimpleSerializer(source='m_state', read_only=True) + p_state_ = StepSimpleSerializer(source='p_state', read_only=True) + class Meta: + model = Operation + fields = '__all__' + + +class OperationInitSerializer(serializers.Serializer): + step = serializers.PrimaryKeyRelatedField(queryset=Step.objects.all(), label="子工序ID") + subproduction_plan = serializers.PrimaryKeyRelatedField(queryset=SubProductionPlan.objects.all(), label="子计划ID") + wproducts = serializers.ListField(child= + serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all()), label="半成品ID列表", required=False) + + def validate(self, data): + subproduction_plan = data['subproduction_plan'] + step = data['step'] + + stepIds=[i['id'] for i in subproduction_plan.steps] + if step.id not in stepIds: + raise serializers.ValidationError('请选择正确的子工序操作') + + if 'wproducts' in data and data['wproducts']: + if step.type == Step.STEP_TYPE_DIV: + raise serializers.ValidationError(_('不可进行此操作')) + for i in data['wproducts']: + if i.is_executed: + raise serializers.ValidationError('不可进行操作') + if i.subproduction_plan != subproduction_plan: + raise serializers.ValidationError('半成品所属子计划不一致') + if i.p_state != step: + raise serializers.ValidationError('半成品所属子工序不一致') + else: + if step.type != Step.STEP_TYPE_DIV: + raise serializers.ValidationError(_('请选择半成品进行操作')) + return data + + +class DoInputSerializer(serializers.Serializer): + id = serializers.PrimaryKeyRelatedField(queryset=WMaterial.objects.all(), label='车间物料ID') + count_input = serializers.IntegerField(min_value=0, label='消耗数量') + +class DoOutputSerializer(serializers.Serializer): + material = serializers.PrimaryKeyRelatedField(queryset=Material.objects.all(), label='物料ID') + count_output = serializers.IntegerField(min_value=0, label='产出数量') + +class OperationRecordItemSerializer(serializers.ModelSerializer): + class Meta: + model = OperationRecordItem + fields = ['form_field', 'field_value'] + +class OperationRecordSerializer(serializers.ModelSerializer): + record_data = OperationRecordItemSerializer(many=True) + class Meta: + model = OperationRecord + fields = ['form', 'record_data'] + + + +class OperationSubmitSerializer(serializers.Serializer): + step = serializers.PrimaryKeyRelatedField(queryset=Step.objects.all(), label="子工序ID") + subproduction_plan = serializers.PrimaryKeyRelatedField(queryset=SubProductionPlan.objects.all(), label="子计划ID") + wproducts = serializers.ListField(child= + serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all()), label="半成品ID列表", required=False) + input = DoInputSerializer(many=True, required=False) + output = DoOutputSerializer(many=True, required=False) + forms = OperationRecordSerializer(many=True, required=False) + remark = serializers.CharField(required=False, label='操作备注') + + + \ No newline at end of file diff --git a/hb_server/apps/wpm/urls.py b/hb_server/apps/wpm/urls.py index d4685ec..7c43ac7 100644 --- a/hb_server/apps/wpm/urls.py +++ b/hb_server/apps/wpm/urls.py @@ -3,11 +3,16 @@ from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.wpm.views import WMaterialViewSet +from apps.wpm.views import DoFormInit, DoFormSubmit, OperationViewSet, WMaterialViewSet, WPlanViewSet, WProductViewSet router = DefaultRouter() router.register('wmaterial', WMaterialViewSet, basename='wmaterial') +router.register('wproduct', WProductViewSet, basename='wproduct') +router.register('operation', OperationViewSet, basename='operation') +router.register('subplan', WPlanViewSet, basename='wplan') urlpatterns = [ + path('do/init/', DoFormInit.as_view()), + path('do/submit/', DoFormSubmit.as_view()), path('', include(router.urls)), ] diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index 8a3dc4e..2cae722 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -1,17 +1,35 @@ from django.shortcuts import render from rest_framework.generics import CreateAPIView, GenericAPIView -from rest_framework.mixins import ListModelMixin +from rest_framework.mixins import ListModelMixin, RetrieveModelMixin +from rest_framework.utils import serializer_helpers from rest_framework.utils.field_mapping import get_relation_kwargs +from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet, ModelViewSet -from apps.pm.serializers import SubProductionPlanUpdateSerializer +from apps.mtm.models import Material, RecordForm, Step, SubprodctionMaterial +from apps.mtm.serializers import RecordFormDetailSerializer +from apps.pm.models import SubProductionPlan, SubProductionProgress +from apps.pm.serializers import SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action -from apps.wpm.models import WMaterial +from apps.wpm.models import WMaterial, WProduct, Operation, OperationMaterial, OperationRecord, OperationRecordItem -from apps.wpm.serializers import PickSerializer, WMaterialListSerializer +from apps.wpm.serializers import OperationDetailSerializer, OperationListSerializer, PickSerializer, OperationInitSerializer, OperationSubmitSerializer, WMaterialListSerializer, WProductListSerializer from rest_framework.response import Response +from django.db import transaction # Create your views here. +class WPlanViewSet(ListModelMixin, GenericViewSet): + """ + 车间生产计划 + """ + perms_map = {'*': '*'} + queryset = SubProductionPlan.objects.select_related('process', 'workshop', 'subproduction', 'main_product').exclude(state=0) + search_fields = [] + serializer_class = SubProductionPlanListSerializer + filterset_fields = ['production_plan', 'process', 'state', 'main_product', 'workshop'] + ordering_fields = ['process__number'] + ordering = ['process__number'] + class WMaterialViewSet(CreateUpdateModelAMixin, ListModelMixin, GenericViewSet): """ 车间物料表 @@ -19,7 +37,7 @@ class WMaterialViewSet(CreateUpdateModelAMixin, ListModelMixin, GenericViewSet): perms_map={'*':'*'} queryset = WMaterial.objects.select_related('material').all() serializer_class = WMaterialListSerializer - filterset_fields = ['material', 'process', 'workshop'] + filterset_fields = ['material', 'subproduction_plan', 'subproduction_plan__process', 'subproduction_plan__workshop'] ordering_fields = ['material__number'] ordering = ['material__number'] @@ -33,8 +51,209 @@ class WMaterialViewSet(CreateUpdateModelAMixin, ListModelMixin, GenericViewSet): serializer.save() return Response() -class DoFormInit(CreateAPIView): +class WProductViewSet(ListModelMixin, GenericViewSet): """ - 生产操作表单创建 + 半成品 """ - perms_map={'*':'*'} \ No newline at end of file + perms_map={'*':'*'} + queryset = WProduct.objects.select_related('p_state', 'm_state').filter(is_hidden=False) + serializer_class = WProductListSerializer + filterset_fields = ['p_state', 'subproduction_plan', 'm_state', 'production_plan', 'p_state__process'] + search_fields = ['number'] + ordering_fields = ['id'] + ordering = ['id'] + + @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=PickSerializer) + def test(): + pass + +class OperationViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): + """ + 生产操作记录 + """ + perms_map={'*':'*'} + queryset = Operation.objects.select_related('p_state', 'm_state').all() + serializer_class = OperationListSerializer + filterset_fields = ['p_state', 'm_state'] + ordering_fields = ['id'] + ordering = ['-id'] + + def get_serializer_class(self): + if self.action == 'retrieve': + return OperationDetailSerializer + return super().get_serializer_class() + +class DoFormInit(CreateAPIView, GenericAPIView): + perms_map={'*':'*'} + serializer_class=OperationInitSerializer + def post(self, request, format=None): + """ + 调用操作表单 + """ + data = request.data + serializer = OperationInitSerializer(data=data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + ret = {} + ret_0 = {} + ret_0['step'] = data['step'] + ret_0['subproduction_plan'] = data['subproduction_plan'] + if 'wproducts' in data and data['wproducts']: + ret_0['wproducts'] = data['wproducts'] + else: + ret_0['wproducts'] = [] + # 调出该子计划现有物料 + ret_0['input'] = list(WMaterial.objects.filter(subproduction_plan=vdata['subproduction_plan'])\ + .values('id', 'material', 'material__name', 'count', 'material__number', 'batch')) + for i in ret_0['input']: + i['count_input'] = 0 + # 需要输出的物料 + if ret_0['wproducts']: + # 排除主要产物, 因为已经放到半成品里了, 由半成品进行处理, 夹层可能需要特殊处理 + o_objs = SubProductionProgress.objects.filter( + subproduction_plan=vdata['subproduction_plan'], type=SubprodctionMaterial.SUB_MA_TYPE_OUT).exclude(is_main=True) + + else: + o_objs = SubProductionProgress.objects.filter( + subproduction_plan=vdata['subproduction_plan'], type=SubprodctionMaterial.SUB_MA_TYPE_OUT) + ret_0['output'] = list(o_objs.values('material', 'material__name', 'material__number')) + for i in ret_0['output']: + i['count_output']=0 + ret['forms'] = [] + ret_0['id'] = 0 + ret_0['name'] = '基本信息' + ret['forms'].append(ret_0) + forms = RecordForm.objects.filter(step=vdata['step'], type=RecordForm.RF_TYPE_DO) + if forms.exists(): + ret['forms'].extend(RecordFormDetailSerializer(instance=forms, many=True).data) + return Response(ret) + + +class DoFormSubmit(CreateAPIView, GenericAPIView): + perms_map={'*':'*'} + serializer_class = OperationSubmitSerializer + + @transaction.atomic + def post(self, request, format=None): + """ + 提交操作表单 + """ + data = request.data + serializer = OperationSubmitSerializer(data=data, context={'request':self.request}) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data #校验之后的数据 + + # 创建一个生产操作记录 + action_obj = Operation() + action_obj.p_state = vdata['step'] + if 'wproducts' in data and data['wproducts']: + action_obj.wproducts = data['wproducts'] + action_obj.m_state = vdata['wproducts'][0].m_state + action_obj.remark = vdata.get('remark', '') # 操作备注 + action_obj.create_by = request.user + action_obj.save() + + # 保存物料消耗 + for i in vdata['input']: + if i['count_input']: #如果有消耗 + i_wmat = i['id'] + OperationMaterial.objects.create(type=1, operation=action_obj, + wmaterial= i_wmat, count=i['count_input']) + # 更新车间物料 + i_wmat.count = i_wmat.count- i['count_input'] + i_wmat.save() + # 更新子计划物料消耗情况 + sp = SubProductionProgress.objects.get(subproduction_plan=i_wmat.subproduction_plan, + material=i_wmat.material) + sp.count_real = sp.count_real + i['count_input'] + sp.save() + + # 物料产出 + if 'output' in data and data['output']: + for i in vdata['output']: # 已经序列化好的数据 + ma = i['material'] + if vdata['subproduction_plan'].main_product == ma: # 如果是该计划主产物 + # 如果是切割 + # 获取下一步子工序 + if vdata['step'].type == Step.STEP_TYPE_DIV: + stepIds = [i['id'] for i in vdata['subproduction_plan'].steps] + pindex = stepIds.index(vdata['step'].id) + wpr = dict(m_state=ma, p_state=Step.objects.get(pk=stepIds[pindex+1]), + act_state=WProduct.WPR_ACT_STATE_DOING, is_executed=False, remark='', + subproduction_plan=vdata['subproduction_plan'], + production_plan=vdata['subproduction_plan'].production_plan) + for x in range(i['count_output']): + WProduct.objects.create(**wpr) + else: + # 更新操作产出物料表 + OperationMaterial.objects.create(type=2, operation=action_obj, + material= ma, count=i['count_output']) + # 更新车间物料表 + ins, _ = WMaterial.objects.get_or_create(subproduction_plan=vdata['subproduction_plan'], + material=ma) + ins.count = ins.count + i['count_output'] + ins.save() + # 更新子计划进度表 + sp = SubProductionProgress.objects.get(subproduction_plan=vdata['subproduction_plan'], + material=ma) + sp.count_real = sp.count_real + i['count_input'] + sp.save() + + # 更新动态产品表 + if 'wproducts' in data and data['wproducts']: + wproducts = WProduct.objects.filter(pk__in=data['wproducts']) + # 获取下一步子工序 + stepIds = [i['id'] for i in vdata['subproduction_plan'].steps] + pindex = stepIds.index(vdata['step'].id) + if pindex + 1 < len(stepIds): # 如果不是最后一步 + newstep = Step.objects.get(pk=stepIds[pindex+1]) + wproducts.update(p_state=newstep, is_executed=False) + + # 特殊情况如果是夹层结合 + if vdata['step'].type == Step.STEP_TYPE_COMB: + wproducts.update(is_hidden=True) # 隐藏 + + WProduct.objects.create( + m_state=vdata['subproduction_plan'].main_product, p_state = newstep, + act_state=WProduct.WPR_ACT_STATE_DOING, is_executed=False, remark='', + subproduction_plan=vdata['subproduction_plan'], + production_plan=vdata['subproduction_plan'].production_plan, + parent = data['wproducts'] + ) + + else: # 如果是最后一步, 此时需要转序并更新状态为待检测 + newstep = vdata['step'] + wproducts.update(p_state=newstep, is_executed=True, act_state=WProduct.WPR_ACT_STATE_TOTEST) + + # 特殊情况如果是夹层结合 + if vdata['step'].type == Step.STEP_TYPE_COMB: + wproducts.update(is_hidden=True) # 隐藏 + + WProduct.objects.create( + m_state=vdata['subproduction_plan'].main_product, p_state = newstep, + act_state=WProduct.WPR_ACT_STATE_TOTEST, is_executed=True, remark='', + subproduction_plan=vdata['subproduction_plan'], + production_plan=vdata['subproduction_plan'].production_plan + ) + + # 保存自定义表单结果 + for i in vdata['forms']: + wr = OperationRecord() + wr.form = i['form'] + wr.create_by = request.user + wr.operation = action_obj + wr.save() + wrds = [] + for m in i['record_data']: # 保存记录详情 + form_field = m['form_field'] + m['field_name'] = form_field.field_name + m['field_key'] = form_field.field_key + m['field_type'] = form_field.field_type + m['field_value'] = m['field_value'] + m['sort'] = form_field.sort + m['operation_record'] = wr + wrds.append(OperationRecordItem(**m)) + OperationRecordItem.objects.bulk_create(wrds) + return Response() + + \ No newline at end of file diff --git a/hb_server/utils/model.py b/hb_server/utils/model.py index 05d04ab..fb7fe70 100644 --- a/hb_server/utils/model.py +++ b/hb_server/utils/model.py @@ -2,9 +2,8 @@ from django.db import models import django.utils.timezone as timezone from django.db.models.query import QuerySet + # 自定义软删除查询基类 - - class SoftDeletableQuerySetMixin(object): ''' QuerySet for SoftDeletableModel. Instead of removing instance sets @@ -63,6 +62,24 @@ class BaseModel(models.Model): class Meta: abstract = True + def save(self, *args, **kwargs): + if self.pk: + # If self.pk is not None then it's an update. + cls = self.__class__ + old = cls.objects.get(pk=self.pk) + # This will get the current model state since super().save() isn't called yet. + new = self # This gets the newly instantiated Mode object with the new values. + changed_fields = [] + for field in cls._meta.get_fields(): + field_name = field.name + try: + if getattr(old, field_name) != getattr(new, field_name): + changed_fields.append(field_name) + except Exception as ex: # Catch field does not exist exception + pass + kwargs['update_fields'] = changed_fields + super().save(*args, **kwargs) + class SoftModel(BaseModel): """ 软删除基本表