diff --git a/hb_client/src/api/file.js b/hb_client/src/api/file.js index 01c683e..9ad01c7 100644 --- a/hb_client/src/api/file.js +++ b/hb_client/src/api/file.js @@ -15,4 +15,11 @@ export function getFileList(query) { method: 'get', params: query }) -} \ No newline at end of file +} +export function upFile(data) { + return request({ + url: '/file/', + method: 'post', + data + }) +} diff --git a/hb_client/src/assets/glass.png b/hb_client/src/assets/glass.png new file mode 100644 index 0000000..7b7c511 Binary files /dev/null and b/hb_client/src/assets/glass.png differ diff --git a/hb_client/src/views/mtm/materialdo.vue b/hb_client/src/views/mtm/materialdo.vue index 2795f0d..317fcf4 100644 --- a/hb_client/src/views/mtm/materialdo.vue +++ b/hb_client/src/views/mtm/materialdo.vue @@ -2,27 +2,34 @@
+ -
- 物料检验记录表 -
- 新增 + 物料检验记录表 +
+ + 新增 + + - + @@ -31,22 +38,26 @@ 查看查看 + 编辑编辑 + 删除删除 + + - +
取消 + >取消 + 确认 + >确认 +
+ - + - - + + + @@ -124,11 +141,12 @@ :label="item.field_name" > @@ -137,9 +155,10 @@ :label="item.field_name" > @@ -169,26 +188,46 @@ + +
+ +
+ + 您的浏览器不支持绘图,请升级或更换浏览器! + + + + +
+
标记
+
文字
+ +
回退
+
保存
+
+
+
+
+
- - - -
+
- 记录字段 + 记录字段
新增新增 + + - + + }} + + }} + + }} + - + - + - + + + - + + - - + - - - + + + - - - - - - - - - - -!--> - - - - - - -
取消取消 + 确认确认 +
@@ -439,329 +494,734 @@ diff --git a/hb_server/apps/mtm/migrations/0036_auto_20211123_1052.py b/hb_server/apps/mtm/migrations/0036_auto_20211123_1052.py new file mode 100644 index 0000000..c0e36de --- /dev/null +++ b/hb_server/apps/mtm/migrations/0036_auto_20211123_1052.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.9 on 2021-11-23 02:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0035_auto_20211117_1637'), + ] + + operations = [ + migrations.AddField( + model_name='recordformfield', + name='draw_template', + field=models.CharField(blank=True, max_length=200, null=True, verbose_name='绘图模板'), + ), + migrations.AlterField( + model_name='recordformfield', + name='field_type', + field=models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('time', '时间'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('textarea', '文本域'), ('draw', '绘图')], max_length=50, verbose_name='类型'), + ), + ] diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index 46e82b4..c6ebc6e 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -131,6 +131,7 @@ class RecordFormField(CommonAModel): ('select', '单选下拉'), ('selects', '多选下拉'), ('textarea', '文本域'), + ('draw', '绘图') ) high_rule_choices = ( (1, '小于'), @@ -161,6 +162,8 @@ class RecordFormField(CommonAModel): is_hidden = models.BooleanField('是否隐藏', default=False) parent = models.ForeignKey('self', verbose_name='父', on_delete=models.CASCADE, null=True, blank=True) + draw_template = models.CharField('绘图模板', max_length=200, null=True, blank=True) + class Meta: verbose_name = '记录表格字段' diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 3e7a46d..f1b3ab8 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -26,7 +26,7 @@ class MaterialDetailSerializer(serializers.ModelSerializer): class MaterialSimpleSerializer(serializers.ModelSerializer): class Meta: model = Material - fields = ['id', 'name', 'number', 'unit','specification'] + fields = ['id', 'name', 'number', 'unit','specification', 'type'] class ProcessSerializer(serializers.ModelSerializer): instruction_ = FileSimpleSerializer(source='instruction', read_only=True) @@ -209,6 +209,12 @@ class RecordFormDetailSerializer(serializers.ModelSerializer): def get_form_fields(self, obj): serializer = RecordFormFieldSerializer(instance=RecordFormField.objects.filter(form=obj, is_deleted=False), many=True) + vdata = serializer.data + if obj.type == RecordForm.RF_TYPE_TEST: + for i in vdata: + if i['need_judge']: + i['is_testok'] = False + i['is_teskok_robot'] = False return serializer.data diff --git a/hb_server/apps/pm/filters.py b/hb_server/apps/pm/filters.py new file mode 100644 index 0000000..313c63d --- /dev/null +++ b/hb_server/apps/pm/filters.py @@ -0,0 +1,27 @@ +from django_filters import rest_framework as filters +from apps.mtm.models import Material, Step +from apps.pm.models import SubProductionProgress +from apps.wpm.models import Operation, WProduct + +from apps.wpm.services import WpmServies + + +class SubproductionProgressFilterSet(filters.FilterSet): + + operation = filters.NumberFilter(method='filter_operation') + class Meta: + model = SubProductionProgress + fields = ['material', 'subproduction_plan', 'operation', 'type'] + + def filter_operation(self, queryset, name, value): + operation = Operation.objects.get(pk=value) + wproducts = WProduct.objects.filter(ow_wproduct__operation=value) + step = operation.step + if wproducts.exists(): + subplans = WpmServies.get_subplans_queryset_from_wproducts(wproducts) + else: + subplans = WpmServies.get_subplans_queyset_from_step(step) + queryset = queryset.filter(subproduction_plan__in=subplans) + if step.type == Step.STEP_TYPE_NOM: + queryset = queryset.exclude(material__type=Material.MA_TYPE_HALFGOOD) + return queryset \ No newline at end of file diff --git a/hb_server/apps/pm/migrations/0014_subproductionplan_number.py b/hb_server/apps/pm/migrations/0014_subproductionplan_number.py new file mode 100644 index 0000000..e84a4d8 --- /dev/null +++ b/hb_server/apps/pm/migrations/0014_subproductionplan_number.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-11-19 02:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0013_alter_subproductionplan_subproduction'), + ] + + operations = [ + migrations.AddField( + model_name='subproductionplan', + name='number', + field=models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='子计划编号'), + ), + ] diff --git a/hb_server/apps/pm/migrations/0015_auto_20211122_1556.py b/hb_server/apps/pm/migrations/0015_auto_20211122_1556.py new file mode 100644 index 0000000..1f00fa9 --- /dev/null +++ b/hb_server/apps/pm/migrations/0015_auto_20211122_1556.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.9 on 2021-11-22 07:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0014_subproductionplan_number'), + ] + + operations = [ + migrations.AddField( + model_name='subproductionplan', + name='main_count_ok', + field=models.IntegerField(default=0, verbose_name='合格数'), + ), + migrations.AddField( + model_name='subproductionprogress', + name='count_ok', + field=models.IntegerField(default=0, verbose_name='合格数量'), + ), + ] diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py index 663d885..9cdd6c1 100644 --- a/hb_server/apps/pm/models.py +++ b/hb_server/apps/pm/models.py @@ -44,6 +44,7 @@ class SubProductionPlan(CommonAModel): (SUBPLAN_STATE_WORKING, '生产中'), (SUBPLAN_STATE_DONE, '已完成') ) + number = models.CharField('子计划编号', max_length=50, unique=True, null=True, blank=True) production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE) subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE, related_name='subplan_subprod') start_date = models.DateField('计划开工日期') @@ -55,6 +56,7 @@ class SubProductionPlan(CommonAModel): 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) + main_count_ok = models.IntegerField('合格数', default=0) steps = models.JSONField('工艺步骤', default=list) @@ -79,3 +81,4 @@ class SubProductionProgress(BaseModel): count = models.IntegerField('应出入数') count_pick = models.IntegerField('实际领用数', default=0) count_real = models.IntegerField('实际消耗/产出数', default=0) + count_ok = models.IntegerField('合格数量', default=0) diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py index 90ff400..65980c0 100644 --- a/hb_server/apps/pm/serializers.py +++ b/hb_server/apps/pm/serializers.py @@ -55,3 +55,8 @@ class PickNeedSerializer(serializers.Serializer): class PlanDestorySerializer(serializers.Serializer): ids = serializers.ListField(child=serializers.IntegerField(), label='主计划ID列表') + +class SubproductionPlanSimpleSerializer(serializers.ModelSerializer): + class Meta: + model = SubProductionPlan + fields = ['id', 'number'] diff --git a/hb_server/apps/pm/urls.py b/hb_server/apps/pm/urls.py index f6f6279..1d3f1eb 100644 --- a/hb_server/apps/pm/urls.py +++ b/hb_server/apps/pm/urls.py @@ -1,4 +1,4 @@ -from apps.pm.views import ProductionPlanViewSet, ResourceViewSet, SubProductionPlanViewSet +from apps.pm.views import ProductionPlanViewSet, ResourceViewSet, SubProductionPlanViewSet, SubProductionProgressViewSet from django.db.models import base from rest_framework import urlpatterns from django.urls import path, include @@ -7,6 +7,7 @@ from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('production_plan', ProductionPlanViewSet, basename='production_plan') router.register('subproduction_plan', SubProductionPlanViewSet, basename='subproduction_plan') +router.register('subproduction_progress', SubProductionProgressViewSet, basename='subproduction_progress') router.register('resource', ResourceViewSet, basename='resource') urlpatterns = [ path('', include(router.urls)), diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index a86d146..aa02784 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -1,4 +1,5 @@ from datetime import timezone +from typing import List from django.db import transaction from rest_framework import serializers from rest_framework.views import APIView @@ -7,6 +8,7 @@ from apps.em.serializers import EquipmentSerializer from apps.inm.models import MaterialBatch from apps.inm.serializers import MaterialBatchSerializer from apps.mtm.models import Step, SubProduction, SubprodctionMaterial, UsedStep +from apps.pm.filters import SubproductionProgressFilterSet from apps.system.mixins import CreateUpdateModelAMixin from apps.pm.serializers import GenSubPlanSerializer, PickNeedSerializer, PlanDestorySerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer from rest_framework.mixins import CreateModelMixin, ListModelMixin, UpdateModelMixin @@ -62,12 +64,12 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel pass else: raise APIException('排产数量错误') - instance = serializer.save(create_by=request.user, product=order.product, number='JH-'+ranstr(7)) + instance = serializer.save(create_by=request.user, product=order.product, number='JH'+ranstr(7)) updateOrderPlanedCount(instance.order) return Response() @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=PlanDestorySerializer) - def destory(self, request, pk=None): + def deletes(self, request, pk=None): """ 批量物理删除 """ @@ -83,13 +85,13 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel if production_plan.is_planed: raise APIException('已生成子计划') subps = SubProduction.objects.filter(product=production_plan.product).order_by('process__number') - for i in subps: + for index, i in enumerate(subps): steps = Step.objects.filter(usedstep__subproduction=i, usedstep__subproduction__is_deleted=False, usedstep__is_deleted=False, is_deleted=False).values('id', 'number', 'name', 'usedstep__remark') instance = SubProductionPlan.objects.create(production_plan=production_plan, subproduction=i, 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)) + steps = list(steps), number=production_plan.number + '-' + str(index+1)) # 生成子计划物料需求/进度 for m in SubprodctionMaterial.objects.filter(subproduction=i, is_deleted=False).order_by('sort'): spro = SubProductionProgress.objects.create(material=m.material, type=m.type, @@ -178,6 +180,17 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo have = MaterialBatchSerializer(instance=objs, many=True).data return Response({'need':need, 'have':have}) +class SubProductionProgressViewSet(ListModelMixin, GenericViewSet): + """ + 生产进度 + """ + perms_map = {'*': '*'} + queryset = SubProductionProgress.objects.select_related('material', 'subproduction_plan') + search_fields = [] + serializer_class = SubProductionProgressSerializer + filterset_class = SubproductionProgressFilterSet + ordering_fields = ['id'] + ordering = ['id'] class ResourceViewSet(GenericViewSet): diff --git a/hb_server/apps/qm/migrations/0011_auto_20211119_0848.py b/hb_server/apps/qm/migrations/0011_auto_20211119_0848.py new file mode 100644 index 0000000..a643fe0 --- /dev/null +++ b/hb_server/apps/qm/migrations/0011_auto_20211119_0848.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.9 on 2021-11-19 00:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0013_alter_subproductionplan_subproduction'), + ('qm', '0010_rename_m_state_testrecord_material'), + ] + + operations = [ + migrations.AddField( + model_name='testrecord', + name='is_testok_robot', + field=models.BooleanField(default=True, verbose_name='自动判定的是否合格'), + ), + migrations.AddField( + model_name='testrecord', + name='number', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='产品编号'), + ), + migrations.AddField( + model_name='testrecord', + name='subproduction_plan', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pm.subproductionplan', verbose_name='关联的生产子计划'), + ), + migrations.AddField( + model_name='testrecorditem', + name='is_testok_robot', + field=models.BooleanField(blank=True, null=True, verbose_name='自动判定的是否合格'), + ), + ] diff --git a/hb_server/apps/qm/models.py b/hb_server/apps/qm/models.py index fb61f53..c2dea88 100644 --- a/hb_server/apps/qm/models.py +++ b/hb_server/apps/qm/models.py @@ -49,8 +49,11 @@ class TestRecord(CommonAModel): """ form = models.ForeignKey('mtm.recordform', verbose_name='所用表格', on_delete=models.CASCADE) is_testok = models.BooleanField('是否合格', default=True) + is_testok_robot = models.BooleanField('自动判定的是否合格', default=True) + number = models.CharField('产品编号', null=True, blank=True, max_length=50) wproduct = models.ForeignKey('wpm.wproduct', verbose_name='关联的动态产品', on_delete=models.CASCADE, null=True, blank=True) material = models.ForeignKey('mtm.material', verbose_name='关联的物料状态', on_delete=models.CASCADE, null=True, blank=True) + subproduction_plan = models.ForeignKey('pm.subproductionplan', verbose_name='关联的生产子计划', on_delete=models.CASCADE, null=True, blank=True) fifo_item = models.ForeignKey('inm.fifoitem', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True) remark = models.TextField('备注', default='') @@ -66,5 +69,6 @@ class TestRecordItem(BaseModel): field_value = models.JSONField('录入值', default=dict, blank=True) need_judge = models.BooleanField('是否需要判定', default=False) sort = models.IntegerField('排序号', default=1) - is_testok = models.BooleanField('是否合格', null=True, blank=True) + is_testok = models.BooleanField('是否合格', null=True, blank=True) + is_testok_robot = models.BooleanField('自动判定的是否合格', null=True, blank=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/wpm/filters.py b/hb_server/apps/wpm/filters.py new file mode 100644 index 0000000..eac725e --- /dev/null +++ b/hb_server/apps/wpm/filters.py @@ -0,0 +1,23 @@ +from django_filters import rest_framework as filters +from apps.mtm.models import Material, Step + +from apps.wpm.services import WpmServies +from .models import Operation, WMaterial, WProduct + +class WMaterialFilterSet(filters.FilterSet): + + operation = filters.NumberFilter(method='filter_operation') + class Meta: + model = WMaterial + fields = ['material', 'subproduction_plan', 'subproduction_plan__process', 'subproduction_plan__workshop', 'operation'] + + def filter_operation(self, queryset, name, value): + operation = Operation.objects.get(pk=value) + wproducts = WProduct.objects.filter(ow_wproduct__operation=value) + step = operation.step + if wproducts.exists(): + subplans = WpmServies.get_subplans_queryset_from_wproducts(wproducts) + else: + subplans = WpmServies.get_subplans_queyset_from_step(step) + queryset = queryset.filter(subproduction_plan__in=subplans).exclude(material__type=Material.MA_TYPE_HALFGOOD) + return queryset \ No newline at end of file diff --git a/hb_server/apps/wpm/migrations/0016_auto_20211119_0848.py b/hb_server/apps/wpm/migrations/0016_auto_20211119_0848.py new file mode 100644 index 0000000..e632e92 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0016_auto_20211119_0848.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.9 on 2021-11-19 00:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0015_auto_20211117_2332'), + ] + + operations = [ + migrations.RemoveField( + model_name='operation', + name='wproducts', + ), + migrations.AddField( + model_name='operation', + name='is_submited', + field=models.BooleanField(default=True, verbose_name='是否提交'), + ), + migrations.AddField( + model_name='wproduct', + name='operation', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='current_operation', to='wpm.operation', verbose_name='关联操作'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0017_auto_20211119_1034.py b/hb_server/apps/wpm/migrations/0017_auto_20211119_1034.py new file mode 100644 index 0000000..bbbaeaa --- /dev/null +++ b/hb_server/apps/wpm/migrations/0017_auto_20211119_1034.py @@ -0,0 +1,49 @@ +# Generated by Django 3.2.9 on 2021-11-19 02:34 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('em', '0009_auto_20210916_1108'), + ('wpm', '0016_auto_20211119_0848'), + ] + + operations = [ + migrations.RemoveField( + model_name='operationrecord', + name='create_by', + ), + migrations.RemoveField( + model_name='operationrecord', + name='update_by', + ), + migrations.AddField( + model_name='operationrecord', + name='is_filled', + field=models.BooleanField(default=True, verbose_name='是否填写'), + ), + migrations.AlterField( + model_name='operation', + name='is_submited', + field=models.BooleanField(default=False, verbose_name='是否提交'), + ), + migrations.CreateModel( + name='OperationEquip', + 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='删除标记')), + ('remark', models.TextField(blank=True, null=True, verbose_name='备注')), + ('equip', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='em.equipment', verbose_name='生产设备')), + ('operation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.operation', verbose_name='关联操作')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/hb_server/apps/wpm/migrations/0018_auto_20211119_1340.py b/hb_server/apps/wpm/migrations/0018_auto_20211119_1340.py new file mode 100644 index 0000000..07b428c --- /dev/null +++ b/hb_server/apps/wpm/migrations/0018_auto_20211119_1340.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.9 on 2021-11-19 05:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0017_auto_20211119_1034'), + ] + + operations = [ + migrations.AlterField( + model_name='operationequip', + name='operation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='oe_operation', to='wpm.operation', verbose_name='关联操作'), + ), + migrations.AlterField( + model_name='operationrecord', + name='operation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='or_operation', to='wpm.operation', verbose_name='关联的生产操作'), + ), + migrations.AlterField( + model_name='operationwproduct', + name='operation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ow_operation', to='wpm.operation', verbose_name='关联操作'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0019_auto_20211122_1110.py b/hb_server/apps/wpm/migrations/0019_auto_20211122_1110.py new file mode 100644 index 0000000..aaffb4a --- /dev/null +++ b/hb_server/apps/wpm/migrations/0019_auto_20211122_1110.py @@ -0,0 +1,46 @@ +# Generated by Django 3.2.9 on 2021-11-22 03:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0035_auto_20211117_1637'), + ('pm', '0014_subproductionplan_number'), + ('wpm', '0018_auto_20211119_1340'), + ] + + operations = [ + migrations.AddField( + model_name='operationmaterial', + name='batch', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='批次号'), + ), + migrations.AddField( + model_name='operationmaterial', + name='subproduction_plan', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pm.subproductionplan', verbose_name='关联的子计划'), + ), + migrations.AddField( + model_name='operationmaterial', + name='subproduction_progress', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pm.subproductionprogress', verbose_name='关联的生产进度'), + ), + migrations.AlterField( + model_name='operationmaterial', + name='material', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='可能产出的产品'), + ), + migrations.AlterField( + model_name='operationmaterial', + name='type', + field=models.IntegerField(choices=[(1, '输入物料'), (2, '输出物料'), (3, '工具工装')], default=0, verbose_name='类型'), + ), + migrations.AlterField( + model_name='wproduct', + name='subproduction_plan', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wproduct_subplan', to='pm.subproductionplan', verbose_name='当前子生产计划'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0020_alter_operationwproduct_wproduct.py b/hb_server/apps/wpm/migrations/0020_alter_operationwproduct_wproduct.py new file mode 100644 index 0000000..45929df --- /dev/null +++ b/hb_server/apps/wpm/migrations/0020_alter_operationwproduct_wproduct.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.9 on 2021-11-22 07:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0019_auto_20211122_1110'), + ] + + operations = [ + migrations.AlterField( + model_name='operationwproduct', + name='wproduct', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ow_wproduct', to='wpm.wproduct', verbose_name='关联半成品'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0021_auto_20211123_0945.py b/hb_server/apps/wpm/migrations/0021_auto_20211123_0945.py new file mode 100644 index 0000000..c8b651a --- /dev/null +++ b/hb_server/apps/wpm/migrations/0021_auto_20211123_0945.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.9 on 2021-11-23 01:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0020_alter_operationwproduct_wproduct'), + ] + + operations = [ + migrations.RemoveField( + model_name='operationwproduct', + name='production_plan', + ), + migrations.RemoveField( + model_name='wproduct', + name='production_plan', + ), + ] diff --git a/hb_server/apps/wpm/migrations/0022_auto_20211123_1425.py b/hb_server/apps/wpm/migrations/0022_auto_20211123_1425.py new file mode 100644 index 0000000..bf6b8de --- /dev/null +++ b/hb_server/apps/wpm/migrations/0022_auto_20211123_1425.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.9 on 2021-11-23 06:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0021_auto_20211123_0945'), + ] + + operations = [ + migrations.RemoveField( + model_name='wproduct', + name='parent', + ), + migrations.AddField( + model_name='wproduct', + name='child', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.wproduct'), + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index 8ec6790..db98eab 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -3,12 +3,13 @@ from django.db.models.base import Model import django.utils.timezone as timezone from django.db.models.query import QuerySet from apps.inm.models import WareHouse -from apps.pm.models import ProductionPlan, SubProductionPlan -from apps.system.models import CommonAModel, CommonBModel, Organization, User, Dict, File +from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress +from apps.system.models import CommonADModel, 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, RecordFormField, Step, RecordForm +from apps.mtm.models import Material, Process, RecordFormField, Step, RecordForm, SubprodctionMaterial from django.core.validators import MinValueValidator +from apps.em.models import Equipment class WMaterial(BaseModel): """ 车间生产物料 @@ -39,54 +40,56 @@ class WProduct(CommonAModel): 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) + child = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE) 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) + subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE, related_name='wproduct_subplan') warehouse = models.ForeignKey(WareHouse, verbose_name='所在仓库', on_delete=models.SET_NULL, null=True, blank=True) + operation = models.ForeignKey('wpm.operation', verbose_name='关联操作', + on_delete=models.SET_NULL, null=True, blank=True, related_name='current_operation') -class Operation(CommonAModel): +class Operation(CommonADModel): """ 生产操作 """ - wproducts = models.ManyToManyField(WProduct, verbose_name='关联半成品', through='wpm.operationwproduct') step = models.ForeignKey(Step, verbose_name='操作步骤', on_delete=models.CASCADE, null=True, blank=True) use_scrap = models.BooleanField('是否使用的边角料', default=False) remark = models.CharField('操作备注', max_length=200, null=True, blank=True) + is_submited = models.BooleanField('是否提交', default=False) class OperationWproduct(BaseModel): """ 生产操作半成品关联表 """ - operation = models.ForeignKey(Operation, verbose_name='关联操作', on_delete=models.CASCADE) - wproduct = models.ForeignKey(WProduct, verbose_name='关联半成品', on_delete=models.CASCADE) + operation = models.ForeignKey(Operation, verbose_name='关联操作', on_delete=models.CASCADE, related_name='ow_operation') + wproduct = models.ForeignKey(WProduct, verbose_name='关联半成品', on_delete=models.CASCADE, related_name='ow_wproduct') number = models.CharField('物品编号', null=True, blank=True, max_length=50) material = models.ForeignKey(Material, verbose_name='操作时的物料状态', on_delete=models.CASCADE) subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE) - production_plan = models.ForeignKey(ProductionPlan, verbose_name='当前主生产计划', on_delete=models.CASCADE) class OperationMaterial(BaseModel): """ 生产操作物料消耗产出表 """ - type_choices=( - (1, '消耗'), - (2, '产出') - ) - type = models.IntegerField('类型', default=0, choices=type_choices) + type = models.IntegerField('类型', default=0, choices=SubprodctionMaterial.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) + + material = models.ForeignKey(Material, verbose_name='可能产出的产品', on_delete=models.CASCADE, null=True, blank=True) count = models.IntegerField('消耗或产出数量', validators=[MinValueValidator(0)]) -class OperationRecord(CommonAModel): + wmaterial = models.ForeignKey(WMaterial, verbose_name='关联的车间物料', on_delete=models.CASCADE, null=True, blank=True) + subproduction_progress = models.ForeignKey(SubProductionProgress, verbose_name='关联的生产进度', on_delete=models.CASCADE, null=True, blank=True) + + subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联的子计划', on_delete=models.CASCADE, null=True, blank=True) + batch = models.CharField('批次号', max_length=100, null=True, blank=True) + +class OperationRecord(BaseModel): """ 记录表格 """ form = models.ForeignKey(RecordForm, verbose_name='所用的生产记录表格', on_delete=models.CASCADE) - operation = models.ForeignKey(Operation, verbose_name='关联的生产操作', on_delete=models.CASCADE) - + operation = models.ForeignKey(Operation, verbose_name='关联的生产操作', on_delete=models.CASCADE, related_name='or_operation') + is_filled = models.BooleanField('是否填写', default=True) class OperationRecordItem(BaseModel): """ @@ -100,3 +103,7 @@ class OperationRecordItem(BaseModel): sort = models.IntegerField('排序号', default=1) operation_record = models.ForeignKey(OperationRecord, verbose_name='关联的生产记录', on_delete=models.CASCADE) +class OperationEquip(BaseModel): + operation = models.ForeignKey(Operation, verbose_name='关联操作', on_delete=models.CASCADE, related_name='oe_operation') + equip = models.ForeignKey(Equipment, verbose_name='生产设备', on_delete=models.CASCADE) + remark = models.TextField('备注', null=True, blank=True) \ No newline at end of file diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index fc6be42..ed2850a 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -1,19 +1,22 @@ from rest_framework import serializers, exceptions from rest_framework.serializers import ModelSerializer +from apps.em.serializers import EquipmentSimpleSerializer from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, MaterialBatch, WareHouse from apps.inm.signals import update_inm -from apps.mtm.models import Material, RecordForm, Step, SubprodctionMaterial -from apps.mtm.serializers import MaterialSimpleSerializer, StepSimpleSerializer +from apps.mtm.models import Material, RecordForm, RecordFormField, Step, SubprodctionMaterial +from apps.mtm.serializers import MaterialSimpleSerializer, RecordFormSimpleSerializer, StepSimpleSerializer from apps.pm.models import SubProductionPlan, SubProductionProgress from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from apps.pm.serializers import SubproductionPlanSimpleSerializer from apps.qm.models import TestRecord, TestRecordItem from apps.system.serializers import UserSimpleSerializer -from apps.wpm.models import Operation, WMaterial, WProduct, OperationRecord, OperationRecordItem +from apps.wpm.models import Operation, OperationEquip, OperationMaterial, OperationWproduct, WMaterial, WProduct, OperationRecord, OperationRecordItem from django.db import transaction class PickHalfSerializer(serializers.Serializer): + id = serializers.PrimaryKeyRelatedField(queryset=SubProductionProgress.objects.all(), label='子计划进度ID') wproducts = serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all(), label='半成品ID'), required=False) # 从半成品表里直接修改状态 class PickDetailSerializer(serializers.Serializer): @@ -129,25 +132,68 @@ class WProductListSerializer(serializers.ModelSerializer): fields = '__all__' class OperationDetailSerializer(serializers.ModelSerializer): - wproducts_ = serializers.SerializerMethodField() create_by_ = UserSimpleSerializer(source='create_by', read_only=True) - material_ = MaterialSimpleSerializer(source='material', read_only=True) step_ = StepSimpleSerializer(source='step', 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) - material_ = MaterialSimpleSerializer(source='material', read_only=True) step_ = StepSimpleSerializer(source='step', read_only=True) + wproduct_count = serializers.SerializerMethodField() + equip_count = serializers.SerializerMethodField() + record_count = serializers.SerializerMethodField() class Meta: model = Operation fields = '__all__' + def get_wproduct_count(self, obj): + return obj.ow_operation.count() + + def get_equip_count(self, obj): + return obj.oe_operation.count() + + def get_record_count(self, obj): + return obj.or_operation.count() + +class OperationCreateSerializer(serializers.Serializer): + """ + 操作创建 + """ + step = serializers.PrimaryKeyRelatedField(queryset=Step.objects.all(), label="子工序ID") + # subproduction_plan = serializers.PrimaryKeyRelatedField(queryset=SubProductionPlan.objects.all(), label="子计划ID", required=False) + 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 exceptions.ValidationError('请选择正确的子工序操作') + + if 'wproducts' in data and data['wproducts']: + if step.type == Step.STEP_TYPE_DIV: + raise exceptions.ValidationError(_('不可进行此操作')) + for i in data['wproducts']: + if i.is_executed: + raise exceptions.ValidationError('不可进行操作') + # if i.subproduction_plan != subproduction_plan: + # raise exceptions.ValidationError('半成品所属子计划不一致') + if i.step != step: + raise exceptions.ValidationError('半成品所属子工序不一致') + else: + if step.type != Step.STEP_TYPE_DIV: + raise exceptions.ValidationError(_('请选择半成品进行操作')) + return data + + +class OperationUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = Operation + fields =['use_scrap', 'remark'] class OperationInitSerializer(serializers.Serializer): step = serializers.PrimaryKeyRelatedField(queryset=Step.objects.all(), label="子工序ID") @@ -193,12 +239,24 @@ class OperationRecordItemSerializer(serializers.ModelSerializer): model = OperationRecordItem fields = ['form_field', 'field_value'] +class OperationRecordSubmitSerializer(serializers.ModelSerializer): + record_data = OperationRecordItemSerializer(many=True) + class Meta: + model = OperationRecord + fields = ['record_data'] + class OperationRecordSerializer(serializers.ModelSerializer): record_data = OperationRecordItemSerializer(many=True) class Meta: model = OperationRecord fields = ['form', 'record_data'] +class OperationWproductListSerializer(serializers.ModelSerializer): + material_ = MaterialSimpleSerializer(source='material', read_only=True) + subproduction_plan_ = SubproductionPlanSimpleSerializer(source='subproduction_plan', read_only=True) + class Meta: + model = OperationWproduct + fields = '__all__' class OperationSubmitSerializer(serializers.Serializer): @@ -237,6 +295,66 @@ class WplanPutInSerializer(serializers.Serializer): class WproductPutInSerializer(serializers.Serializer): warehouse = serializers.PrimaryKeyRelatedField(queryset=WareHouse.objects.all(), label="仓库ID") remark = serializers.CharField(label="入库备注", required =False) + +class OperationEquipListSerializer(serializers.Serializer): + equip_ = EquipmentSimpleSerializer(source='equip', read_only=True) + class Meta: + model = OperationEquip + fields = '__all__' + +class OperationEquipUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = OperationEquip + fields = ['remark'] + +class OperationRecordListSerializer(serializers.ModelSerializer): + form_ = RecordFormSimpleSerializer(source='form', read_only=True) + class Meta: + model = OperationRecord + fields = '__all__' + +class OperationMaterialListSerializer(serializers.ModelSerializer): + material_ = MaterialSimpleSerializer(source='material', read_only=True) + subproduction_plan_ = SubproductionPlanSimpleSerializer(source='subproduction_plan', read_only=True) + class Meta: + model = OperationMaterial + fields = '__all__' + +class OperationMaterialCreate1Serailizer(serializers.ModelSerializer): + wmaterial = serializers.PrimaryKeyRelatedField(required=True, queryset=WMaterial.objects.all()) + class Meta: + model = OperationMaterial + fields = ['operation', 'wmaterial', 'count'] + + def create(self, validated_data): + wmaterial = validated_data['wmaterial'] + validated_data['material'] = wmaterial.material + validated_data['subproduction_plan'] = wmaterial.subproduction_plan + validated_data['batch'] = wmaterial.batch + validated_data['type'] = SubprodctionMaterial.SUB_MA_TYPE_IN + return super().create(validated_data) +class OperationMaterialCreate2Serailizer(serializers.ModelSerializer): + subproduction_progress = serializers.PrimaryKeyRelatedField(required=True, queryset=SubProductionProgress.objects.all()) + class Meta: + model = OperationMaterial + fields = ['operation', 'subproduction_progress', 'count'] + + def create(self, validated_data): + subproduction_progress = validated_data['subproduction_progress'] + validated_data['material'] = subproduction_progress.material + validated_data['subproduction_plan'] = subproduction_progress.subproduction_plan + validated_data['type'] = SubprodctionMaterial.SUB_MA_TYPE_OUT + return super().create(validated_data) + +class OperationMaterialCreate3Serializer(serializers.ModelSerializer): + material = serializers.PrimaryKeyRelatedField(required=True, queryset=Material.objects.all()) + class Meta: + model = OperationMaterial + fields = ['operation', 'material'] + + def create(self, validated_data): + validated_data['type'] = SubprodctionMaterial.SUB_MA_TYPE_TOOL + return super().create(validated_data) \ No newline at end of file diff --git a/hb_server/apps/wpm/services.py b/hb_server/apps/wpm/services.py index de6e5c8..20f2870 100644 --- a/hb_server/apps/wpm/services.py +++ b/hb_server/apps/wpm/services.py @@ -1,5 +1,7 @@ +from typing import List from apps.pm.models import SubProductionPlan -from apps.mtm.models import Step +from apps.mtm.models import Step, SubprodctionMaterial +from apps.wpm.models import WProduct class WpmServies(object): @classmethod @@ -12,4 +14,21 @@ class WpmServies(object): if pindex + 1 < len(stepIds): return Step.objects.get(pk=stepIds[pindex+1]), True else: - return nowstep, False \ No newline at end of file + return nowstep, False + + @classmethod + def get_subplans_queryset_from_wproducts(cls, wproducts:List): + """ + 通过半成品列表获取所属子计划 + """ + splans = SubProductionPlan.objects.filter(is_deleted=False, wproduct_subplan__in=wproducts) + return splans + + @classmethod + def get_subplans_queyset_from_step(cls, step:Step): + """ + 通过当前操作获取所有正在进行的子计划 + """ + splans = SubProductionPlan.objects.filter(is_deleted=False, + subproduction__usedstep_subproduction__step=step, state=SubProductionPlan.SUBPLAN_STATE_WORKING) + return splans \ No newline at end of file diff --git a/hb_server/apps/wpm/urls.py b/hb_server/apps/wpm/urls.py index 7c43ac7..57db6b9 100644 --- a/hb_server/apps/wpm/urls.py +++ b/hb_server/apps/wpm/urls.py @@ -3,12 +3,18 @@ from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.wpm.views import DoFormInit, DoFormSubmit, OperationViewSet, WMaterialViewSet, WPlanViewSet, WProductViewSet +from apps.wpm.views import DoFormInit, DoFormSubmit, OperationEquipViewSet, OperationMaterialInputViewSet, OperationMaterialOutputViewSet, OperationMaterialToolViewSet, OperationRecordViewSet, OperationViewSet, OperationWproductViewSet, 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('operation_wproduct', OperationWproductViewSet, basename='operation_wproduct') +router.register('operation_equip', OperationEquipViewSet, basename='operation_equip') +router.register('operation_record', OperationRecordViewSet, basename='operation_record') +router.register('operation_input', OperationMaterialInputViewSet, basename='operation_input') +router.register('operation_output', OperationMaterialOutputViewSet, basename='operation_output') +router.register('operation_tool', OperationMaterialToolViewSet, basename='operation_tool') router.register('subplan', WPlanViewSet, basename='wplan') urlpatterns = [ path('do/init/', DoFormInit.as_view()), diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index 1b63890..cdb81de 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -1,23 +1,24 @@ from django.shortcuts import render from rest_framework.generics import CreateAPIView, GenericAPIView -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin +from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin 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.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, WareHouse from apps.inm.signals import update_inm -from apps.mtm.models import Material, RecordForm, Step, SubprodctionMaterial, TechDoc +from apps.mtm.models import Material, RecordForm, RecordFormField, Step, SubprodctionMaterial, TechDoc from apps.mtm.serializers import RecordFormDetailSerializer, SubprodctionMaterialListSerializer, TechDocListSerializer from apps.pm.models import SubProductionPlan, SubProductionProgress -from apps.pm.serializers import SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer +from apps.pm.serializers import SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer from apps.qm.models import TestRecordItem from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action -from apps.wpm.models import OperationWproduct, WMaterial, WProduct, Operation, OperationMaterial, OperationRecord, OperationRecordItem +from apps.wpm.filters import WMaterialFilterSet +from apps.wpm.models import OperationEquip, OperationWproduct, WMaterial, WProduct, Operation, OperationMaterial, OperationRecord, OperationRecordItem -from apps.wpm.serializers import OperationDetailSerializer, OperationListSerializer, PickHalfSerializer, PickSerializer, OperationInitSerializer, OperationSubmitSerializer, WMaterialListSerializer, WProductListSerializer, WplanPutInSerializer, WpmTestRecordCreateSerializer, WproductPutInSerializer +from apps.wpm.serializers import OperationEquipListSerializer, OperationEquipUpdateSerializer, OperationMaterialCreate1Serailizer, OperationMaterialCreate2Serailizer, OperationMaterialCreate3Serializer, OperationMaterialListSerializer, OperationRecordListSerializer, OperationRecordSubmitSerializer, OperationUpdateSerializer, OperationWproductListSerializer, OperationCreateSerializer, OperationDetailSerializer, OperationListSerializer, PickHalfSerializer, PickSerializer, OperationInitSerializer, OperationSubmitSerializer, WMaterialListSerializer, WProductListSerializer, WplanPutInSerializer, WpmTestRecordCreateSerializer, WproductPutInSerializer from rest_framework.response import Response from django.db import transaction from rest_framework import exceptions, serializers @@ -25,6 +26,7 @@ from rest_framework import exceptions, serializers from apps.wpm.services import WpmServies from django.utils import timezone from utils.tools import ranstr +from rest_framework import status # Create your views here. class WPlanViewSet(ListModelMixin, GenericViewSet): """ @@ -49,19 +51,25 @@ class WPlanViewSet(ListModelMixin, GenericViewSet): """ 领半成品 """ - mIds = SubProductionProgress.objects.filter(type=SubprodctionMaterial.SUB_MA_TYPE_IN, - material__type=Material.MA_TYPE_HALFGOOD).values_list('material', flat=True) - queyset = WProduct.objects.filter(is_hidden=False, material__in=mIds, act_state=WProduct.WPR_ACT_STATE_OK) - return Response(WProductListSerializer(instance=queyset, many=True).data) + spps = SubProductionProgress.objects.filter(type=SubprodctionMaterial.SUB_MA_TYPE_IN, + material__type=Material.MA_TYPE_HALFGOOD, subproduction_plan=sp).select_related('material') + return Response(SubProductionProgressSerializer(instance=spps, many=True).data) elif request.method=='POST': - serializer= PickHalfSerializer(data=request.data) + serializer= PickHalfSerializer(data=request.data, many=True) serializer.is_valid(raise_exception=True) vdata = serializer.data - wps = WProduct.objects.filter(pk__in=[x for x in vdata['wproducts']]) - first_step = Step.objects.get(pk=sp.steps[0].id) - wps.update(step=first_step, is_executed=False, - act_state=WProduct.WPR_ACT_STATE_DOING, is_hidden=False, warehouse=None, - subproduction_plan=sp, production_plan=sp.production_plan) + first_step = Step.objects.get(pk=sp.steps[0]['id']) + for i in vdata: + if 'wproducts' in i and len(i['wproducts'])>0: + spp = SubProductionProgress.objects.get(pk=i['id']) + spp.count_pick = spp.count_pick + len(i['wproducts']) + if spp.count_pick > spp.count: + raise exceptions.APIException('超过计划数') + spp.save() + wps = WProduct.objects.filter(pk__in=[x for x in i['wproducts']]) + wps.update(step=first_step, is_executed=False, + act_state=WProduct.WPR_ACT_STATE_DOING, is_hidden=False, warehouse=None, + subproduction_plan=sp, update_by=request.user, update_time=timezone.now()) return Response() @@ -76,7 +84,7 @@ class WPlanViewSet(ListModelMixin, GenericViewSet): vdata = serializer.data subplan = self.get_object() material = subplan.main_product - batch = subplan.production_plan.number + batch = subplan.number warehouse = WareHouse.objects.get(id=vdata['warehouse']) wproducts = WProduct.objects.filter(subproduction_plan=subplan, act_state=WProduct.WPR_ACT_STATE_OK, material=material, is_deleted=False) @@ -119,7 +127,7 @@ class WPlanViewSet(ListModelMixin, GenericViewSet): IProduct.objects.bulk_create(ips2) # 更新库存并修改半成品进行状态 update_inm(fifo) - wproducts.update(act_sate=WProduct.WPR_ACT_STATE_INM, warehouse=warehouse) + wproducts.update(act_sate=WProduct.WPR_ACT_STATE_INM, warehouse=warehouse, update_by=request.user, update_time=timezone.now()) return Response() @@ -131,7 +139,7 @@ class WMaterialViewSet(CreateUpdateModelAMixin, ListModelMixin, GenericViewSet): perms_map={'*':'*'} queryset = WMaterial.objects.select_related('material').all() serializer_class = WMaterialListSerializer - filterset_fields = ['material', 'subproduction_plan', 'subproduction_plan__process', 'subproduction_plan__workshop'] + filterset_class = WMaterialFilterSet ordering_fields = ['material__number'] ordering = ['material__number'] @@ -150,9 +158,9 @@ class WProductViewSet(ListModelMixin, GenericViewSet): 半成品 """ perms_map={'*':'*'} - queryset = WProduct.objects.select_related('step', 'material').filter(is_hidden=False) + queryset = WProduct.objects.select_related('step', 'material').filter(is_hidden=False, operation=None) serializer_class = WProductListSerializer - filterset_fields = ['step', 'subproduction_plan', 'material', 'production_plan', 'step__process', 'act_state'] + filterset_fields = ['step', 'subproduction_plan', 'material', 'step__process', 'act_state'] search_fields = ['number'] ordering_fields = ['id'] ordering = ['id'] @@ -193,13 +201,13 @@ class WProductViewSet(ListModelMixin, GenericViewSet): if obj.is_testok: wproduct.act_state = WProduct.WPR_ACT_STATE_OK if wproduct.number is None: # 产生半成品编号 - wproduct.number = 'WP-'+ranstr(7) + wproduct.number = 'WP'+ranstr(7) wproduct.save() # 更新子计划状态 # 更新子计划主产品数 instance = SubProductionProgress.objects.get(subproduction_plan=wproduct.subproduction_plan, is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) - instance.count_real = instance.count_real + 1 # 这个地方可能会有问题 + instance.count_ok = instance.count_ok + 1 # 这个地方可能会有问题 instance.save() else:# 如果不合格 pass @@ -220,7 +228,7 @@ class WProductViewSet(ListModelMixin, GenericViewSet): if wproduct.act_state != WProduct.WPR_ACT_STATE_OK: raise exceptions.APIException('半成品不可入库') material = wproduct.material - batch = wproduct.production_plan.number + batch = wproduct.subproduction_plan.number # 创建入库记录 remark = vdata.get('remark', '') fifo = FIFO.objects.create(type=FIFO.FIFO_TYPE_DO_IN, @@ -253,22 +261,353 @@ class WProductViewSet(ListModelMixin, GenericViewSet): wproduct.save() return Response() -class OperationViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): +class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): """ 生产操作记录 """ perms_map={'*':'*'} - queryset = Operation.objects.select_related('step', 'material').all() + queryset = Operation.objects.select_related('step').prefetch_related('ow_operation', 'oe_operation', 'or_operation').all() serializer_class = OperationListSerializer - filterset_fields = ['step', 'step__process'] + filterset_fields = ['step', 'step__process', 'is_submited'] ordering_fields = ['id'] ordering = ['-id'] + def get_queryset(self): + return self.queryset.filter(create_by=self.request.user) + def get_serializer_class(self): if self.action == 'retrieve': return OperationDetailSerializer + elif self.action == 'create': + return OperationCreateSerializer + elif self.action == 'update': + return OperationUpdateSerializer return super().get_serializer_class() + def update(self, request, *args, **kwargs): + instance = self.get_object() + if instance.is_submited: + raise exceptions.APIException('该操作已提交') + return super().update(request, *args, **kwargs) + + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.is_submited: + raise exceptions.APIException('该操作已提交') + self.perform_destroy(instance) + return Response(status=status.HTTP_204_NO_CONTENT) + + @transaction.atomic + def create(self, request, *args, **kwargs): + data = request.data + serializer = OperationCreateSerializer(data=data, context={'request':self.request}) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data #校验之后的数据 + step = vdata['step'] + op = Operation() + op.step = step + op.is_submited = False + op.create_by = request.user + op.save() + splans = [] + # 创建操作所用半成品关联记录 + if 'wproducts' in vdata: + owps = [] + splans = WpmServies.get_subplans_queryset_from_wproducts(vdata['wproducts']) + for wpd in vdata['wproducts']: + owp = {} + owp['operation'] = op + owp['wproduct'] = wpd + owp['number'] = wpd.number + owp['material'] = wpd.material + owp['subproduction_plan'] = wpd.subproduction_plan + owps.append(OperationWproduct(**owp)) + OperationWproduct.objects.bulk_create(owps) + else: + splans = WpmServies.get_subplans_queryset_from_wproducts(vdata['wproducts']) + # 查询需要填写的自定义表格 + forms = RecordForm.objects.filter(step=step, type=RecordForm.RF_TYPE_DO) + for i in forms: + opr = OperationRecord() + opr.operation = op + opr.form = i + opr.is_filled = False + opr.save() + # 查询需要使用的生产设备 + for i in step.equipments.all(): + ope = OperationEquip() + ope.operation = op + ope.equip = i + ope.save() + # 查询所需的工具工装 + for i in SubprodctionMaterial.objects.filter(type=SubprodctionMaterial.SUB_MA_TYPE_TOOL, + subproduction__subplan_subprod__in = splans, is_deleted=False).distinct(): + opm = OperationMaterial() + opm.operation = op + opm.material = i.material + opm.type = SubprodctionMaterial.SUB_MA_TYPE_TOOL + opm.save() + return Response() + + @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=serializers.Serializer) + @transaction.atomic + def submit(self, request, pk=None): + """ + 提交车间操作重要 + """ + op = self.get_object() + step = op.step + # 检查自定义表单填写 + if OperationRecord.objects.filter(operation=op, is_filled=False).exists(): + raise exceptions.APIException('存在自定义表单未填写') + # 更新物料消耗进度 + for i in OperationMaterial.objects.filter(operation=op, type=SubprodctionMaterial.SUB_MA_TYPE_IN): + # 更新车间物料 + i_wmat = i.wmaterial + i_wmat.count = i_wmat.count- i.count + i_wmat.save() + # 更新子计划物料消耗情况 + spp = SubProductionProgress.objects.get(subproduction_plan=i_wmat.subproduction_plan, + material=i_wmat.material) + spp.count_real = spp.count_real + i.count + spp.save() + # 更新产出 + for i in OperationMaterial.objects.filter(operation=op, type=SubprodctionMaterial.SUB_MA_TYPE_OUT): + if not i.subproduction_progress.is_main: + # 更新车间物料产出情况 + ins, _ = WMaterial.objects.get_or_create(subproduction_plan=i.subproduction_plan, material=i.material) + ins.count = ins.count + i.count + ins.save() + # 更新子计划物料产出情况 + spp = i.subproduction_progress + spp.count_real = spp.count_real + i.count + spp.save() + # 更新动态产品表 + if step.type == Step.STEP_TYPE_NOM: + for i in OperationWproduct.objects.filter(operation=op): + wp = i.wproduct + wsp = i.subproduction_plan + # 获取下一步子工序 + newstep, hasNext = WpmServies.get_next_step(wsp, step) + wp.step = newstep + wp.pre_step = step + if hasNext: + wp.is_executed= False + else: + wp.is_executed = True + wp.act_state = WProduct.WPR_ACT_STATE_TOTEST + wp.material = wsp.main_product + # 更新子计划进度 + instance = SubProductionProgress.objects.get(subproduction_plan=wsp, + is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) + instance.count_real = instance.count_real + 1 # 这个地方可能会有问题,不够严谨 + instance.save() + wp.operation = None + wp.save() + elif step.type == Step.STEP_TYPE_DIV: + # 更新物料产出情况 + for i in OperationMaterial.objects.filter(operation=op, type=SubprodctionMaterial.SUB_MA_TYPE_OUT): + if i.subproduction_progress.is_main: + newstep, _ = WpmServies.get_next_step(i.subproduction_plan, step) + wpr = dict(material=i.material, step=newstep, + act_state=WProduct.WPR_ACT_STATE_DOING, is_executed=False, remark='', + subproduction_plan=i.subproduction_plan) + for x in range(i.count): + WProduct.objects.create(**wpr) + elif step.type == Step.STEP_TYPE_COMB: + # 隐藏原半成品 + ows = OperationWproduct.objects.filter(operation=op) + if i.subproduction_progress.is_main: + newstep, hasNext = WpmServies.get_next_step(i.subproduction_plan, step) + wproduct = WProduct() + wproduct.material = i.material + wproduct.step = newstep + wproduct.subproduction_plan = i.subproduction_plan + if hasNext: + wproduct.act_state = WProduct.WPR_ACT_STATE_DOING + wproduct.is_executed = False + else: + wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST + wproduct.is_executed = True + # 更新子计划进度 + instance = SubProductionProgress.objects.get(subproduction_plan=i.subproduction_plan, + is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) + instance.count_real = instance.count_real + 1 # 这个地方可能会有问题,不够严谨 + instance.save() + wproduct.save() + ows.update(is_hidden=True, child=wproduct) + op.is_submited = True + op.save() + return Response() + + + +class OperationWproductViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): + """ + 操作使用的半成品 + """ + perms_map={'*':'*'} + queryset = OperationWproduct.objects.select_related('subproduction_plan', 'material').all() + serializer_class = OperationWproductListSerializer + filterset_fields = ['material', 'subproduction_plan', 'operation'] + ordering_fields = ['id'] + ordering = ['-id'] + + @transaction.atomic() + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.operation.is_submited: + raise exceptions.APIException('该操作已提交') + instance.delete() + wp = instance.wproduct + wp.operation = None + wp.save() + return Response() + +class OperationEquipViewSet(ListModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet): + """ + 操作使用的设备 + """ + perms_map={'*':'*'} + queryset = OperationEquip.objects.select_related('operation', 'equip').all() + serializer_class = OperationEquipListSerializer + filterset_fields = ['operation', 'equip'] + ordering_fields = ['id'] + ordering = ['-id'] + + def get_serializer_class(self): + if self.action == 'update': + return OperationEquipUpdateSerializer + return super().get_serializer_class() + + def update(self, request, *args, **kwargs): + instance = self.get_object() + if instance.operation.is_submited: + raise exceptions.APIException('该操作已提交') + return super().update(request, *args, **kwargs) + + @transaction.atomic() + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.operation.is_submited: + raise exceptions.APIException('该操作已提交') + instance.delete() + return Response() + +class OperationRecordViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): + """ + 操作使用的自定义表格 + """ + perms_map={'*':'*'} + queryset = OperationRecord.objects.select_related('operation', 'form').all() + serializer_class = OperationRecordListSerializer + filterset_fields = ['operation', 'form'] + ordering_fields = ['id'] + ordering = ['-id'] + + @transaction.atomic() + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.operation.is_submited: + raise exceptions.APIException('该操作已提交') + instance.delete() + return Response() + + @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=OperationRecordSubmitSerializer) + def submit(self, request, pk=None): + serializer = OperationRecordSubmitSerializer(data=request.data, context={'request':self.request}) + serializer.is_valid(raise_exception=True) + vdata = serializer.data + opr = self.get_object() + wrds = [] + for m in vdata['record_data']: # 保存记录详情 + form_field = RecordFormField.objects.get(pk=m['form_field']) + m['form_field'] = 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'] = opr + wrds.append(OperationRecordItem(**m)) + OperationRecordItem.objects.bulk_create(wrds) + opr.is_filled = True + opr.save() + return Response() + + +class OperationMaterialInputViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, GenericViewSet): + """ + 消耗物料 + """ + perms_map={'*':'*'} + queryset = OperationMaterial.objects.select_related('operation', 'subproduction_plan').filter(type=SubprodctionMaterial.SUB_MA_TYPE_IN) + serializer_class = OperationMaterialListSerializer + filterset_fields = ['operation', 'subproduction_plan'] + ordering_fields = ['id'] + ordering = ['-id'] + + def get_serializer_class(self): + if self.action == 'create': + return OperationMaterialCreate1Serailizer + return super().get_serializer_class() + + @transaction.atomic() + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.operation.is_submited: + raise exceptions.APIException('该操作已提交') + instance.delete() + return Response() + +class OperationMaterialOutputViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, GenericViewSet): + """ + 产出物料 + """ + perms_map={'*':'*'} + queryset = OperationMaterial.objects.select_related('operation', 'subproduction_plan').filter(type=SubprodctionMaterial.SUB_MA_TYPE_OUT) + serializer_class = OperationMaterialListSerializer + filterset_fields = ['operation', 'subproduction_plan'] + ordering_fields = ['id'] + ordering = ['-id'] + + def get_serializer_class(self): + if self.action == 'create': + return OperationMaterialCreate2Serailizer + return super().get_serializer_class() + + @transaction.atomic() + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.operation.is_submited: + raise exceptions.APIException('该操作已提交') + instance.delete() + return Response() + +class OperationMaterialToolViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, GenericViewSet): + """ + 工具工装 + """ + perms_map={'*':'*'} + queryset = OperationMaterial.objects.select_related('operation', 'subproduction_plan').filter(type=SubprodctionMaterial.SUB_MA_TYPE_TOOL) + serializer_class = OperationMaterialListSerializer + filterset_fields = ['operation', 'subproduction_plan'] + ordering_fields = ['id'] + ordering = ['-id'] + + def get_serializer_class(self): + if self.action == 'create': + return OperationMaterialCreate3Serializer + return super().get_serializer_class() + + @transaction.atomic() + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.operation.is_submited: + raise exceptions.APIException('该操作已提交') + instance.delete() + return Response() + class DoFormInit(CreateAPIView, GenericAPIView): perms_map={'*':'*'} serializer_class=OperationInitSerializer @@ -368,7 +707,6 @@ class DoFormSubmit(CreateAPIView, GenericAPIView): owp['number'] = wp.number owp['material'] = wp.material owp['subproduction_plan'] = wp.subproduction_plan - owp['production_plan'] = wp.production_plan owps.append(OperationWproduct(**owp)) OperationWproduct.objects.bulk_create(owps) @@ -398,8 +736,7 @@ class DoFormSubmit(CreateAPIView, GenericAPIView): newstep, _ = WpmServies.get_next_step(i['subproduction_plan'], vdata['step']) wpr = dict(material=ma, step=newstep, act_state=WProduct.WPR_ACT_STATE_DOING, is_executed=False, remark='', - subproduction_plan=i['subproduction_plan'], - production_plan=i['subproduction_plan'].production_plan) + subproduction_plan=i['subproduction_plan']) for x in range(i['count_output']): WProduct.objects.create(**wpr) else: @@ -428,7 +765,6 @@ class DoFormSubmit(CreateAPIView, GenericAPIView): wproduct.material = vdata['subproduction_plan'].main_product wproduct.step = newstep wproduct.subproduction_plan=vdata['subproduction_plan'] - wproduct.production_plan=vdata['subproduction_plan'].production_plan wproduct.parent = data['wproducts'] if hasNext: wproduct.act_state=WProduct.WPR_ACT_STATE_DOING diff --git a/hb_server/requirements.txt b/hb_server/requirements.txt index a223cbc..3d3b1be 100644 --- a/hb_server/requirements.txt +++ b/hb_server/requirements.txt @@ -1,5 +1,5 @@ celery==5.1.2 -Django==3.2.6 +Django==3.2.9 django-celery-beat==2.2.1 django-cors-headers==3.7.0 django-filter==2.4.0 diff --git a/hb_server/server/urls.py b/hb_server/server/urls.py index b5ac030..3db8e36 100644 --- a/hb_server/server/urls.py +++ b/hb_server/server/urls.py @@ -26,7 +26,7 @@ from rest_framework.documentation import include_docs_urls from rest_framework_simplejwt.views import (TokenObtainPairView, TokenRefreshView) from django.views.generic import TemplateView -from utils.view import GenSignature +from utils.view import GenSignature, UpdateDevelop router = routers.DefaultRouter() router.register('', FileViewSet, basename="file") @@ -72,6 +72,7 @@ urlpatterns = [ # 工具 path('api/utils/signature/', GenSignature.as_view()), + path('api/utils/develop/', UpdateDevelop.as_view()), # 前端页面入口 path('',TemplateView.as_view(template_name="index.html")) diff --git a/hb_server/utils/tools.py b/hb_server/utils/tools.py index 786b8f2..73a6db6 100644 --- a/hb_server/utils/tools.py +++ b/hb_server/utils/tools.py @@ -2,6 +2,6 @@ import random import string def ranstr(num): - salt = ''.join(random.sample(string.ascii_letters + string.digits, num)) + salt = ''.join(random.sample(string.ascii_lowercase + string.digits, num)) return salt ranstr(10) \ No newline at end of file diff --git a/hb_server/utils/view.py b/hb_server/utils/view.py index 23707ff..8b07565 100644 --- a/hb_server/utils/view.py +++ b/hb_server/utils/view.py @@ -49,3 +49,22 @@ class GenSignature(APIView): image[i][j][0],image[i][j][1],image[i][j][2] = 0,0,0 cv2.imwrite(path,image) return Response(request.data, status=status.HTTP_200_OK) + + +class UpdateDevelop(APIView): + """ + 更新开发服务器 + """ + authentication_classes = () + permission_classes = () + + def post(self, request, *args, **kwargs): + import os + # 更新后端 + os.chdir('/home/hberp') + ret = os.popen('git pull https://caoqianming%40ctc.ac.cn:9093qqww@e.coding.net/ctcdevteam/hberp/hberp.git develop') + # 打包前端 + # os.chdir('/home/hberp/hb_client') + # os.system('npm run build:prod') + # os.system('\cp -rf /home/hberp/hb_client/dist/* /home/hberp/hb_server/vuedist') + return Response(ret.read())