diff --git a/hb_client/src/router/index.js b/hb_client/src/router/index.js index e793c8d..fe7eead 100644 --- a/hb_client/src/router/index.js +++ b/hb_client/src/router/index.js @@ -153,8 +153,7 @@ export const asyncRoutes = [ meta: { title: '产品管理', icon: 'example', perms: ['mtm_productprocess'] } }, ] - } - , + }, { path: '/pm', component: Layout, @@ -208,8 +207,7 @@ export const asyncRoutes = [ } ] - } - , + }, { path: '/wpm', component: Layout, @@ -339,8 +337,7 @@ export const asyncRoutes = [ hidden: true } ] - } - , + }, { path: '/qm', component: Layout, @@ -407,7 +404,6 @@ export const asyncRoutes = [ ] }, - { path: '/personnel', component: Layout, @@ -435,8 +431,7 @@ export const asyncRoutes = [ }, ] - } - , + }, { path: '/inm', component: Layout, @@ -492,36 +487,74 @@ export const asyncRoutes = [ ] }, - - - { - path: '/procurement', - component: Layout, - redirect: '/procurement/vendor', - name: 'procurement', - meta: { title: '采购管理', icon: 'example', perms: ['procurement_set'] }, - children: [ - { - path: 'vendor', - name: 'vendor', - component: () => import('@/views/procurement/vendor'), - meta: { title: '供应商', icon: 'example', perms: ['vendor_manage'] } - }, - { - path: 'puorder', - name: 'puorder', - component: () => import('@/views/procurement/puorder'), - meta: { title: '采购订单', icon: 'example', perms: ['vendor_manage'] } - }, - { - path: 'puorderitem/:id', - name: 'puorderitem', - component: () => import('@/views/procurement/puorderitem'), - meta: { title: '采购订单项', perms: ['vendor_manage'] }, - hidden: true - } - ] - }, + { + path: '/procurement', + component: Layout, + redirect: '/procurement/vendor', + name: 'procurement', + meta: { title: '采购管理', icon: 'example', perms: ['procurement_set'] }, + children: [ + { + path: 'vendor', + name: 'vendor', + component: () => import('@/views/procurement/vendor'), + meta: { title: '供应商', icon: 'example', perms: ['vendor_manage'] } + }, + { + path: 'puorder', + name: 'puorder', + component: () => import('@/views/procurement/puorder'), + meta: { title: '采购订单', icon: 'example', perms: ['vendor_manage'] } + }, + { + path: 'puorderitem/:id', + name: 'puorderitem', + component: () => import('@/views/procurement/puorderitem'), + meta: { title: '采购订单项', perms: ['vendor_manage'] }, + hidden: true + } + ] + }, + { + path: '/statistics', + component: Layout, + redirect: '/statistics/progressStatistics', + name: 'statistics', + meta: { title: '统计分析', icon: 'example', perms: ['workflow_manage'] }, + children: [ + { + path: 'progressStatistics', + name: 'progressStatistics', + component: () => import('@/views/statistics/progressStatistics'), + meta: { title: '进度统计', icon: 'example', perms: ['workflow_index'] } + }, + { + path: 'materialStatistics', + name: 'materialStatistics', + component: () => import('@/views/statistics/materialStatistics'), + meta: { title: '物料统计', icon: 'example', perms: ['workflow_index'] } + }, + { + path: 'personStatistics', + name: 'personStatistics', + component: () => import('@/views/statistics/personStatistics'), + meta: { title: '人员统计', icon: 'example', perms: ['workflow_index'] } + }, + { + path: 'costStatistics', + name: 'costStatistics', + component: () => import('@/views/statistics/costStatistics'), + meta: { title: '成本统计', icon: 'example', perms: ['workflow_index'] } + }, + { + path: 'testStatistics', + name: 'testStatistics', + component: () => import('@/views/statistics/testStatistics'), + meta: { title: '检验统计', icon: 'example', perms: ['workflow_index'] } + }, + + ] + }, { path: '/workflow', component: Layout, @@ -707,6 +740,12 @@ export const asyncRoutes = [ component: () => import('@/views/testModel/faceLogin'), meta: { title: '人脸识别登录', icon: 'example' } }, + { + path: 'caram', + name: 'caram', + component: () => import('@/views/testModel/caram'), + meta: { title: '相机调用', icon: 'example' } + }, { path: 'markImage', name: 'markImage', diff --git a/hb_client/src/views/procurement/vendor.vue b/hb_client/src/views/procurement/vendor.vue index fcfc435..a40edb0 100644 --- a/hb_client/src/views/procurement/vendor.vue +++ b/hb_client/src/views/procurement/vendor.vue @@ -8,7 +8,7 @@ +
+ + + + + diff --git a/hb_client/src/views/dashboard/largeScreen.vue b/hb_client/src/views/statistics/costStatistics.vue similarity index 67% rename from hb_client/src/views/dashboard/largeScreen.vue rename to hb_client/src/views/statistics/costStatistics.vue index ed331a3..3d4342c 100644 --- a/hb_client/src/views/dashboard/largeScreen.vue +++ b/hb_client/src/views/statistics/costStatistics.vue @@ -1,10 +1,10 @@ diff --git a/hb_client/src/views/statistics/materialStatistics.vue b/hb_client/src/views/statistics/materialStatistics.vue new file mode 100644 index 0000000..d7b83f5 --- /dev/null +++ b/hb_client/src/views/statistics/materialStatistics.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/hb_client/src/views/statistics/personStatistics.vue b/hb_client/src/views/statistics/personStatistics.vue new file mode 100644 index 0000000..ee6fefe --- /dev/null +++ b/hb_client/src/views/statistics/personStatistics.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/hb_client/src/views/statistics/progressStatistics.vue b/hb_client/src/views/statistics/progressStatistics.vue new file mode 100644 index 0000000..dece4b0 --- /dev/null +++ b/hb_client/src/views/statistics/progressStatistics.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/hb_client/src/views/statistics/testStatistics.vue b/hb_client/src/views/statistics/testStatistics.vue new file mode 100644 index 0000000..b8153af --- /dev/null +++ b/hb_client/src/views/statistics/testStatistics.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/hb_client/src/views/testModel/caram.vue b/hb_client/src/views/testModel/caram.vue new file mode 100644 index 0000000..1d33058 --- /dev/null +++ b/hb_client/src/views/testModel/caram.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/hb_server/apps/develop/urls.py b/hb_server/apps/develop/urls.py index 0048316..58c675e 100644 --- a/hb_server/apps/develop/urls.py +++ b/hb_server/apps/develop/urls.py @@ -2,7 +2,7 @@ from django.db.models import base from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateEquipState, UpdateFIFOItem, UpdateLastTestResult, UpdateNeedToOrder, UpdateSpg +from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateEquipState, UpdateFIFOItem, UpdateFIFONumber, UpdateLastTestResult, UpdateNeedToOrder, UpdateSpg urlpatterns = [ path('cleandata/', CleanDataView.as_view()), @@ -12,6 +12,7 @@ urlpatterns = [ path('update_fifoitem/', UpdateFIFOItem.as_view()), path('update_spg/', UpdateSpg.as_view()), path('update_equip_state/', UpdateEquipState.as_view()), - path('update_need_to_order/', UpdateNeedToOrder.as_view()) + path('update_need_to_order/', UpdateNeedToOrder.as_view()), + path('update_fifo_number/', UpdateFIFONumber.as_view()) ] diff --git a/hb_server/apps/develop/views.py b/hb_server/apps/develop/views.py index 4d6c050..2412742 100644 --- a/hb_server/apps/develop/views.py +++ b/hb_server/apps/develop/views.py @@ -10,7 +10,7 @@ from apps.pm.models import ProductionPlan, SubProductionPlan from apps.sam.models import Order from apps.wf.models import Ticket from apps.wpm.models import Operation, OperationMaterial, WProduct, WproductFlow -from apps.wpm.services import WpmServies +from apps.wpm.services import WpmService from apps.em.tasks import update_equip_state_by_next_check_date # Create your views here. @@ -46,7 +46,7 @@ class UpdateCuttingView(APIView): i.coperation = op i.save() WproductFlow.objects.filter(wproduct=i).update(coperation=op) - WpmServies.update_cutting_list_with_operation(op) + WpmService.update_cutting_list_with_operation(op) return Response() from apps.qm.models import TestRecord @@ -90,7 +90,7 @@ class UpdateSpg(APIView): 冷加工重新计算合格率 """ for i in SubProductionPlan.objects.filter(subproduction__process__id=1): - WpmServies.update_subproduction_progress_main(sp=i) + WpmService.update_subproduction_progress_main(sp=i) return Response() @@ -105,4 +105,16 @@ class UpdateNeedToOrder(APIView): permission_classes = [IsAdminUser] def post(self, request): WProduct.objects.exclude(to_order=None).update(need_to_order=True) + return Response() + +class UpdateFIFONumber(APIView): + permission_classes = [IsAdminUser] + def post(self, request): + from utils.tools import ranstr + for i in FIFO.objects.all(): + if i.type in [FIFO.FIFO_TYPE_DO_IN, FIFO.FIFO_TYPE_PUR_IN]: + i.number = 'RK'+ ranstr(7) + else: + i.number = 'CK' + ranstr(7) + i.save() return Response() \ No newline at end of file diff --git a/hb_server/apps/inm/apps.py b/hb_server/apps/inm/apps.py index 25c889c..360a2ea 100644 --- a/hb_server/apps/inm/apps.py +++ b/hb_server/apps/inm/apps.py @@ -2,7 +2,4 @@ from django.apps import AppConfig class InmConfig(AppConfig): name = 'apps.inm' - verbose_name = '库存管理' - - def ready(self): - import apps.inm.signals + verbose_name = '库存管理' \ No newline at end of file diff --git a/hb_server/apps/inm/migrations/0030_auto_20220128_0943.py b/hb_server/apps/inm/migrations/0030_auto_20220128_0943.py new file mode 100644 index 0000000..1a4adee --- /dev/null +++ b/hb_server/apps/inm/migrations/0030_auto_20220128_0943.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.9 on 2022-01-28 01:43 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('pum', '0004_puorder_puorderitem'), + ('inm', '0029_fifo_pu_order'), + ] + + operations = [ + migrations.AddField( + model_name='fifo', + name='number', + field=models.CharField(default=1, max_length=100, verbose_name='记录编号'), + preserve_default=False, + ), + migrations.AddField( + model_name='fifo', + name='vendor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pum.vendor', verbose_name='供应商'), + ), + migrations.AlterField( + model_name='fifoitemproduct', + name='fifoitem', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inm.fifoitem', verbose_name='关联出入库条目'), + ), + ] diff --git a/hb_server/apps/inm/migrations/0031_fifoitem_pu_order_item.py b/hb_server/apps/inm/migrations/0031_fifoitem_pu_order_item.py new file mode 100644 index 0000000..a02ae0f --- /dev/null +++ b/hb_server/apps/inm/migrations/0031_fifoitem_pu_order_item.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.9 on 2022-01-28 06:32 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('pum', '0004_puorder_puorderitem'), + ('inm', '0030_auto_20220128_0943'), + ] + + operations = [ + migrations.AddField( + model_name='fifoitem', + name='pu_order_item', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pum.puorderitem', verbose_name='关联采购订单条目'), + ), + ] diff --git a/hb_server/apps/inm/models.py b/hb_server/apps/inm/models.py index 4042844..90d80c7 100644 --- a/hb_server/apps/inm/models.py +++ b/hb_server/apps/inm/models.py @@ -2,7 +2,7 @@ from django.db import models from django.db.models.base import Model import django.utils.timezone as timezone from django.db.models.query import QuerySet -from apps.pum.models import PuOrder +from apps.pum.models import PuOrder, PuOrderItem, Vendor from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File from utils.model import SoftModel, BaseModel from simple_history.models import HistoricalRecords @@ -73,12 +73,15 @@ class FIFO(CommonADModel): (FIFO_TYPE_PUR_IN, '采购入库'), (FIFO_TYPE_DO_IN, '生产入库') ) + number = models.CharField('记录编号', max_length=100) type = models.IntegerField('出入库类型', default=1) is_audited = models.BooleanField('是否审核', default=False) auditor = models.ForeignKey( User, verbose_name='审核人', on_delete=models.CASCADE, null=True, blank=True) inout_date = models.DateField('出入库日期', null=True, blank=True) remark = models.CharField('备注', max_length=1000, default='') + vendor = models.ForeignKey(Vendor, verbose_name='供应商', + on_delete=models.CASCADE, null=True, blank=True) pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单', null=True, blank=True, on_delete=models.CASCADE) @@ -100,6 +103,8 @@ class FIFOItem(BaseModel): subproduction_plan = models.ForeignKey( SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE, null=True, blank=True) files = models.ManyToManyField(File, verbose_name='上传材料', blank=True) + pu_order_item = models.ForeignKey(PuOrderItem, + verbose_name='关联采购订单条目', null=True, blank=True, on_delete=models.CASCADE) class IProduct(BaseModel): @@ -119,14 +124,17 @@ class IProduct(BaseModel): class FIFOItemProduct(BaseModel): """ - 出入库产品 + 出入库记录具体产品 """ fifoitem = models.ForeignKey( - FIFOItem, verbose_name='关联出入库具体产品', on_delete=models.CASCADE) - wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False, null=True, blank=True, - related_name='fifoitem_wproduct') + FIFOItem, verbose_name='关联出入库条目', on_delete=models.CASCADE) + wproduct = models.ForeignKey('wpm.wproduct', + on_delete=models.CASCADE, verbose_name='关联的动态产品', + db_constraint=False, null=True, blank=True, + related_name='fifoitem_wproduct') number = models.CharField('物品编号', max_length=50) material = models.ForeignKey( Material, verbose_name='物料类型', on_delete=models.CASCADE) iproduct = models.ForeignKey( - IProduct, verbose_name='关联库存产品', null=True, blank=True, on_delete=models.SET_NULL) + IProduct, verbose_name='关联库存产品', + null=True, blank=True, on_delete=models.SET_NULL) diff --git a/hb_server/apps/inm/serializers.py b/hb_server/apps/inm/serializers.py index 858c06d..b1e842a 100644 --- a/hb_server/apps/inm/serializers.py +++ b/hb_server/apps/inm/serializers.py @@ -2,6 +2,7 @@ from rest_framework import exceptions from rest_framework import serializers from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, MaterialBatch, WareHouse, Inventory +from apps.pum.models import PuOrder, Vendor from apps.qm.models import TestRecord, TestRecordItem from apps.sam.serializers import OrderSimpleSerializer @@ -10,6 +11,8 @@ from apps.system.serializers import FileSimpleSerializer, UserSimpleSerializer from apps.mtm.serializers import MaterialSimpleSerializer from django.db import transaction from utils.mixins import DynamicFieldsSerializerMixin +from utils.tools import ranstr +from rest_framework.exceptions import ValidationError @@ -73,6 +76,28 @@ class FIFOListSerializer(serializers.ModelSerializer): model = FIFO fields = '__all__' +class FIFOItemCreateSerializer(serializers.ModelSerializer): + class Meta: + model = FIFOItem + fields = ['warehouse', + 'material', 'batch', 'fifo', 'files', 'pu_order_item'] + + def create(self, validated_data): + fifo = validated_data['fifo'] + pu_order_item = validated_data.get('pu_order_item', None) + if pu_order_item: + if fifo.pu_order != pu_order_item.pu_order: + raise ValidationError('项目与采购订单不一致') + validated_data['material']=pu_order_item.material + else: + if fifo.pu_order is not None: + raise ValidationError('非采购订单') + return super().create(validated_data) + +class FIFOItemUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = FIFOItem + fields = ['warehouse', 'batch', 'files'] class FIFOItemSerializer(serializers.ModelSerializer): warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True) @@ -83,14 +108,14 @@ class FIFOItemSerializer(serializers.ModelSerializer): fields = '__all__' -class IProductInPurSerializer(serializers.ModelSerializer): +class FIFOItemProductCreateSerializer(serializers.ModelSerializer): class Meta: model = IProduct fields = ['number'] class FIFODetailInPurSerializer(serializers.ModelSerializer): - details = IProductInPurSerializer(many=True, required=False) + details = FIFOItemProductCreateSerializer(many=True, required=False) class Meta: model = FIFOItem @@ -101,41 +126,26 @@ class MaterialBatchQuerySerializer(serializers.Serializer): warehouse = serializers.IntegerField(label="仓库ID", required=False) materials = serializers.ListField(child=serializers.IntegerField(label="物料ID"), required=False) - -class FIFOInPurSerializer(serializers.ModelSerializer): +class FIFOInOtherSerializer(serializers.ModelSerializer): """ - 采购入库序列化 + 其他入库序列化 """ - details = FIFODetailInPurSerializer(many=True) - + details = FIFODetailInPurSerializer(many=True, required=False) + vendor = serializers.PrimaryKeyRelatedField(label='供应商ID', + queryset=Vendor.objects.all()) class Meta: model = FIFO - fields = ['details'] + fields = ['details', 'vendor'] def create(self, validated_data): details = validated_data.pop('details') - if len(details) > 0: - pass - else: - raise serializers.ValidationError('没有入库内容') - - # for i in details: - # # 校验批次 - # try: - # if i['batch']: - # obj = MaterialBatch.objects.get(batch=i['batch'], material=i['material']) - # if obj.warehouse != validated_data['warehouse']: - # raise serializers.ValidationError('批次号{}在其他仓库已存在'.format(i['batch'])) - # except: - # pass - - # 创建采购入库 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: + i['number'] = 'RK' + ranstr(7) p_details = i.pop('details') if len(p_details) != i['count']: raise serializers.ValidationError('数目对不上') @@ -148,19 +158,30 @@ class FIFOInPurSerializer(serializers.ModelSerializer): x['fifoitem'] = fifoitem p_list0.append(FIFOItemProduct(**x)) FIFOItemProduct.objects.bulk_create(p_list0) - - p_list = [] - for x in p_details: - x['material'] = i['material'] - x['warehouse'] = validated_data['warehouse'] - x['batch'] = i['batch'] - p_list.append(IProduct(**x)) - IProduct.objects.bulk_create(p_list) else: i['fifo'] = obj + i['number'] = 'RK' + ranstr(7) FIFOItem.objects.create(**i) return obj +class FIFOInPurSerializer(serializers.ModelSerializer): + """ + 采购入库序列化 + """ + pu_order = serializers.PrimaryKeyRelatedField(label='采购订单ID', + queryset = PuOrder.objects.filter(is_audited=True)) + + class Meta: + model = FIFO + fields = ['pu_order'] + + def create(self, validated_data): + pu_order = validated_data['pu_order'] + validated_data['vendor'] = pu_order.vendor + validated_data['number'] = 'RK' + ranstr(7) + obj = FIFO.objects.create(**validated_data) + return obj + class InmTestRecordItemCreateSerializer(serializers.ModelSerializer): class Meta: diff --git a/hb_server/apps/inm/services.py b/hb_server/apps/inm/services.py new file mode 100644 index 0000000..d227e62 --- /dev/null +++ b/hb_server/apps/inm/services.py @@ -0,0 +1,75 @@ +from rest_framework.exceptions import ValidationError +from apps.inm.models import FIFOItemProduct, IProduct, Inventory, MaterialBatch, FIFO, FIFOItem + +class InmService: + @classmethod + def update_inm(cls, instance:FIFO, type:int=1): + """ + 更新库存(正反) + """ + if instance.type in [FIFO.FIFO_TYPE_PUR_IN, FIFO.FIFO_TYPE_DO_IN]: # 采购入库, 生产入库 + # 更新相关表 + for i in FIFOItem.objects.filter(fifo=instance): + material = i.material + warehouse = i.warehouse + o1, _ = Inventory.objects.get_or_create(material=material, warehouse=warehouse, \ + defaults={'material':material, 'warehouse':warehouse, 'count':0}) + o1.count = o1.count + i.count + o1.save() + o2, _ = MaterialBatch.objects.get_or_create(material=material, warehouse=warehouse, batch=i.batch,\ + defaults={'material':material, 'warehouse':warehouse, 'count':0, 'batch':i.batch}) + o2.count = o2.count + i.count + o2.save() + material.count = material.count + i.count + material.save() + + # 创建IProduct + ips2 = [] + for m in FIFOItemProduct.objects.filter(fifoitem=i): + ip = {} + ip['warehouse'] = warehouse + ip['batch'] = i.batch + wp = m.wproduct + ip['wproduct'] = wp + ip['number'] = m.number + ip['material'] = m.material + ips2.append(IProduct(**ip)) + IProduct.objects.bulk_create(ips2) + + # 如果是采购入库更新采购订单表 + if instance.type == FIFO.FIFO_TYPE_PUR_IN: + pur_order_item = i.pur_order_item + delivered_count = pur_order_item.delivered_count + i.count + if delivered_count > pur_order_item.count: + raise ValidationError('超出采购订单所需量') + pur_order_item.delivered_count = delivered_count + pur_order_item.save() + + elif instance.type in [FIFO.FIFO_TYPE_DO_OUT, FIFO.FIFO_TYPE_SALE_OUT]: # 生产领料 销售出库 + # 更新相关表 + for i in FIFOItem.objects.filter(fifo=instance): + material = i.material + warehouse = i.warehouse + o1 = Inventory.objects.get(material=material, warehouse=warehouse) + temp_count = o1.count - i.count + if temp_count < 0: + raise ValidationError('库存不足,操作失败') + o1.count = temp_count + o1.save() + o2 = MaterialBatch.objects.get(material=material, warehouse=warehouse, batch=i.batch) + temp_count = o2.count - i.count + if temp_count < 0: + raise ValidationError('库存不足,操作失败') + o2.count = temp_count + o2.save() + temp_count = material.count - i.count + if temp_count < 0: + raise ValidationError('库存不足,操作失败') + material.count = temp_count + material.save() + + # 删除IProduct + if instance.type == FIFO.FIFO_TYPE_DO_OUT: + # 生产领料的情况直接从IProduct中删除 + numbers = FIFOItemProduct.objects.filter(fifoitem=i).values_list('number', flat=True) + IProduct.objects.filter(number__in=numbers).delete() \ No newline at end of file diff --git a/hb_server/apps/inm/signals.py b/hb_server/apps/inm/signals.py deleted file mode 100644 index 59df461..0000000 --- a/hb_server/apps/inm/signals.py +++ /dev/null @@ -1,58 +0,0 @@ -from django.db.models.signals import post_save -from django.dispatch import receiver - -from apps.inm.models import FIFOItemProduct, IProduct, Inventory, MaterialBatch, FIFO, FIFOItem - - -def update_inm(instance:FIFO, type:int=1): - """ - 更新库存(正反) - """ - if instance.type in [FIFO.FIFO_TYPE_PUR_IN, FIFO.FIFO_TYPE_DO_IN]: # 采购入库, 生产入库 - # 更新相关表 - for i in FIFOItem.objects.filter(fifo=instance): - material = i.material - warehouse = i.warehouse - o1, _ = Inventory.objects.get_or_create(material=material, warehouse=warehouse, \ - defaults={'material':material, 'warehouse':warehouse, 'count':0}) - o1.count = o1.count + i.count - o1.save() - o2, _ = MaterialBatch.objects.get_or_create(material=material, warehouse=warehouse, batch=i.batch,\ - defaults={'material':material, 'warehouse':warehouse, 'count':0, 'batch':i.batch}) - o2.count = o2.count + i.count - o2.save() - material.count = material.count + i.count - material.save() - - # 创建IProduct - ips2 = [] - for m in FIFOItemProduct.objects.filter(fifoitem=i): - ip = {} - ip['warehouse'] = warehouse - ip['batch'] = i.batch - wp = m.wproduct - ip['wproduct'] = wp - ip['number'] = m.number - ip['material'] = m.material - ips2.append(IProduct(**ip)) - IProduct.objects.bulk_create(ips2) - - elif instance.type in [FIFO.FIFO_TYPE_DO_OUT, FIFO.FIFO_TYPE_SALE_OUT]: # 生产领料 销售出库 - # 更新相关表 - for i in FIFOItem.objects.filter(fifo=instance): - material = i.material - warehouse = i.warehouse - o1 = Inventory.objects.get(material=material, warehouse=warehouse) - o1.count = o1.count - i.count - o1.save() - o2 = MaterialBatch.objects.get(material=material, warehouse=warehouse, batch=i.batch) - o2.count = o2.count - i.count - o2.save() - material.count = material.count - i.count - material.save() - - # 删除IProduct - if instance.type == FIFO.FIFO_TYPE_DO_OUT: - numbers = FIFOItemProduct.objects.filter(fifoitem=i).values_list('number', flat=True) - IProduct.objects.filter(number__in=numbers).delete() - diff --git a/hb_server/apps/inm/views.py b/hb_server/apps/inm/views.py index 26823cb..7a4e069 100644 --- a/hb_server/apps/inm/views.py +++ b/hb_server/apps/inm/views.py @@ -1,15 +1,15 @@ from rest_framework import serializers from rest_framework import exceptions -from rest_framework.exceptions import APIException -from rest_framework.mixins import DestroyModelMixin, ListModelMixin, UpdateModelMixin +from rest_framework.exceptions import APIException, ValidationError +from rest_framework.mixins import DestroyModelMixin, ListModelMixin, UpdateModelMixin, CreateModelMixin from rest_framework.viewsets import GenericViewSet, ModelViewSet from apps.inm.filters import IProductFilterSet, MbFilterSet from apps.inm.models import FIFO, FIFOItem, IProduct, MaterialBatch, WareHouse, Inventory -from apps.inm.serializers import FIFOItemSerializer, FIFOInPurSerializer, FIFOItemUpdateSerializer, FIFOListSerializer, IProductListSerializer, \ +from apps.inm.serializers import FIFOInOtherSerializer, FIFOItemCreateSerializer, FIFOItemSerializer, FIFOInPurSerializer, FIFOItemUpdateSerializer, FIFOListSerializer, IProductListSerializer, \ InmTestRecordCreateSerializer, MaterialBatchQuerySerializer, MaterialBatchSerializer, WareHouseSerializer, \ WareHouseCreateUpdateSerializer, InventorySerializer -from apps.inm.signals import update_inm +from apps.inm.services import InmService from apps.qm.models import TestRecordItem from apps.system.mixins import CreateUpdateModelAMixin from rest_framework.decorators import action @@ -77,7 +77,7 @@ class MaterialBatchViewSet(ListModelMixin, GenericViewSet): return Response(MaterialBatchSerializer(instance=queryset, many=True).data) -class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet): +class FIFOItemViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet): """ 出入库记录详情表 """ @@ -93,12 +93,27 @@ class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, UpdateModelMixin, Gener def get_serializer_class(self): if self.action == 'update': return FIFOItemUpdateSerializer + elif self.action == 'create': + return FIFOItemCreateSerializer return super().get_serializer_class() - def perform_destroy(self, instance): - if instance.fifo.is_audited: - raise APIException('该出入库记录已通过审核, 无法删除') - return super().perform_destroy(instance) + def create(self, request, *args, **kwargs): + obj = self.get_object() + if obj.fifo.is_audited: + raise ValidationError('该出入库记录已审核') + return super().create(request, *args, **kwargs) + + def update(self, request, *args, **kwargs): + obj = self.get_object() + if obj.fifo.is_audited: + raise ValidationError('该出入库记录已审核') + return super().update(request, *args, **kwargs) + + def destroy(self, request, *args, **kwargs): + obj = self.get_object() + if obj.fifo.is_audited: + raise ValidationError('该出入库记录已审核') + return super().destroy(request, *args, **kwargs) @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=InmTestRecordCreateSerializer) def test(self, request, pk=None): @@ -160,6 +175,17 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): serializer.is_valid(raise_exception=True) serializer.save(create_by=request.user) return Response() + + @action(methods=['post'], detail=False, perms_map={'post': '*'}, + serializer_class=FIFOInOtherSerializer) + def in_other(self, request, pk=None): + """ + 其他入库 + """ + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(create_by=request.user) + return Response() @action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=serializers.Serializer) def audit(self, request, pk=None): @@ -167,6 +193,8 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): 审核通过 """ obj = self.get_object() + if not FIFOItem.objects.filter(fifo=obj).exists(): + raise ValidationError('出入库条目为空') for i in FIFOItem.objects.filter(fifo=obj, need_test=True): if not i.is_testok: raise APIException('未检验通过, 不可审核') @@ -177,7 +205,7 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): obj.auditor = request.user obj.inout_date = timezone.now() # 也是审核日期 obj.save() - update_inm(obj) # 更新库存 + InmService.update_inm(obj) # 更新库存 return Response() diff --git a/hb_server/apps/mtm/migrations/0045_usedstep_need_test.py b/hb_server/apps/mtm/migrations/0045_usedstep_need_test.py new file mode 100644 index 0000000..11b7fff --- /dev/null +++ b/hb_server/apps/mtm/migrations/0045_usedstep_need_test.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-29 07:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0044_subproduction_need_combtest'), + ] + + operations = [ + migrations.AddField( + model_name='usedstep', + name='need_test', + field=models.BooleanField(default=False, verbose_name='工序内检验'), + ), + ] diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index 4afb8b5..f8ccc83 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -242,6 +242,7 @@ class UsedStep(CommonADModel): 涉及的生产子工序 """ step = models.ForeignKey(Step, verbose_name='子工序', on_delete=models.CASCADE, related_name='usedstep') + need_test = models.BooleanField('工序内检验', default=False) remark = models.TextField('生产备注', null=True, blank=True) subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE, related_name='usedstep_subproduction') diff --git a/hb_server/apps/pm/filters.py b/hb_server/apps/pm/filters.py index aa9c6da..a53c29d 100644 --- a/hb_server/apps/pm/filters.py +++ b/hb_server/apps/pm/filters.py @@ -3,7 +3,7 @@ from apps.mtm.models import Material, Step from apps.pm.models import ProductionPlan, SubProductionProgress from apps.wpm.models import Operation, WProduct from datetime import * -from apps.wpm.services import WpmServies +from apps.wpm.services import WpmService from django.db.models import F from utils.mixins import DynamicFieldsFilterMixin @@ -52,9 +52,9 @@ class SubproductionProgressFilterSet(filters.FilterSet): wproducts = WProduct.objects.filter(ow_wproduct__operation=value) step = operation.step if wproducts.exists(): - subplans = WpmServies.get_subplans_queryset_from_wproducts(wproducts) + subplans = WpmService.get_subplans_queryset_from_wproducts(wproducts) else: - subplans = WpmServies.get_subplans_queyset_from_step(step) + subplans = WpmService.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__in =[Material.MA_TYPE_HALFGOOD, Material.MA_TYPE_GOOD]) diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index 0fea0a7..449cb2d 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -89,7 +89,8 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel subps = SubProduction.objects.filter(product=production_plan.product).order_by('process__number') 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') + usedstep__is_deleted=False, is_deleted=False + ).values('id', 'number', 'name', 'usedstep__remark', need_test=F('usedstep__need_test')) 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, diff --git a/hb_server/apps/pum/serializers.py b/hb_server/apps/pum/serializers.py index 4fe4ae7..c3d948b 100644 --- a/hb_server/apps/pum/serializers.py +++ b/hb_server/apps/pum/serializers.py @@ -1,8 +1,7 @@ -from rest_framework.serializers import ModelSerializer +from rest_framework.serializers import ModelSerializer, ValidationError from apps.mtm.serializers import MaterialSimpleSerializer from .models import PuOrder, PuOrderItem, Vendor - class VendorSerializer(ModelSerializer): class Meta: model = Vendor @@ -19,6 +18,22 @@ class PuOrderItemSerializer(ModelSerializer): model = PuOrderItem fields = '__all__' +class PuOrderItemCreateSerializer(ModelSerializer): + class Meta: + model = PuOrderItem + fields = ['count', 'delivery_date', 'pu_order', 'material'] + + def validate(self, attrs): + pu_order = attrs['pu_order'] + if pu_order.is_audited: + raise ValidationError('采购订单已审核') + return super().validate(attrs) + +class PuOrderItemUpdateSerializer(ModelSerializer): + class Meta: + model = PuOrderItem + fields = ['count', 'delivery_date'] + class PuOrderSerializer(ModelSerializer): vendor_ = VendorSimpleSerializer(source='vendor', read_only=True) items = PuOrderItemSerializer(source='item_pu_order', many=True, read_only=True) @@ -28,4 +43,5 @@ class PuOrderSerializer(ModelSerializer): class PuOrderCreateUpdateSerializer(ModelSerializer): class Meta: + model = PuOrder fields = ['number', 'vendor'] \ No newline at end of file diff --git a/hb_server/apps/pum/urls.py b/hb_server/apps/pum/urls.py index a81ad24..1ecc84b 100644 --- a/hb_server/apps/pum/urls.py +++ b/hb_server/apps/pum/urls.py @@ -1,11 +1,13 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.pum.views import VendorViewSet +from apps.pum.views import PuOrderItemViewSet, PuOrderViewSet, VendorViewSet from django.urls import path, include from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('vendor', VendorViewSet, basename='vendor') +router.register('pu_order', PuOrderViewSet, basename='pu_order') +router.register('pu_order_item', PuOrderItemViewSet, basename='pu_order_item') urlpatterns = [ path('', include(router.urls)), ] diff --git a/hb_server/apps/pum/views.py b/hb_server/apps/pum/views.py index ec11c43..6f13f39 100644 --- a/hb_server/apps/pum/views.py +++ b/hb_server/apps/pum/views.py @@ -1,13 +1,17 @@ from django.shortcuts import render +from numpy import delete from rest_framework.viewsets import ModelViewSet -from rest_framework.mixins import CreateModelMixin, DestroyModelMixin +from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin from rest_framework.viewsets import GenericViewSet -from apps.pum.models import PuOrder, Vendor -from apps.pum.serializers import PuOrderCreateUpdateSerializer, PuOrderSerializer, VendorSerializer +from apps.pum.models import PuOrder, PuOrderItem, Vendor +from apps.pum.serializers import PuOrderCreateUpdateSerializer, PuOrderItemCreateSerializer, PuOrderItemSerializer, PuOrderItemUpdateSerializer, PuOrderSerializer, VendorSerializer from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin - - +from rest_framework.exceptions import APIException, ValidationError +from rest_framework.decorators import action +from rest_framework import serializers +from rest_framework.response import Response +from rest_framework import status # Create your views here. class VendorViewSet(CreateUpdateModelAMixin, ModelViewSet): """ @@ -41,7 +45,49 @@ class PuOrderViewSet(CreateUpdateModelAMixin, ModelViewSet): return PuOrderCreateUpdateSerializer return super().get_serializer_class() + def update(self, request, *args, **kwargs): + obj = self.get_object() + if obj.is_audited: + raise ValidationError('该采购订单已审核') + return super().update(request, *args, **kwargs) + + def destroy(self, request, *args, **kwargs): + obj = self.get_object() + if obj.is_audited: + raise ValidationError('该采购订单已审核') + return super().destroy(request, *args, **kwargs) + @action(methods=['post'], detail=True, perms_map={'post':'*'}, + serializer_class=serializers.Serializer) + def audit(self, request, pk=None): + obj = self.get_object() + if obj.item_pu_order.exists() and not obj.is_audited: + obj.is_audited = True + obj.update_by = request.user + obj.save() + return Response() + return Response('订单状态有误', status=status.HTTP_400_BAD_REQUEST) -class PuOrderItemViewSet(CreateModelMixin, DestroyModelMixin, GenericViewSet): - pass \ No newline at end of file +class PuOrderItemViewSet(CreateModelMixin, ListModelMixin, DestroyModelMixin, GenericViewSet): + """ + 采购订单条目 + """ + perms_map = {'get': '*', 'post': '*', + 'put': '*', 'delete': '*'} + queryset = PuOrderItem.objects.select_related('material').all() + serializer_class = PuOrderItemSerializer + filterset_fields = ['pu_order', 'material'] + ordering = ['-create_time'] + + def get_serializer_class(self): + if self.action == 'create': + return PuOrderItemCreateSerializer + elif self.action == 'update': + return PuOrderItemUpdateSerializer + return super().get_serializer_class() + + def destroy(self, request, *args, **kwargs): + obj = self.get_object() + if obj.pu_order.is_audited: + raise ValidationError('所属采购已审核') + return super().destroy(request, *args, **kwargs) \ No newline at end of file diff --git a/hb_server/apps/qm/migrations/0023_auto_20220129_1512.py b/hb_server/apps/qm/migrations/0023_auto_20220129_1512.py new file mode 100644 index 0000000..912b50e --- /dev/null +++ b/hb_server/apps/qm/migrations/0023_auto_20220129_1512.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.9 on 2022-01-29 07:12 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('qm', '0022_auto_20211216_1401'), + ] + + operations = [ + migrations.AddField( + model_name='testrecord', + name='is_midtesing', + field=models.BooleanField(default=False, verbose_name='是否子工序检验中'), + ), + migrations.AddField( + model_name='testrecorditem', + name='create_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='testrecorditem_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='testrecorditem', + name='update_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='testrecorditem_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'), + ), + migrations.AlterField( + model_name='testrecord', + name='type', + field=models.PositiveSmallIntegerField(choices=[(20, '工序检验'), (30, '工序复检'), (36, '夹层检验'), (40, '成品检验')], default=20), + ), + ] diff --git a/hb_server/apps/qm/migrations/0024_rename_is_midtesing_testrecord_is_midtesting.py b/hb_server/apps/qm/migrations/0024_rename_is_midtesing_testrecord_is_midtesting.py new file mode 100644 index 0000000..720045d --- /dev/null +++ b/hb_server/apps/qm/migrations/0024_rename_is_midtesing_testrecord_is_midtesting.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-29 07:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0023_auto_20220129_1512'), + ] + + operations = [ + migrations.RenameField( + model_name='testrecord', + old_name='is_midtesing', + new_name='is_midtesting', + ), + ] diff --git a/hb_server/apps/qm/models.py b/hb_server/apps/qm/models.py index e892071..db776de 100644 --- a/hb_server/apps/qm/models.py +++ b/hb_server/apps/qm/models.py @@ -47,13 +47,11 @@ class TestRecord(CommonADModel): """ 检验记录 """ - TEST_STEP = 10 TEST_PROCESS = 20 TEST_PROCESS_RE = 30 TEST_COMB = 36 TEST_FINAL = 40 type_choice = ( - (TEST_STEP, '子工序检验'), (TEST_PROCESS, '工序检验'), (TEST_PROCESS_RE, '工序复检'), (TEST_COMB, '夹层检验'), @@ -70,10 +68,11 @@ class TestRecord(CommonADModel): fifo_item = models.ForeignKey('inm.fifoitem', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True) origin_test = models.ForeignKey('self', verbose_name='原检验记录', on_delete=models.CASCADE, null=True, blank=True) is_submited = models.BooleanField('是否提交', default=False) + is_midtesting = models.BooleanField('是否子工序检验中', default=False) remark = models.TextField('备注', default='') -class TestRecordItem(BaseModel): +class TestRecordItem(CommonADModel): """ 记录表格字段值 """ diff --git a/hb_server/apps/qm/serializers.py b/hb_server/apps/qm/serializers.py index 561bf2b..fd64cd6 100644 --- a/hb_server/apps/qm/serializers.py +++ b/hb_server/apps/qm/serializers.py @@ -124,14 +124,18 @@ class TestRecordUpdateSerializer(serializers.ModelSerializer): fields = ['is_testok', 'record_data'] def update(self, instance, validated_data): + # 获取更新人 + update_by = self.context['request'].user record_data = validated_data.pop('record_data') for attr, value in validated_data.items(): setattr(instance, attr, value) - instance.save() + instance.save(update_by=update_by) for i in record_data: tri = i['id'] - tri.field_value = i['field_value'] - tri.is_testok = i['is_testok'] - tri.is_hidden = i['is_hidden'] - tri.save() + if i['field_value'] != tri.field_value: + tri.field_value = i['field_value'] + tri.update_by = update_by + tri.is_testok = i['is_testok'] + tri.is_hidden = i['is_hidden'] + tri.save() return instance diff --git a/hb_server/apps/qm/views.py b/hb_server/apps/qm/views.py index 247e6d6..7cb1f40 100644 --- a/hb_server/apps/qm/views.py +++ b/hb_server/apps/qm/views.py @@ -12,7 +12,7 @@ from django.db import transaction from rest_framework.decorators import action from apps.wpm.models import WProduct -from apps.wpm.services import WpmServies +from apps.wpm.services import WpmService # Create your views here. class StandardViewSet(CreateUpdateModelAMixin, ModelViewSet): """ @@ -69,7 +69,7 @@ class TestRecordViewSet(ListModelMixin, UpdateModelMixin, RetrieveModelMixin, De def update(self, request, *args, **kwargs): obj = self.get_object() - if obj.is_submited: + if obj.is_submited and obj.is_midtesting is False: raise exceptions.APIException('该记录已提交不可编辑') return super().update(request, *args, **kwargs) @@ -77,20 +77,22 @@ class TestRecordViewSet(ListModelMixin, UpdateModelMixin, RetrieveModelMixin, De obj = self.get_object() if obj.is_submited: raise exceptions.APIException('该记录已提交不可删除') - WpmServies.add_wproduct_flow_log(obj.wproduct, 'test_delete') + WpmService.add_wproduct_flow_log(obj.wproduct, 'test_delete') return super().destroy(request, *args, **kwargs) @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TestRecordUpdateSerializer) def submit(self, request, pk=None): obj = self.get_object() + if obj.is_submited and obj.is_midtesting is False: + raise exceptions.APIException('该记录已提交') # 校验是否有未填项目 - if obj.type != TestRecord.TEST_PROCESS_RE: + if obj.type != TestRecord.TEST_PROCESS_RE and obj.is_midtesting is False: if TestRecordItem.objects.filter(field_value__isnull=True, is_hidden=False, test_record=obj).exists(): raise exceptions.APIException('存在未填写项目') with transaction.atomic(): obj.is_submited=True obj.save() - WpmServies.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算 + WpmService.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算 return Response() # def create(self, request, *args, **kwargs): diff --git a/hb_server/apps/sam/views_sale.py b/hb_server/apps/sam/views_sale.py index c67c5ca..c114d89 100644 --- a/hb_server/apps/sam/views_sale.py +++ b/hb_server/apps/sam/views_sale.py @@ -2,6 +2,7 @@ from rest_framework.mixins import ListModelMixin, DestroyModelMixin, CreateModel from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, WareHouse +from apps.inm.services import InmService from apps.mtm.models import Material from apps.sam.models import Sale, SaleProduct from apps.sam.serializers_sale import SaleCreateSerializer, SaleListSerializer, SaleProductCreateSerializer, SaleProductListSerializer @@ -11,10 +12,11 @@ from rest_framework.decorators import action from django.utils import timezone from apps.system.mixins import CreateUpdateModelAMixin -from apps.inm.signals import update_inm from rest_framework import serializers from django.db.models import Count +from utils.tools import ranstr + class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, CreateModelMixin, DestroyModelMixin, GenericViewSet): """ 销售记录 @@ -71,6 +73,7 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C fifo.auditor = request.user fifo.inout_date = timezone.now() fifo.create_by = request.user + fifo.number = 'CK' + ranstr(7) fifo.save() # 创建出库条目 ips = IProduct.objects.filter(sale_iproduct__sale=obj) @@ -104,7 +107,7 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update( act_state=WProduct.WPR_ACT_STATE_SELLED) # 更新库存 - update_inm(fifo) + InmService.update_inm(fifo) # 变更审核状态 obj.is_audited = True obj.save() diff --git a/hb_server/apps/wpm/filters.py b/hb_server/apps/wpm/filters.py index 5cfb6ea..2798e98 100644 --- a/hb_server/apps/wpm/filters.py +++ b/hb_server/apps/wpm/filters.py @@ -1,7 +1,7 @@ from django_filters import rest_framework as filters from apps.mtm.models import Material, Step -from apps.wpm.services import WpmServies +from apps.wpm.services import WpmService from utils.mixins import DynamicFieldsFilterMixin from .models import Operation, OperationMaterial, OperationRecord, WMaterial, WProduct @@ -20,10 +20,10 @@ class WMaterialFilterSet(filters.FilterSet): wproducts = WProduct.objects.filter(ow_wproduct__operation=value) step = operation.step if wproducts.exists(): - subplans = WpmServies.get_subplans_queryset_from_wproducts( + subplans = WpmService.get_subplans_queryset_from_wproducts( wproducts) else: - subplans = WpmServies.get_subplans_queyset_from_step(step) + subplans = WpmService.get_subplans_queyset_from_step(step) queryset = queryset.filter(subproduction_plan__in=subplans).exclude( material__type=Material.MA_TYPE_HALFGOOD) return queryset diff --git a/hb_server/apps/wpm/migrations/0053_auto_20220129_1512.py b/hb_server/apps/wpm/migrations/0053_auto_20220129_1512.py new file mode 100644 index 0000000..21d27fe --- /dev/null +++ b/hb_server/apps/wpm/migrations/0053_auto_20220129_1512.py @@ -0,0 +1,65 @@ +# Generated by Django 3.2.9 on 2022-01-29 07:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0045_usedstep_need_test'), + ('wpm', '0052_auto_20220125_1116'), + ] + + operations = [ + migrations.AddField( + model_name='wproduct', + name='material_check', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wp_material_check', to='mtm.material', verbose_name='按物料状态检查'), + ), + migrations.AddField( + model_name='wproductflow', + name='material_check', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wpf_material_check', to='mtm.material', verbose_name='按物料状态检查'), + ), + migrations.AlterField( + model_name='wproduct', + name='child', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wp_child', to='wpm.wproduct'), + ), + migrations.AlterField( + model_name='wproduct', + name='material', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wp_material', to='mtm.material', verbose_name='所属物料状态'), + ), + migrations.AlterField( + model_name='wproduct', + name='pre_step', + field=models.ForeignKey(blank=True, help_text='已执行完的步骤', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wp_pre_step', to='mtm.step', verbose_name='已执行到'), + ), + migrations.AlterField( + model_name='wproduct', + name='step', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wp_step', to='mtm.step', verbose_name='所在步骤'), + ), + migrations.AlterField( + model_name='wproductflow', + name='child', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wpf_child', to='wpm.wproductflow'), + ), + migrations.AlterField( + model_name='wproductflow', + name='material', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wpf_material', to='mtm.material', verbose_name='所属物料状态'), + ), + migrations.AlterField( + model_name='wproductflow', + name='pre_step', + field=models.ForeignKey(blank=True, help_text='已执行完的步骤', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wpf_pre_step', to='mtm.step', verbose_name='已执行到'), + ), + migrations.AlterField( + model_name='wproductflow', + name='step', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wpf_step', to='mtm.step', verbose_name='所在步骤'), + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index 571960e..997e65c 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -1,4 +1,5 @@ import re + from rest_framework import exceptions from django.db import models from django.db.models.base import Model @@ -88,16 +89,20 @@ class WProduct(CommonAModel): number = models.CharField( '物品编号', unique=True, null=True, blank=True, max_length=50) material = models.ForeignKey( - Material, verbose_name='所属物料状态', on_delete=models.CASCADE) + Material, verbose_name='所属物料状态', on_delete=models.CASCADE, + related_name='wp_material') + material_check = models.ForeignKey( + Material, verbose_name='按物料状态检查', on_delete=models.CASCADE, + null=True, blank=True, related_name='wp_material_check') pre_step = models.ForeignKey(Step, verbose_name='已执行到', help_text='已执行完的步骤', null=True, blank=True, - on_delete=models.CASCADE, related_name='w_pre_step') + on_delete=models.CASCADE, related_name='wp_pre_step') step = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True, - related_name='w_step') + related_name='wp_step') act_state = models.IntegerField( '进行状态', default=0, choices=act_state_choices) is_hidden = models.BooleanField('是否隐藏', default=False) child = models.ForeignKey('self', blank=True, null=True, - on_delete=models.CASCADE, related_name='wproduct_child') + on_delete=models.CASCADE, related_name='wp_child') remark = models.CharField('备注', max_length=200, null=True, blank=True) subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE, related_name='wproduct_subplan') @@ -164,16 +169,19 @@ class WproductFlow(CommonAModel): 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) + Material, verbose_name='所属物料状态', on_delete=models.CASCADE, related_name='wpf_material') + material_check = models.ForeignKey( + Material, verbose_name='按物料状态检查', on_delete=models.CASCADE, + null=True, blank=True, related_name='wpf_material_check') pre_step = models.ForeignKey(Step, verbose_name='已执行到', help_text='已执行完的步骤', null=True, blank=True, - on_delete=models.CASCADE, related_name='wl_pre_step') + on_delete=models.CASCADE, related_name='wpf_pre_step') step = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True, - related_name='wl_step') + related_name='wpf_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, related_name='wproduct_child') + on_delete=models.CASCADE, related_name='wpf_child') remark = models.CharField('备注', max_length=200, null=True, blank=True) subproduction_plan = models.ForeignKey( SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE) diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index 9636c0f..3fa0a32 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -4,8 +4,8 @@ from rest_framework.serializers import ModelSerializer from apps.em.models import Equipment 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.inm.serializers import WareHouseSimpleSerializer +from apps.inm.services import InmService from apps.mtm.models import Material, RecordForm, RecordFormField, Step, SubprodctionMaterial from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, RecordFormSimpleSerializer, StepSimpleSerializer @@ -21,6 +21,7 @@ from apps.wpm.models import Operation, OperationEquip, OperationMaterial, Operat from django.db import transaction from apps.sam.models import Order from utils.mixins import DynamicFieldsSerializerMixin +from utils.tools import ranstr class PickHalfSerializer(serializers.Serializer): id = serializers.PrimaryKeyRelatedField(queryset=SubProductionProgress.objects.all(), label='子计划进度ID') @@ -29,12 +30,14 @@ class PickHalfSerializer(serializers.Serializer): class PickHalfsSerializer(serializers.ListSerializer): child = PickHalfSerializer() + class PickDetailSerializer(serializers.Serializer): material = serializers.PrimaryKeyRelatedField(queryset=Material.objects.all(), label="物料ID") batch = serializers.CharField(label='物料批次', allow_blank=True) warehouse = serializers.PrimaryKeyRelatedField(queryset=WareHouse.objects.all(), label="仓库ID") pick_count = serializers.IntegerField(label="领料数量", required=False) - iproducts = serializers.PrimaryKeyRelatedField(queryset=IProduct.objects.all(), label='库存半成品ID',required=False, many=True) + iproducts = serializers.PrimaryKeyRelatedField(queryset=IProduct.objects.all(), label='库存半成品ID', + required=False, many=True) class PickSerializer(serializers.Serializer): subproduction_plan=serializers.PrimaryKeyRelatedField(queryset=SubProductionPlan.objects.all(), label="子计划ID") @@ -58,7 +61,9 @@ class PickSerializer(serializers.Serializer): # 创建出库记录 with transaction.atomic(): - fifo = FIFO.objects.create(type=FIFO.FIFO_TYPE_DO_OUT, inout_date=timezone.now(), create_by=self.context['request'].user) + fifo = FIFO.objects.create(type=FIFO.FIFO_TYPE_DO_OUT, + inout_date=timezone.now(), create_by=self.context['request'].user, + number = 'CK' + ranstr(7)) for i in picks: isLowLevel = False # 更新出库详情 @@ -123,7 +128,7 @@ class PickSerializer(serializers.Serializer): # 更新库存 fifo.is_audited = True fifo.save() - update_inm(fifo) + InmService.update_inm(fifo) return fifo class WMaterialListSerializer(serializers.ModelSerializer): diff --git a/hb_server/apps/wpm/services.py b/hb_server/apps/wpm/services.py index d880f7f..c3ca066 100644 --- a/hb_server/apps/wpm/services.py +++ b/hb_server/apps/wpm/services.py @@ -9,19 +9,21 @@ from apps.system.models import User from apps.wf.models import State, TicketFlow, Transition from apps.wpm.models import Operation, OperationMaterial, WProduct, WproductFlow, WprouctTicket from utils.tools import ranstr -class WpmServies(object): +class WpmService(object): @classmethod def get_next_step(cls, subproduction_plan:SubProductionPlan, nowstep:Step): """ 获取下一步骤 """ - stepIds = [i['id'] for i in subproduction_plan.steps] + steps_list = subproduction_plan.steps + stepIds = [i['id'] for i in steps_list] pindex = stepIds.index(nowstep.id) + need_test = steps_list[pindex].get('need_test', False) if pindex + 1 < len(stepIds): - return Step.objects.get(pk=stepIds[pindex+1]), True + return Step.objects.get(pk=stepIds[pindex+1]), need_test else: - return nowstep, False + return nowstep, need_test @classmethod def get_subplans_queryset_from_wproducts(cls, wproducts:List): @@ -47,9 +49,15 @@ class WpmServies(object): """ is_testok = test.is_testok wproduct = test.wproduct + test_i = None if is_testok: if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST: # 复检 wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT + + elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and \ + test.is_midtesing is True: + wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT + test_i = test 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 @@ -97,7 +105,7 @@ class WpmServies(object): wproduct.update_by = user wproduct.update_time = timezone.now() - wproduct.test = None + wproduct.test = test_i wproduct.last_test_result = is_testok wproduct.save() # 添加日志 diff --git a/hb_server/apps/wpm/signals.py b/hb_server/apps/wpm/signals.py index 9b37b54..04455d7 100644 --- a/hb_server/apps/wpm/signals.py +++ b/hb_server/apps/wpm/signals.py @@ -8,7 +8,7 @@ from django.dispatch import receiver from rest_framework import exceptions from apps.wpm.models import WProduct, WproductFlow, WprouctTicket from apps.wpm.models import OperationWproduct -from apps.wpm.services import WpmServies +from apps.wpm.services import WpmService @receiver(post_save, sender=Ticket) @@ -34,7 +34,7 @@ def handleTicket(sender, instance, created, **kwargs): # 工单绑定半成品 wproduct.ticket = instance wproduct.save() - WpmServies.add_wproduct_flow_log(wproduct, 'ticket_create') + WpmService.add_wproduct_flow_log(wproduct, 'ticket_create') elif instance.act_state == Ticket.TICKET_ACT_STATE_FINISH: @@ -77,7 +77,7 @@ def handleTicket(sender, instance, created, **kwargs): wp.save() # 添加日志 - WpmServies.add_wproduct_flow_log(wp, 'ticket_finish') + WpmService.add_wproduct_flow_log(wp, 'ticket_finish') # 更新子计划合格进度 - WpmServies.update_subproduction_progress_main(sp=wp.subproduction_plan) + WpmService.update_subproduction_progress_main(sp=wp.subproduction_plan) diff --git a/hb_server/apps/wpm/urls.py b/hb_server/apps/wpm/urls.py index b617c02..40614fd 100644 --- a/hb_server/apps/wpm/urls.py +++ b/hb_server/apps/wpm/urls.py @@ -3,7 +3,7 @@ from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.wpm.views import CuttingListViewSet, DoFormInit, DoFormSubmit, OperationEquipViewSet, OperationMaterialInputViewSet, OperationMaterialOutputViewSet, OperationMaterialToolViewSet, OperationRecordViewSet, OperationViewSet, OperationWproductViewSet, WMaterialViewSet, WPlanViewSet, WProductViewSet, WproductTicketViewSet +from apps.wpm.views import CuttingListViewSet, OperationEquipViewSet, OperationMaterialInputViewSet, OperationMaterialOutputViewSet, OperationMaterialToolViewSet, OperationRecordViewSet, OperationViewSet, OperationWproductViewSet, WMaterialViewSet, WPlanViewSet, WProductViewSet, WproductTicketViewSet router = DefaultRouter() router.register('wmaterial', WMaterialViewSet, basename='wmaterial') @@ -19,8 +19,6 @@ router.register('operation_tool', OperationMaterialToolViewSet, basename='operat router.register('subplan', WPlanViewSet, basename='wplan') router.register('cutting_list', CuttingListViewSet, basename='cutting_list') 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 a7f4529..43d9cee 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -3,7 +3,7 @@ from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, \ ListModelMixin, RetrieveModelMixin, UpdateModelMixin from rest_framework.viewsets import GenericViewSet from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct -from apps.inm.signals import update_inm +from apps.inm.services import InmService 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 @@ -32,11 +32,13 @@ from apps.wpm.serializers import CuttingListSerializer, OperationEquipListSerial from rest_framework.response import Response from django.db import transaction from rest_framework import exceptions, serializers -from apps.wpm.services import WpmServies +from apps.wpm.services import WpmService from django.utils import timezone from rest_framework import status from django.db.models import Count +from utils.tools import ranstr + # Create your views here. @@ -103,7 +105,7 @@ class WPlanViewSet(ListModelMixin, GenericViewSet): m.update_by = request.user m.update_time = timezone.now() m.save() - WpmServies.add_wproduct_flow_log( + WpmService.add_wproduct_flow_log( instance=m, change_str='pick_half') pw = PickWproduct() pw.pick = pick @@ -149,7 +151,7 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): perms_map = {'*': '*'} queryset = WProduct.objects.select_related('step', 'material', 'subproduction_plan', 'warehouse', 'subproduction_plan__production_plan__order', - 'to_order').prefetch_related('wproduct_child') + 'to_order').prefetch_related('wp_child') serializer_class = WProductListSerializer filterset_class = WProductFilterSet search_fields = ['number'] @@ -186,9 +188,8 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): WProduct.WPR_ACT_STATE_TORETEST, WProduct.WPR_ACT_STATE_TOFINALTEST, WProduct.WPR_ACT_STATE_TOCOMBTEST]: raise exceptions.APIException('该产品当前状态不可检验') - savedict = dict( - create_by=self.request.user, + create_by=request.user, wproduct=wproduct, material=wproduct.material, number=wproduct.number, @@ -206,21 +207,26 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): savedict['type'] = TestRecord.TEST_FINAL elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOCOMBTEST: savedict['type'] = TestRecord.TEST_COMB + elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and\ + wproduct.step != wproduct.pre_step: # 如果是工序内检验 + savedict['is_midtesting'] = True tr = TestRecord.objects.create(**savedict) # 更新wproduct wproduct.test = tr wproduct.update_by = request.user wproduct.update_time = timezone.now() wproduct.save() - WpmServies.add_wproduct_flow_log(wproduct, 'test_init') + WpmService.add_wproduct_flow_log(wproduct, 'test_init') # 创建检验条目 for i in RecordFormField.objects.filter(form=form, is_deleted=False): tri = TestRecordItem() tri.test_record = tr tri.form_field = i tri.is_hidden = i.is_hidden + tri.create_by = request.user tri.save() return Response(TestRecordDetailSerializer(instance=tr).data) + @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproductPutInsSerializer) @transaction.atomic @@ -242,9 +248,10 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): 'subproduction_plan', 'material', 'subproduction_plan__number').annotate(total=Count('id')) # 创建入库记录 remark = vdata.get('remark', '') - fifo = FIFO.objects.create(type=FIFO.FIFO_TYPE_DO_IN, - is_audited=True, auditor=request.user, inout_date=timezone.now(), - create_by=request.user, remark=remark) + fifo = FIFO.objects.create( + type=FIFO.FIFO_TYPE_DO_IN, + is_audited=True, auditor=request.user, inout_date=timezone.now(), + create_by=request.user, remark=remark, number='RK'+ranstr(7)) # 创建入库明细 for i in wproducts_a: spi = SubProductionPlan.objects.get(pk=i['subproduction_plan']) @@ -270,14 +277,14 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): ips.append(FIFOItemProduct(**ip)) FIFOItemProduct.objects.bulk_create(ips) # 更新库存并修改半成品进行状态 - update_inm(fifo) + InmService.update_inm(fifo) 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() - WpmServies.add_wproduct_flow_log(i, 'putins') + WpmService.add_wproduct_flow_log(i, 'putins') return Response() @action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=WproductPutInSerializer) @@ -320,11 +327,11 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): ips.append(FIFOItemProduct(**ip)) FIFOItemProduct.objects.bulk_create(ips) # 更新库存并修改半成品进行状态 - update_inm(fifo) + InmService.update_inm(fifo) wproduct.act_state = WProduct.WPR_ACT_STATE_INM wproduct.warehouse = warehouse wproduct.save() - WpmServies.add_wproduct_flow_log(wproduct, 'putin') + WpmService.add_wproduct_flow_log(wproduct, 'putin') return Response() @action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=ScrapSerializer) @@ -350,9 +357,9 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): obj.update_by = request.user obj.update_time = timezone.now() obj.save() - WpmServies.add_wproduct_flow_log(obj, 'scrap') + WpmService.add_wproduct_flow_log(obj, 'scrap') if obj.step.process.id == 1: # 如果是冷加工 - WpmServies.update_cutting_list_with_operation(obj.coperation) + WpmService.update_cutting_list_with_operation(obj.coperation) return Response() # @action(methods=['get'], detail=False, perms_map={'get':'*'}) @@ -407,14 +414,14 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): is_mtestok = request.data.get('is_mtestok') obj.is_mtestok = is_mtestok if is_mtestok: - WpmServies.update_plan_state_by_mtestok( + WpmService.update_plan_state_by_mtestok( obj.subproduction_plan.production_plan) obj.update_by = request.user obj.save() change_str = 'mtest_notok' if is_mtestok: change_str = 'mtest_ok' - WpmServies.add_wproduct_flow_log(instance=obj, change_str=change_str) + WpmService.add_wproduct_flow_log(instance=obj, change_str=change_str) return Response() @action(methods=['get'], detail=True, perms_map={'get': '*'}) @@ -457,7 +464,7 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): i.need_to_order = True i.update_by = request.user i.save() - WpmServies.add_wproduct_flow_log(i, change_str='need_to_order') + WpmService.add_wproduct_flow_log(i, change_str='need_to_order') return Response() @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproductToOrderSerializer) @@ -482,7 +489,7 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): i.to_order = order i.update_by = request.user i.save() - WpmServies.add_wproduct_flow_log(i,change_str='to_order') + WpmService.add_wproduct_flow_log(i,change_str='to_order') return Response() class WproductTicketViewSet(ListModelMixin, GenericViewSet): @@ -540,7 +547,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd i.act_state = WProduct.WPR_ACT_STATE_DOWAIT i.update_by = request.user i.save() - WpmServies.add_wproduct_flow_log(i, 'operation_delete') + WpmService.add_wproduct_flow_log(i, 'operation_delete') self.perform_destroy(instance) return Response(status=status.HTTP_204_NO_CONTENT) @@ -561,14 +568,14 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd # 创建操作所用半成品关联记录 if 'wproducts' in vdata: owps = [] - splans = WpmServies.get_subplans_queryset_from_wproducts( + splans = WpmService.get_subplans_queryset_from_wproducts( vdata['wproducts']) for wpd in vdata['wproducts']: wpd.operation = op wpd.act_state = WProduct.WPR_ACT_STATE_DOING wpd.update_by = request.user wpd.save() - WpmServies.add_wproduct_flow_log(wpd, 'operation_create') + WpmService.add_wproduct_flow_log(wpd, 'operation_create') owp = {} owp['operation'] = op owp['wproduct'] = wpd @@ -579,7 +586,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd owps.append(OperationWproduct(**owp)) OperationWproduct.objects.bulk_create(owps) else: - splans = WpmServies.get_subplans_queryset_from_wproducts( + splans = WpmService.get_subplans_queryset_from_wproducts( vdata['wproducts']) # 查询需要填写的自定义表格 forms = RecordForm.objects.filter( @@ -673,22 +680,31 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd wsp = i.subproduction_plan # 获取下一步子工序 - newstep, hasNext = WpmServies.get_next_step(wsp, step) + newstep, needTest = WpmService.get_next_step(wsp, step) wp.step = newstep wp.pre_step = step - if hasNext: - wp.act_state = WProduct.WPR_ACT_STATE_DOWAIT - else: + + if step == newstep: wp.act_state = WProduct.WPR_ACT_STATE_TOTEST wp.material = wsp.product + if wp.test:# 如果有正在进行的工序中检验 + wp.test.is_midtesting = False + wp.test.is_submited = False + wp.test.save() + else: + wp.act_state = WProduct.WPR_ACT_STATE_DOWAIT + if needTest: + wp.act_state = WProduct.WPR_ACT_STATE_TOTEST + wp.material_check = wsp.product + wp.operation = None wp.update_by = request.user wp.save() - WpmServies.add_wproduct_flow_log(wp, 'operation_submit') + WpmService.add_wproduct_flow_log(wp, 'operation_submit') for i in ows.values('subproduction_plan').distinct(): # 更新进度 - WpmServies.update_subproduction_progress_main(sp=wsp) + WpmService.update_subproduction_progress_main(sp=wsp) elif step.type == Step.STEP_TYPE_DIV: # 更新物料产出情况 @@ -696,7 +712,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd raise exceptions.APIException('请选择物料产出') for i in omos: if i.subproduction_progress.is_main: - newstep, _ = WpmServies.get_next_step( + newstep, _ = WpmService.get_next_step( i.subproduction_plan, step) wpr = dict(material=i.material, step=newstep, act_state=WProduct.WPR_ACT_STATE_DOWAIT, remark='', @@ -705,33 +721,41 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd for x in range(i.count): ins = WProduct.objects.create(**wpr) # 添加日志 - WpmServies.add_wproduct_flow_log( + WpmService.add_wproduct_flow_log( ins, 'wproduct_create') # 更新进度 - WpmServies.update_subproduction_progress_main( + WpmService.update_subproduction_progress_main( sp=i.subproduction_plan) elif step.type == Step.STEP_TYPE_COMB: oms_w = omos.filter(subproduction_progress__is_main=True) if len(oms_w) == 1: oms_w = oms_w[0] # 校验单片数量是否正确, 暂时未写 - newstep, hasNext = WpmServies.get_next_step( + newstep, needTest = WpmService.get_next_step( oms_w.subproduction_plan, step) wproduct = WProduct() wproduct.material = oms_w.material wproduct.step = newstep wproduct.subproduction_plan = oms_w.subproduction_plan - if hasNext: - wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT - else: + if step == newstep: wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST - # 更新子计划进度 - WpmServies.update_subproduction_progress_main( - sp=oms_w.subproduction_plan) + if wproduct.test:# 如果有正在进行的工序中检验 + wproduct.test.is_midtesting = False + wproduct.test.is_submited = False + wproduct.test.save() + else: + wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT + if needTest: + wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST + wproduct.material_check = wproduct.product + + # 更新子计划进度 + WpmService.update_subproduction_progress_main( + sp=oms_w.subproduction_plan) wproduct.create_by = request.user wproduct.coperation = op wproduct.save() - WpmServies.add_wproduct_flow_log(wproduct, 'wproduct_create') + WpmService.add_wproduct_flow_log(wproduct, 'wproduct_create') # 隐藏原半成品 wps = WProduct.objects.filter(ow_wproduct__operation=op) wps.update(is_hidden=True, child=wproduct, @@ -743,7 +767,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd # 如果是冷加工 if step.process.id == 1: - WpmServies.update_cutting_list_with_operation(op=op) + WpmService.update_cutting_list_with_operation(op=op) return Response() @@ -967,213 +991,3 @@ class OperationMaterialToolViewSet(ListModelMixin, CreateModelMixin, DestroyMode return Response() -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'] - splans = [] - ret_0['input'] = [] - # ret_0['subproduction_plan'] = data['subproduction_plan'] - if 'wproducts' in data and data['wproducts']: - ret_0['wproducts'] = data['wproducts'] - splans = WProduct.objects.filter(id__in=data['wproducts']).values_list( - 'subproduction_plan', flat=True) - # 调出所属子计划现有物料 - ret_0['input'] = WMaterialListSerializer(instance=WMaterial.objects.filter( - subproduction_plan__in=splans), many=True).data - else: - if 'subproduction_plan' in vdata: - splans = [vdata['subproduction_plan']] - else: - splans = SubProductionPlan.objects.filter(is_deleted=False, - subproduction__usedstep_subproduction__step=vdata[ - 'step'], - state=3) - ret_0['wproducts'] = [] - ret_0['input'] = WMaterialListSerializer(instance=WMaterial.objects.filter( - subproduction_plan__in=splans), many=True).data - - for i in ret_0['input']: - i['count_input'] = 0 - # 需要输出的物料 - if ret_0['wproducts']: - # 排除主要产物, 因为已经放到半成品里了, 由半成品进行处理, 夹层可能需要特殊处理 - o_objs = SubProductionProgress.objects.filter( - subproduction_plan__in=splans, type=SubprodctionMaterial.SUB_MA_TYPE_OUT).exclude(is_main=True) - - else: - # 此时显示所有子计划需要输出的物料 - o_objs = SubProductionProgress.objects.filter( - subproduction_plan__in=splans, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) - ret_0['output'] = list(o_objs.values( - 'subproduction_plan', '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_0['tools'] = SubprodctionMaterialListSerializer( - instance=SubprodctionMaterial.objects.filter(type=SubprodctionMaterial.SUB_MA_TYPE_TOOL, - subproduction__subplan_subprod__in=splans), many=True).data - # 查询技术文档 - ret_0['techdocs'] = TechDocListSerializer( - instance=TechDoc.objects.filter( - subproduction__subplan_subprod__in=splans, enabled=True) - .distinct(), many=True).data - - 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.step = vdata['step'] - action_obj.remark = vdata.get('remark', '') # 操作备注 - action_obj.create_by = request.user - action_obj.use_scrap = vdata.get('use_scrap', False) - action_obj.save() - - # 保存关联半成品 - if 'wproducts' in data and data['wproducts']: - owps = [] - for i in data['wproducts']: - owp = {} - owp['operation'] = action_obj - wp = WProduct.objects.get(pk=i) - owp['wproduct'] = wp - owp['number'] = wp.number - owp['material'] = wp.material - owp['subproduction_plan'] = wp.subproduction_plan - owps.append(OperationWproduct(**owp)) - OperationWproduct.objects.bulk_create(owps) - - # 保存物料消耗 - 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 i['subproduction_plan'].product == ma: # 如果是该计划主产物 - # 如果是切割 - # 获取下一步子工序 - if vdata['step'].type == Step.STEP_TYPE_DIV: - newstep, _ = WpmServies.get_next_step( - i['subproduction_plan'], vdata['step']) - wpr = dict(material=ma, step=newstep, - act_state=WProduct.WPR_ACT_STATE_DOWAIT, remark='', - subproduction_plan=i['subproduction_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=i['subproduction_plan'], - material=ma) - ins.count = ins.count + i['count_output'] - ins.save() - # 更新子计划进度表 - sp = SubProductionProgress.objects.get(subproduction_plan=i['subproduction_plan'], - material=ma) - sp.count_real = sp.count_real + i['count_input'] - sp.save() - - # 更新动态产品表 - if 'wproducts' in vdata and vdata['wproducts']: - if vdata['step'].type == Step.STEP_TYPE_COMB: - wproducts = vdata['wproducts'] - if 'suproduction_plan' in vdata: - wproducts.update(is_hidden=True) # 隐藏 - newstep, hasNext = WpmServies.get_next_step( - i['subproduction_plan'], vdata['step']) - wproduct = WProduct() - wproduct.material = vdata['subproduction_plan'].product - wproduct.step = newstep - wproduct.subproduction_plan = vdata['subproduction_plan'] - wproduct.parent = data['wproducts'] - if hasNext: - wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT - else: - wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST - wproduct.save() - else: - raise exceptions.APIException('请指定子计划') - else: - for wproduct in vdata['wproducts']: - # 获取下一步子工序 - newstep, hasNext = WpmServies.get_next_step( - wproduct.subproduction_plan, vdata['step']) - wproduct.step = newstep - wproduct.pre_step = vdata['step'] - if hasNext: - wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT - else: - wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST - wproduct.material = wproduct.subproduction_plan.product - wproduct.save() - - # 保存自定义表单结果 - 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()