From bc2a16a9a4675d4905c8ee24d2029c7de12c4c81 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 24 Oct 2023 10:26:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=BB=84=E5=90=88?= =?UTF-8?q?=E4=BB=B6=E5=87=BA=E5=85=A5=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/inm/filters.py | 13 +++ .../0005_materialbatcha_mioitema.py | 48 +++++++++++ apps/inm/models.py | 24 ++++++ apps/inm/serializers.py | 80 +++++++++++++++++-- apps/inm/services.py | 19 +++-- apps/inm/views.py | 8 +- apps/wpm/filters.py | 22 ++++- apps/wpm/services.py | 44 ++++++---- apps/wpm/views.py | 4 +- 9 files changed, 224 insertions(+), 38 deletions(-) create mode 100644 apps/inm/filters.py create mode 100644 apps/inm/migrations/0005_materialbatcha_mioitema.py diff --git a/apps/inm/filters.py b/apps/inm/filters.py new file mode 100644 index 00000000..8fd8edd9 --- /dev/null +++ b/apps/inm/filters.py @@ -0,0 +1,13 @@ +from django_filters import rest_framework as filters +from apps.inm.models import MaterialBatch + + +class MaterialBatchFilter(filters.FilterSet): + + class Meta: + model = MaterialBatch + fields = { + "warehouse": ["exact"], + "material": ["exact"], + "count": ["exact", "gte", "lte"] + } diff --git a/apps/inm/migrations/0005_materialbatcha_mioitema.py b/apps/inm/migrations/0005_materialbatcha_mioitema.py new file mode 100644 index 00000000..d40e7c95 --- /dev/null +++ b/apps/inm/migrations/0005_materialbatcha_mioitema.py @@ -0,0 +1,48 @@ +# Generated by Django 3.2.12 on 2023-10-24 02:25 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0017_auto_20231018_1033'), + ('inm', '0004_auto_20230927_1700'), + ] + + operations = [ + migrations.CreateModel( + name='MIOItemA', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')), + ('batch', models.CharField(max_length=50, verbose_name='批次号')), + ('rate', models.PositiveIntegerField(default=1, verbose_name='比例')), + ('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='物料')), + ('mioitem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='a_mioitem', to='inm.mioitem', verbose_name='关联出入库明细')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='MaterialBatchA', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')), + ('batch', models.CharField(max_length=100, verbose_name='批次号')), + ('rate', models.PositiveIntegerField(default=1, verbose_name='比例')), + ('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='物料')), + ('mb', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='a_mb', to='inm.materialbatch', verbose_name='关联物料批次')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/inm/models.py b/apps/inm/models.py index 21714466..2c62b5c1 100644 --- a/apps/inm/models.py +++ b/apps/inm/models.py @@ -29,6 +29,18 @@ class MaterialBatch(BaseModel): expiration_date = models.DateField('有效期', null=True, blank=True) +class MaterialBatchA(BaseModel): + """ + 组合件物料批次 + """ + batch = models.CharField('批次号', max_length=100) + material = models.ForeignKey( + Material, on_delete=models.CASCADE, verbose_name='物料') + rate = models.PositiveIntegerField('比例', default=1) + mb = models.ForeignKey( + MaterialBatch, verbose_name='关联物料批次', on_delete=models.CASCADE, related_name='a_mb') + + class MIO(CommonBDModel): """ 出入库记录 @@ -89,3 +101,15 @@ class MIOItem(BaseModel): Material, verbose_name='物料', on_delete=models.CASCADE) batch = models.CharField('批次号', max_length=50) count = models.PositiveIntegerField('数量', default=0) + + +class MIOItemA(BaseModel): + """ + 组合件出入库明细 + """ + material = models.ForeignKey( + Material, verbose_name='物料', on_delete=models.CASCADE) + batch = models.CharField('批次号', max_length=50) + rate = models.PositiveIntegerField('比例', default=1) + mioitem = models.ForeignKey( + MIOItem, verbose_name='关联出入库明细', on_delete=models.CASCADE, related_name='a_mioitem') diff --git a/apps/inm/serializers.py b/apps/inm/serializers.py index 2a50731b..68fd88a2 100644 --- a/apps/inm/serializers.py +++ b/apps/inm/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from rest_framework.exceptions import ValidationError +from rest_framework.exceptions import ValidationError, ParseError from apps.mtm.serializers import MaterialSerializer from apps.pum.models import PuOrder @@ -8,7 +8,8 @@ from apps.system.models import Dept, User from apps.utils.constants import EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS from apps.utils.serializers import CustomModelSerializer -from .models import MIO, MaterialBatch, MIOItem, WareHouse +from .models import MIO, MaterialBatch, MIOItem, WareHouse, MIOItemA, MaterialBatchA +from django.db import transaction class WareHourseSerializer(CustomModelSerializer): @@ -18,6 +19,14 @@ class WareHourseSerializer(CustomModelSerializer): read_only_fields = EXCLUDE_FIELDS_DEPT +class MaterialBatchAListSerializer(CustomModelSerializer): + material_ = MaterialSerializer(source='material', read_only=True) + + class Meta: + model = MaterialBatchA + fields = ['material', 'batch', 'rate', 'mb', 'id', 'material_'] + + class MaterialBatchSerializer(CustomModelSerializer): warehouse_name = serializers.CharField( source='warehouse.name', read_only=True) @@ -31,6 +40,20 @@ class MaterialBatchSerializer(CustomModelSerializer): read_only_fields = EXCLUDE_FIELDS_BASE +class MaterialBatchDetailSerializer(CustomModelSerializer): + warehouse_name = serializers.CharField( + source='warehouse.name', read_only=True) + material_name = serializers.CharField( + source='material.name', read_only=True) + material_ = MaterialSerializer(source='material', read_only=True) + assemb = MaterialBatchAListSerializer( + source='a_mb', read_only=True, many=True) + + class Meta: + model = MaterialBatch + fields = '__all__' + + class MIOListSerializer(CustomModelSerializer): create_by_name = serializers.CharField( source='create_by.name', read_only=True) @@ -78,20 +101,61 @@ class MIOListSerializer(CustomModelSerializer): # return super().update(instance, validated_data) +class MIOItemACreateSerializer(CustomModelSerializer): + class Meta: + model = MIOItem + fields = ['material', 'batch'] + + +class MIOItemCreateSerializer(CustomModelSerializer): + assemb = MIOItemACreateSerializer( + label='组合件信息', many=True, write_only=True, required=False) + + class Meta: + model = MIOItem + fields = ['mio', 'warehouse', 'material', 'batch', 'count', 'assemb'] + + def create(self, validated_data): + mio = validated_data['mio'] + material = validated_data['material'] + if mio.state != MIO.MIO_CREATE: + raise ValidationError('出入库记录非创建中不可新增') + with transaction.atomic(): + assemb = validated_data.pop('assemb', []) + instance = super().create(validated_data) + assemb_dict = {} + for i in assemb: + assemb_dict[i['material'].id] = i + if material.is_assemb and '_in' in mio.type: # 仅入库且是组合件的时候需要填写下一级 + components = material.components + for k, v in components.items(): + if k in assemb_dict: + mia = assemb_dict[k] + MIOItemA.objects.create( + mioitem=instance, material=mia['material'], batch=mio['batch'], count=v) + else: + raise ParseError('缺少组合件') + return instance + + +class MIOItemAListSerializer(CustomModelSerializer): + material_ = MaterialSerializer(source='material', read_only=True) + + class Meta: + model = MIOItemA + fields = ['material', 'batch', 'rate', 'mioitem', 'id', 'material_'] + + class MIOItemSerializer(CustomModelSerializer): warehouse_name = serializers.CharField(source='warehouse', read_only=True) material_ = MaterialSerializer(source='material', read_only=True) + assemb = MIOItemAListSerializer( + source='a_mioitem', read_only=True, many=True) class Meta: model = MIOItem fields = '__all__' - def create(self, validated_data): - mio = validated_data['mio'] - if mio.state != MIO.MIO_CREATE: - raise ValidationError('出入库记录非创建中不可新增') - return super().create(validated_data) - class MIODoSerializer(CustomModelSerializer): belong_dept = serializers.PrimaryKeyRelatedField( diff --git a/apps/inm/services.py b/apps/inm/services.py index 7132b5da..5b596a1d 100644 --- a/apps/inm/services.py +++ b/apps/inm/services.py @@ -1,5 +1,5 @@ -from apps.inm.models import MIO, MIOItem, MaterialBatch -from rest_framework.exceptions import ValidationError +from apps.inm.models import MIO, MIOItem, MaterialBatch, MaterialBatchA, MIOItemA +from rest_framework.exceptions import ValidationError, ParseError from django.db.models.aggregates import Sum from apps.wpm.services import do_out, do_in from apps.mtm.models import Material @@ -35,20 +35,27 @@ class InmService: for i in MIOItem.objects.filter(mio=instance): material = i.material warehouse = i.warehouse - mb, _ = MaterialBatch.objects.get_or_create(material=material, warehouse=warehouse, batch=i.batch, - defaults={'material': material, 'warehouse': warehouse, 'count': 0, 'batch': i.batch}) + mb, is_created = MaterialBatch.objects.get_or_create(material=material, warehouse=warehouse, batch=i.batch, + defaults={'material': material, 'warehouse': warehouse, 'count': 0, 'batch': i.batch}) if in_or_out == 1: mb.count = mb.count + i.count # if mb.expiration_date is None: # mb.expiration_date = i.expiration_date mb.save() + mias = MIOItemA.objects.filter(mioitem=i) + if mias.exists(): # 组合件入库 + if not is_created: + raise ParseError('该批次组合件已存在') + for mia in mias: + MaterialBatchA.objects.create( + mb=mb, material=mia.material, batch=mia.batch, count=mia.count) elif in_or_out == -1: mb.count = mb.count - i.count if mb.count < 0: - raise ValidationError('批次库存不足,操作失败') + raise ParseError('批次库存不足,操作失败') mb.save() else: - raise ValidationError('不支持的操作') + raise ParseError('不支持的操作') material_count = MaterialBatch.objects.filter( material=material).aggregate(total=Sum('count')).get('total', 0) Material.objects.filter(id=material.id).update( diff --git a/apps/inm/views.py b/apps/inm/views.py index fea861ab..ee22ad9c 100644 --- a/apps/inm/views.py +++ b/apps/inm/views.py @@ -9,11 +9,13 @@ from rest_framework.response import Response from apps.inm.models import WareHouse, MaterialBatch, MIO, MIOItem from apps.inm.serializers import ( - MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer) + MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, + MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer, MaterialBatchDetailSerializer) from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet from apps.inm.services import InmService from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, BulkUpdateModelMixin from apps.utils.permission import has_perm +from .filters import MaterialBatchFilter # Create your views here. @@ -43,8 +45,9 @@ class MaterialBatchViewSet(ListModelMixin, CustomGenericViewSet): perms_map = {'get': '*'} queryset = MaterialBatch.objects.all() serializer_class = MaterialBatchSerializer + retrieve_serializer_class = MaterialBatchDetailSerializer select_related_fields = ['warehouse', 'material'] - filterset_fields = ['warehouse', 'material'] + filterset_class = MaterialBatchFilter search_fields = ['material__name'] @@ -168,6 +171,7 @@ class MIOItemViewSet(ListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin perms_map = {'get': '*', 'post': '*', 'delete': '*'} queryset = MIOItem.objects.all() serializer_class = MIOItemSerializer + create_serializer_class = MIOItemCreateSerializer select_related_fields = ['warehouse', 'mio', 'material'] filterset_fields = ['warehouse', 'mio', 'material'] ordering = ['create_time'] diff --git a/apps/wpm/filters.py b/apps/wpm/filters.py index a89790cf..40fd641a 100644 --- a/apps/wpm/filters.py +++ b/apps/wpm/filters.py @@ -1,6 +1,8 @@ from django_filters import rest_framework as filters -from apps.wpm.models import SfLog, SfLogExp +from apps.wpm.models import SfLog, SfLogExp, WMaterial ['mgroup', 'shift', 'team', 'leader', 'team__belong_dept'] + + class SfLogFilter(filters.FilterSet): class Meta: model = SfLog @@ -14,8 +16,10 @@ class SfLogFilter(filters.FilterSet): "start_time": ["gte"] } + class SfLogExpFilter(filters.FilterSet): is_st = filters.BooleanFilter(method='filter_is_st', label='是否停机') + class Meta: model = SfLogExp fields = { @@ -28,8 +32,20 @@ class SfLogExpFilter(filters.FilterSet): "stlog__start_time": ["day", "month", "year"], "stlog__end_time": ["day", "month", "year"] } - + def filter_is_st(self, queryset, name, value): if value: return queryset.exclude(stlog=None) - return queryset.filter(stlog=None) \ No newline at end of file + return queryset.filter(stlog=None) + + +class WMaterialFilter(filters.FilterSet): + class Meta: + model = WMaterial + fields = { + "material": ["exact", "in"], + "belong_dept": ["exact"], + "belong_dept__name": ["exact"], + "batch": ["exact"], + "count": ["gte", "lte", "exact"] + } diff --git a/apps/wpm/services.py b/apps/wpm/services.py index 95dc663b..1895c402 100644 --- a/apps/wpm/services.py +++ b/apps/wpm/services.py @@ -7,7 +7,7 @@ from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from rest_framework.exceptions import ParseError -from apps.inm.models import MIO, MIOItem +from apps.inm.models import MIO, MIOItem, MIOItemA from apps.pm.models import Mtask from apps.mtm.models import Mgroup, Shift, Material @@ -80,28 +80,38 @@ def do_out(mio: MIO): def do_in(mio: MIO): """ - 生产入库 + 生产入库后更新车间物料 """ belong_dept = mio.belong_dept do_user = mio.do_user mioitems = MIOItem.objects.filter(mio=mio) for item in mioitems: - try: - wm = WMaterial.objects.get( - batch=item.batch, material=item.material, belong_dept=belong_dept) - except ObjectDoesNotExist: - raise ParseError('车间物料不存在!') - except MultipleObjectsReturned: - raise ParseError('存在多行车间物料!') - new_count = wm.count - item.count - if new_count > 0: - wm.count = new_count - wm.update_by = do_user - wm.save() - elif new_count == 0: - wm.delete() + action_list = [] + mias = MIOItemA.objects.filter(mioitem=item) + if mias.exists(): # 组合件入库 + action_list = list(mias.values_list('material', 'batch', 'rate')) + for i in action_list: + i[2] = i[2]*item.count else: - raise ParseError('车间物料不足') + action_list = [item.material, item.batch, item.count] + for al in action_list: + xmaterial, xbatch, xcount = al + try: + wm = WMaterial.objects.get( + batch=xbatch, material=xmaterial, belong_dept=belong_dept) + except ObjectDoesNotExist: + raise ParseError('车间物料不存在!') + except MultipleObjectsReturned: + raise ParseError('存在多行车间物料!') + new_count = wm.count - xcount + if new_count > 0: + wm.count = new_count + wm.update_by = do_user + wm.save() + elif new_count == 0: + pass + else: + raise ParseError('车间物料不足') def mlog_confirm(mlog: Mlog): diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 486f107f..d6a86165 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -10,7 +10,7 @@ from apps.pm.models import Mtask from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet from apps.utils.mixins import BulkCreateModelMixin -from .filters import SfLogExpFilter, SfLogFilter +from .filters import SfLogExpFilter, SfLogFilter, WMaterialFilter from .models import SfLog, SfLogExp, StLog, WMaterial, Mlog from .serializers import SflogExpSerializer, SfLogSerializer, StLogSerializer, WMaterialSerializer, MlogSerializer, MlogRelatedSerializer, DeptBatchSerializer @@ -95,7 +95,7 @@ class WMaterialViewSet(ListModelMixin, CustomGenericViewSet): select_related_fields = ['material', 'belong_dept'] search_fields = ['material__name', 'material__number', 'material__specification'] - filterset_fields = ['material', 'belong_dept'] + filterset_class = WMaterialFilter @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=DeptBatchSerializer) @transaction.atomic