diff --git a/hb_server/apps/develop/__init__.py b/hb_server/apps/develop/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/develop/admin.py b/hb_server/apps/develop/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/hb_server/apps/develop/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/hb_server/apps/develop/apps.py b/hb_server/apps/develop/apps.py new file mode 100644 index 0000000..afbb5ed --- /dev/null +++ b/hb_server/apps/develop/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + +class DevelopConfig(AppConfig): + name = 'apps.develop' + verbose_name = '开发调试接口' + + diff --git a/hb_server/apps/develop/views.py b/hb_server/apps/develop/views.py new file mode 100644 index 0000000..69c1d41 --- /dev/null +++ b/hb_server/apps/develop/views.py @@ -0,0 +1,12 @@ +from django.shortcuts import render +from rest_framework.exceptions import APIException +from rest_framework.views import APIView +from rest_framework.viewsets import ModelViewSet + +from rest_framework import serializers, status +from rest_framework.response import Response + + + +class CleanData(APIView): + pass diff --git a/hb_server/apps/pm/migrations/0019_alter_productionplan_process_json.py b/hb_server/apps/pm/migrations/0019_alter_productionplan_process_json.py new file mode 100644 index 0000000..4bc670e --- /dev/null +++ b/hb_server/apps/pm/migrations/0019_alter_productionplan_process_json.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-12-28 06:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0018_productionplan_process_json'), + ] + + operations = [ + migrations.AlterField( + model_name='productionplan', + name='process_json', + field=models.JSONField(blank=True, default=dict, null=True, verbose_name='按工序的统计数'), + ), + ] diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index 53a6e00..9f3d8fd 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -11,7 +11,7 @@ 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 +from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin from apps.pm.models import ProductionPlan, SubProductionProgress, SubProductionPlan from rest_framework.viewsets import GenericViewSet, ModelViewSet from django.shortcuts import render @@ -34,7 +34,7 @@ def updateOrderPlanedCount(order): order.planed_count = planed_count order.save() -class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModelMixin, GenericViewSet): +class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModelMixin, RetrieveModelMixin, GenericViewSet): """ 生产计划 """ diff --git a/hb_server/apps/wpm/filters.py b/hb_server/apps/wpm/filters.py index 231a858..bf3783e 100644 --- a/hb_server/apps/wpm/filters.py +++ b/hb_server/apps/wpm/filters.py @@ -25,6 +25,7 @@ class WMaterialFilterSet(filters.FilterSet): class WProductFilterSet(filters.FilterSet): tag = filters.CharFilter(method='filter_tag') + production_plan = filters.NumberFilter(field_name='subproduction_plan__production_plan') class Meta: model = WProduct fields = ['step', 'subproduction_plan', 'material', 'step__process', 'act_state', 'material__type'] diff --git a/hb_server/apps/wpm/migrations/0039_auto_20211228_1417.py b/hb_server/apps/wpm/migrations/0039_auto_20211228_1417.py new file mode 100644 index 0000000..4d4be43 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0039_auto_20211228_1417.py @@ -0,0 +1,58 @@ +# Generated by Django 3.2.9 on 2021-12-28 06:17 + +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 = [ + ('inm', '0024_auto_20211227_0948'), + ('qm', '0022_auto_20211216_1401'), + ('mtm', '0042_alter_recordformfield_field_type'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('pm', '0019_alter_productionplan_process_json'), + ('wf', '0023_auto_20211227_1318'), + ('wpm', '0038_auto_20211227_0948'), + ] + + operations = [ + migrations.AlterField( + model_name='wprouctticket', + name='step', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.step', verbose_name='所在步骤/发现步骤'), + ), + migrations.CreateModel( + name='WproductFlow', + 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='删除标记')), + ('number', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='物品编号')), + ('act_state', models.IntegerField(choices=[(6, '待复检'), (8, '操作准备中'), (10, '操作进行中'), (20, '待检验'), (26, '待夹层检验'), (30, '已合格'), (40, '已入库'), (50, '不合格'), (60, '待成品检验'), (70, '已报废'), (80, '已售出')], default=0, verbose_name='进行状态')), + ('is_hidden', models.BooleanField(default=False, verbose_name='是否隐藏')), + ('remark', models.CharField(blank=True, max_length=200, null=True, verbose_name='备注')), + ('scrap_reason', models.IntegerField(blank=True, choices=[(10, '气泡'), (20, '破点'), (30, '划伤'), (40, '其他')], null=True, verbose_name='报废原因')), + ('ng_sign', models.PositiveSmallIntegerField(blank=True, choices=[(10, '返工'), (20, '返修'), (30, '报废'), (40, '让步接收'), (50, '偏离许可'), (60, '降级使用'), (70, '退回供方'), (80, '召回')], null=True, verbose_name='不合格标记')), + ('is_lastlog', models.BooleanField(default=True, verbose_name='是否该子计划下的最后一条日志')), + ('child', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.wproductflow')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductflow_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='所属物料状态')), + ('operation', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wpm.operation', verbose_name='当前操作')), + ('pre_step', models.ForeignKey(blank=True, help_text='已执行完的步骤', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wl_pre_step', to='mtm.step', verbose_name='已执行到')), + ('step', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wl_step', to='mtm.step', verbose_name='所在步骤')), + ('subproduction_plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pm.subproductionplan', verbose_name='当前子生产计划')), + ('test', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='qm.testrecord', verbose_name='当前检验')), + ('ticket', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wf.ticket', verbose_name='当前工单')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductflow_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ('warehouse', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inm.warehouse', verbose_name='所在仓库')), + ('wproduct', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.wproduct', verbose_name='关联产品')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/hb_server/apps/wpm/migrations/0040_alter_wproductflow_number.py b/hb_server/apps/wpm/migrations/0040_alter_wproductflow_number.py new file mode 100644 index 0000000..b6f9153 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0040_alter_wproductflow_number.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-12-28 07:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0039_auto_20211228_1417'), + ] + + operations = [ + migrations.AlterField( + model_name='wproductflow', + name='number', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='物品编号'), + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index 55491ca..55feadb 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -118,12 +118,40 @@ class WprouctTicket(CommonAModel): number = models.CharField('物品编号', null=True, blank=True, max_length=50) wproduct = models.ForeignKey(WProduct, verbose_name='关联产品', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='所在物料状态', on_delete=models.CASCADE) - step = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE) + step = models.ForeignKey(Step, verbose_name='所在步骤/发现步骤', on_delete=models.CASCADE) subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='所在子生产计划', on_delete=models.CASCADE) ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.CASCADE, related_name='wt_ticket') decision = models.PositiveSmallIntegerField('最终决定', choices=WProduct.ng_choices, null=True, blank=True) +class WproductFlow(CommonAModel): + """ + 动态产品表日志 + """ + wproduct = models.ForeignKey(WProduct, on_delete=models.CASCADE, verbose_name='关联产品', null=True, blank=True) + number = models.CharField('物品编号', null=True, blank=True, max_length=50) + material = models.ForeignKey(Material, verbose_name='所属物料状态', on_delete=models.CASCADE) + pre_step = models.ForeignKey(Step, verbose_name='已执行到', help_text='已执行完的步骤', null=True, blank=True, on_delete=models.CASCADE, related_name='wl_pre_step') + step = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True, related_name='wl_step') + act_state = models.IntegerField('进行状态', default=0, choices=WProduct.act_state_choices) + is_hidden = models.BooleanField('是否隐藏', default=False) + 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) + + scrap_reason = models.IntegerField('报废原因', choices= WProduct.scrap_reason_choices, null=True, blank=True) + ng_sign = models.PositiveSmallIntegerField('不合格标记', choices= WProduct.ng_choices, null=True, blank=True) + + 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) + test = models.ForeignKey('qm.testrecord', verbose_name='当前检验', + on_delete=models.SET_NULL, null=True, blank=True) + ticket = models.ForeignKey('wf.ticket', verbose_name='当前工单', + on_delete=models.SET_NULL, null=True, blank=True) + is_lastlog = models.BooleanField('是否该子计划下的最后一条日志', default=True) + + class Pick(CommonADModel): """ 领料记录 diff --git a/hb_server/apps/wpm/services.py b/hb_server/apps/wpm/services.py index 04b8e87..096b514 100644 --- a/hb_server/apps/wpm/services.py +++ b/hb_server/apps/wpm/services.py @@ -5,7 +5,7 @@ from apps.pm.models import SubProductionPlan, SubProductionProgress from apps.mtm.models import Material, Step, SubprodctionMaterial from apps.qm.models import TestRecord from apps.system.models import User -from apps.wpm.models import WProduct +from apps.wpm.models import WProduct, WproductFlow, WprouctTicket from utils.tools import ranstr class WpmServies(object): @@ -48,7 +48,7 @@ class WpmServies(object): if is_testok: if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST: # 复检 wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT - wproduct.ng_sign = None # 把不合格标记去除 + elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and wproduct.material.type == Material.MA_TYPE_GOOD: # 成品检验 wproduct.act_state = WProduct.WPR_ACT_STATE_TOFINALTEST else: @@ -56,14 +56,32 @@ class WpmServies(object): if wproduct.number is None: # 产生半成品编号 wproduct.number = 'WP'+ranstr(7) - # 更新子计划合格进度 - ins = SubProductionProgress.objects.get(subproduction_plan=wproduct.subproduction_plan, - is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) - ins.count_ok = ins.count_ok + 1 - ins.save() + # 去除ng_sign + if wproduct.ng_sign: + wt = WprouctTicket.objects.order_by('id').last() #取最后的工单 + if wt.step.process == test.step.process: + wproduct.ng_sign = None + # 更新子计划相关进度 + cls.update_subproduction_progress_main(sp=wproduct.subproduction_plan) else:# 如果不合格 wproduct.act_state = WProduct.WPR_ACT_STATE_NOTOK # 需要走不合格品审理的工单 wproduct.update_by = user wproduct.test = None wproduct.save() + + @classmethod + def update_subproduction_progress_main(cls, sp:SubProductionPlan): + """ + 根据产品变动日志更新生产进度 + """ + objs = WproductFlow.objects.filter(subproduction_plan=sp, is_lastlog=True) + count_ok = objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_INM, + WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_SELLED]).count() + count_real = objs.exclude(act_state__in=[WProduct.WPR_ACT_STATE_TORETEST, + WProduct.WPR_ACT_STATE_DOWAIT, WProduct.WPR_ACT_STATE_DOING]) + ins = SubProductionProgress.objects.get(subproduction_plan=sp, + is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) + ins.count_ok = count_ok + ins.count_real = count_real + ins.save() \ No newline at end of file diff --git a/hb_server/apps/wpm/signals.py b/hb_server/apps/wpm/signals.py index 6c61165..4c936b8 100644 --- a/hb_server/apps/wpm/signals.py +++ b/hb_server/apps/wpm/signals.py @@ -6,8 +6,9 @@ from apps.qm.models import TestRecord from apps.wf.models import Ticket from django.dispatch import receiver from rest_framework import exceptions -from apps.wpm.models import WProduct, WprouctTicket +from apps.wpm.models import WProduct, WproductFlow, WprouctTicket from apps.wpm.models import OperationWproduct +from apps.wpm.services import WpmServies @receiver(post_save, sender=Ticket) @@ -45,7 +46,7 @@ def handleTicket(sender, instance, created, **kwargs): 执行操作决定 """ ticket_data = instance.ticket_data - wt = instance.wt_ticket + wt = WprouctTicket.objects.get(ticket=instance) wp = wt.wproduct decision = WProduct.NG_BACK_WORK @@ -61,7 +62,7 @@ def handleTicket(sender, instance, created, **kwargs): wp.step = step # 找到当时所属的计划 sp = SubProductionPlan.objects.filter(ow_subplan__wproduct=wp, - ow_subplan__operation__is_submited=True, ow_subplan__step=step).first() + ow_subplan__operation__is_submited=True, ow_subplan__operation__step=step).first() if sp: wp.subproduction_plan = sp wt.save() @@ -69,11 +70,7 @@ def handleTicket(sender, instance, created, **kwargs): wp.act_state = WProduct.WPR_ACT_STATE_DOWAIT wp.save() # 更新子计划合格进度 - if sp != wt.subproduction_plan: - ins = SubProductionProgress.objects.filter(subproduction_plan=sp, - is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) - ins.count_ok = ins.count_ok - 1 - ins.save() + WpmServies.update_subproduction_progress_main(sp=sp) else: raise exceptions.APIException('返回步骤点错误') @@ -85,5 +82,16 @@ def handleTicket(sender, instance, created, **kwargs): wp.ticket = None # 解除当前工单 wp.save() - - \ No newline at end of file + +@receiver(post_save, sender=WProduct) +def update_wproduct_log(sender, instance, created, **kwargs): + """ + 更新产品变动日志 + """ + WproductFlow.objects.filter(wproduct=instance, subproduction_plan=instance.subproduction_plan).update(is_lastlog=False) + ins = WproductFlow() + ins.wproduct = instance + for f in WproductFlow._meta.fields: + if f.name not in ['id', 'create_time', 'update_time', 'wproduct', 'is_lastlog']: + setattr(ins, f.name, getattr(instance, f.name, None)) + ins.save() \ No newline at end of file diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index a3b840c..e11db5d 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -180,14 +180,21 @@ class WProductViewSet(ListModelMixin, GenericViewSet): 半成品 """ perms_map={'*':'*'} - queryset = WProduct.objects.select_related('step', 'material', 'subproduction_plan').filter(is_hidden=False) + queryset = WProduct.objects.select_related('step', 'material', 'subproduction_plan') serializer_class = WProductListSerializer filterset_class = WProductFilterSet search_fields = ['number'] ordering_fields = ['id'] ordering = ['id'] + def get_queryset(self): + queryset = self.queryset + if self.request.query_params.get('tag', None) != 'show_hidden': + queryset = queryset.filter(is_hidden=False) + return queryset + @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=WpmTestFormInitSerializer) + @transaction.atomic def test_init(self, request, pk=None): """ 检验记录创建及初始化 @@ -283,7 +290,12 @@ class WProductViewSet(ListModelMixin, GenericViewSet): FIFOItemProduct.objects.bulk_create(ips) # 更新库存并修改半成品进行状态 update_inm(fifo) - wproducts.update(act_state=WProduct.WPR_ACT_STATE_INM, warehouse=warehouse, update_by=request.user, update_time=timezone.now()) + for i in wproducts: + i.act_state = WProduct.WPR_ACT_STATE_INM + i.warehouse = warehouse + i.update_by = request.user + i.update_time = timezone.now() + i.save() return Response() @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=WproductPutInSerializer) @@ -544,10 +556,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd # 更新子计划生产进度 # 如果产品有返工标记不做计算 if wp.ng_sign not in [WProduct.NG_BACK_FIX, WProduct.NG_BACK_WORK]: - ins = SubProductionProgress.objects.get(subproduction_plan=wsp, - is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) - ins.count_real = ins.count_real + 1 - ins.save() + WpmServies.update_subproduction_progress_main(sp=wsp) wp.operation = None wp.update_by = request.user wp.save() @@ -560,7 +569,8 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd act_state=WProduct.WPR_ACT_STATE_DOWAIT, remark='', subproduction_plan=i.subproduction_plan) for x in range(i.count): - WProduct.objects.create(**wpr) + WProduct.objects.create(**wpr) + WpmServies.update_subproduction_progress_main(sp=i.subproduction_plan) elif step.type == Step.STEP_TYPE_COMB: oms_w = OperationMaterial.objects.filter(operation=op, type=SubprodctionMaterial.SUB_MA_TYPE_OUT, subproduction_progress__is_main=True) @@ -577,9 +587,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd else: wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST # 更新子计划进度 - instance = oms_w.subproduction_progress - instance.count_real = instance.count_real + 1 # 这个地方可能会有问题,不够严谨 - instance.save() + WpmServies.update_subproduction_progress_main(sp=oms_w.subproduction_plan) wproduct.create_by = request.user wproduct.save() # 隐藏原半成品