diff --git a/hb_server/apps/inm/views.py b/hb_server/apps/inm/views.py index 0fd15d8..2c3bd49 100644 --- a/hb_server/apps/inm/views.py +++ b/hb_server/apps/inm/views.py @@ -74,7 +74,7 @@ class FIFODetailViewSet(ListModelMixin, GenericViewSet): search_fields = [] ordering_fields = ['create_time'] ordering = ['-create_time'] - + class FIFOViewSet(ListModelMixin, GenericViewSet): """ 出入库记录 @@ -111,6 +111,8 @@ class FIFOViewSet(ListModelMixin, GenericViewSet): for i in FIFODetail.objects.filter(fifo=obj): if not i.is_testok: raise APIException('未检验通过, 不可审核') + if obj.is_audited: + raise APIException('该入库记录已审核通过') obj.is_audited = True obj.save() update_inm(obj) # 更新库存 diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 49f4b87..d9e8411 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -61,6 +61,11 @@ class StepDetailSerializer(serializers.ModelSerializer): queryset = queryset.prefetch_related('equipments') return queryset +class SubProductionSimpleSerializer(serializers.ModelSerializer): + class Meta: + model = SubProduction + fields = ['id', 'name'] + class SubProductionSerializer(serializers.ModelSerializer): process_ = ProcessSimpleSerializer(source='process', read_only=True) class Meta: diff --git a/hb_server/apps/pm/apps.py b/hb_server/apps/pm/apps.py index c0c4b31..7fa3289 100644 --- a/hb_server/apps/pm/apps.py +++ b/hb_server/apps/pm/apps.py @@ -5,3 +5,5 @@ class SamConfig(AppConfig): verbose_name = '生产计划管理' + def ready(self): + import apps.pm.signals \ No newline at end of file diff --git a/hb_server/apps/pm/migrations/0010_auto_20211103_1700.py b/hb_server/apps/pm/migrations/0010_auto_20211103_1700.py new file mode 100644 index 0000000..ba5b40c --- /dev/null +++ b/hb_server/apps/pm/migrations/0010_auto_20211103_1700.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.6 on 2021-11-03 09:00 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0028_auto_20211102_1707'), + ('pm', '0009_auto_20211029_1017'), + ] + + operations = [ + migrations.AddField( + model_name='subproductionplan', + name='main_count', + field=models.IntegerField(default=1, verbose_name='应产出数'), + preserve_default=False, + ), + migrations.AddField( + model_name='subproductionplan', + name='main_count_real', + field=models.IntegerField(default=0, verbose_name='实际产出数'), + ), + migrations.AddField( + model_name='subproductionplan', + name='main_product', + field=models.ForeignKey(default=19, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='主要产品'), + preserve_default=False, + ), + migrations.AddField( + model_name='subproductionprogress', + name='count_pick', + field=models.IntegerField(default=0, verbose_name='实际领用数'), + ), + migrations.AddField( + model_name='subproductionprogress', + name='is_main', + field=models.BooleanField(default=False, verbose_name='是否主产出'), + ), + migrations.AlterField( + model_name='subproductionprogress', + name='count', + field=models.IntegerField(verbose_name='应出入数'), + ), + migrations.AlterField( + model_name='subproductionprogress', + name='count_real', + field=models.IntegerField(default=0, verbose_name='实际消耗/产出数'), + ), + ] diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py index 2a00e1a..f528c64 100644 --- a/hb_server/apps/pm/models.py +++ b/hb_server/apps/pm/models.py @@ -43,9 +43,16 @@ class SubProductionPlan(CommonAModel): subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE) start_date = models.DateField('计划开工日期') end_date = models.DateField('计划完工日期') + workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE) + + main_product = models.ForeignKey(Material, verbose_name='主要产品', on_delete=models.CASCADE) + main_count = models.IntegerField('应产出数') + main_count_real = models.IntegerField('实际产出数', default=0) + steps = models.JSONField('工艺步骤', default=list) + state = models.IntegerField('状态', default=0) start_date_real = models.DateField('实际开工日期', null=True, blank=True) end_date_real = models.DateField('实际完工日期', null=True, blank=True) @@ -65,6 +72,8 @@ class SubProductionProgress(BaseModel): ) subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE, related_name='progress_subplan') material = models.ForeignKey(Material, verbose_name='关联物料', on_delete=models.CASCADE) + is_main = models.BooleanField('是否主产出', default=False) type = models.IntegerField('物料应用类型', default=1) - count = models.FloatField('应出入数') - count_real = models.IntegerField('实际出入数', default=0) + count = models.IntegerField('应出入数') + count_pick = models.IntegerField('实际领用数', default=0) + count_real = models.IntegerField('实际消耗/产出数', default=0) diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py index 8b170a5..735b0cc 100644 --- a/hb_server/apps/pm/serializers.py +++ b/hb_server/apps/pm/serializers.py @@ -1,7 +1,7 @@ from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress from rest_framework import serializers from apps.sam.serializers import OrderSerializer -from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer +from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, SubProductionSimpleSerializer from apps.system.serializers import OrganizationSimpleSerializer @@ -27,10 +27,15 @@ class ResourceCalListSerializer(serializers.ListSerializer): class SubProductionPlanListSerializer(serializers.ModelSerializer): workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True) process_ = ProcessSimpleSerializer(source='process', read_only=True) + subproduction_ = SubProductionSimpleSerializer(source='subproduction', read_only=True) + main_product_ = MaterialSimpleSerializer(source='main_product', read_only=True) class Meta: model=SubProductionPlan fields = '__all__' + + + class SubProductionPlanUpdateSerializer(serializers.ModelSerializer): class Meta: model = SubProductionPlan diff --git a/hb_server/apps/pm/signals.py b/hb_server/apps/pm/signals.py new file mode 100644 index 0000000..69aa69c --- /dev/null +++ b/hb_server/apps/pm/signals.py @@ -0,0 +1,18 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver +from apps.pm.models import SubProductionProgress + +@receiver(post_save, sender=SubProductionProgress) +def update_subplan_main(sender, instance, created, **kwargs): + """ + 根据生产进度统计表更新子计划进度表相关字段 + """ + if instance.is_main: + subplan = instance.subproduction_plan + if created: + subplan.main_product = instance.material + subplan.main_count = instance.count + subplan.main_count_real = instance.count_real + subplan.save() + + diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index ac35f84..c646ab6 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -87,8 +87,11 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel start_date=production_plan.start_date, end_date=production_plan.end_date, workshop=i.process.workshop, process=i.process, create_by=request.user, steps = list(steps)) + # 生成子计划物料需求/进度 for m in SubprodctionMaterial.objects.filter(subproduction=i, is_deleted=False).order_by('sort'): - SubProductionProgress.objects.create(material=m.material, type=m.type, count=m.count*production_plan.count, subproduction_plan=instance) + spro = SubProductionProgress.objects.create(material=m.material, type=m.type, + is_main=m.is_main, + count=m.count*production_plan.count, subproduction_plan=instance) production_plan.is_planed=True production_plan.save() return Response() @@ -98,10 +101,10 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo 子生产计划-列表/修改 """ perms_map = {'*': '*'} - queryset = SubProductionPlan.objects.select_related('process', 'workshop') + queryset = SubProductionPlan.objects.select_related('process', 'workshop', 'subproduction', 'main_product') search_fields = [] serializer_class = SubProductionPlanListSerializer - filterset_fields = ['production_plan', 'process', 'state'] + filterset_fields = ['production_plan', 'process', 'state', 'main_product', 'workshop'] ordering_fields = ['process__number'] ordering = ['process__number'] diff --git a/hb_server/apps/wpm/migrations/0004_auto_20211104_0914.py b/hb_server/apps/wpm/migrations/0004_auto_20211104_0914.py new file mode 100644 index 0000000..85eb696 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0004_auto_20211104_0914.py @@ -0,0 +1,61 @@ +# Generated by Django 3.2.6 on 2021-11-04 01:14 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0010_auto_20211103_1700'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mtm', '0028_auto_20211102_1707'), + ('wpm', '0003_auto_20211102_0935'), + ] + + operations = [ + migrations.CreateModel( + name='WProductRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('data', models.JSONField(blank=True, default=dict, verbose_name='记录的数据')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('record_form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.recordform', verbose_name='所用表格')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.RemoveField( + model_name='wmaterial', + name='process', + ), + migrations.RemoveField( + model_name='wmaterial', + name='workshop', + ), + migrations.RemoveField( + model_name='wproductflow', + name='wproduct', + ), + migrations.AddField( + model_name='wmaterial', + name='subproduction_plan', + field=models.ForeignKey(default=68, on_delete=django.db.models.deletion.CASCADE, to='pm.subproductionplan', verbose_name='关联子计划'), + preserve_default=False, + ), + migrations.AddField( + model_name='wproductflow', + name='wproducts', + field=models.JSONField(default=list, verbose_name='关联产品'), + ), + migrations.DeleteModel( + name='WProductForm', + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index 97dfa62..31bd224 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -12,8 +12,9 @@ class WMaterial(BaseModel): """ 车间生产物料 """ - workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE) - process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE) + # workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE) + # process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE) + subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子计划', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='关联物料', on_delete=models.CASCADE) batch = models.CharField('批次号', max_length=100, null=True, blank=True) count = models.IntegerField('当前数量', default=0) @@ -36,7 +37,7 @@ class WProduct(CommonAModel): subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE) production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE) -class WProductForm(CommonAModel): +class WProductRecord(CommonAModel): """ 记录表格 """ @@ -46,6 +47,6 @@ class WProductForm(CommonAModel): class WProductFlow(BaseModel): """ - 产品流转日志 + 生产操作日志 """ - wproduct = models.ForeignKey(WProduct, verbose_name='产品', on_delete=models.CASCADE) \ No newline at end of file + wproducts = models.JSONField('关联产品', default=list) \ No newline at end of file diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index bec720e..b70d745 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -2,7 +2,7 @@ from rest_framework import serializers from rest_framework.serializers import ModelSerializer from apps.inm.models import FIFO, FIFODetail, MaterialBatch, WareHouse from apps.inm.signals import update_inm -from apps.mtm.models import Material +from apps.mtm.models import Material, Step from apps.mtm.serializers import MaterialSimpleSerializer from apps.pm.models import SubProductionPlan, SubProductionProgress @@ -23,6 +23,8 @@ class PickSerializer(serializers.Serializer): def create(self, validated_data): picks = validated_data.pop('picks') sp = validated_data['subproduction_plan'] + if sp.state not in [1,2]: + raise serializers.ValidationError('该子计划状态错误') if sp.is_picked: raise serializers.ValidationError('该子计划已领料') for i in picks: @@ -46,20 +48,21 @@ class PickSerializer(serializers.Serializer): FIFODetail.objects.create(**i) # 更新车间物料 wm, _ = WMaterial.objects.get_or_create(material=i['material'], batch=i['batch'], \ - process=sp.process,defaults={ + subproduction_plan=sp,defaults={ 'material':i['material'], 'batch':i['batch'], - 'process':sp.process, - 'workshop':sp.workshop, + 'subproduction_plan':sp, 'count':0 }) wm.count = wm.count + i['count'] wm.save() # 更新子计划物料情况 spp = SubProductionProgress.objects.get(material=i['material'], subproduction_plan=sp, type=1) - spp.count_real = spp.count_real + i['count'] + spp.count_pick = spp.count_pick + i['count'] spp.save() sp.is_picked=True + sp.state = 3 #生产中 + sp.state_date_real = timezone.now() #实际开工日期 sp.save() # 更新库存 fifo.is_audited = True @@ -74,4 +77,8 @@ class WMaterialListSerializer(serializers.ModelSerializer): material_ = MaterialSimpleSerializer(source='material', read_only=True) class Meta: model = WMaterial - fields = '__all__' \ No newline at end of file + fields = '__all__' + + +class DoFormInitSerializer(serializers.Serializer): + action = serializers.PrimaryKeyRelatedField(queryset=Step.objects.all()) \ No newline at end of file diff --git a/hb_server/apps/wpm/urls.py b/hb_server/apps/wpm/urls.py index d4685ec..d6aad5e 100644 --- a/hb_server/apps/wpm/urls.py +++ b/hb_server/apps/wpm/urls.py @@ -3,10 +3,11 @@ from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.wpm.views import WMaterialViewSet +from apps.wpm.views import WMaterialViewSet, WPlanViewSet router = DefaultRouter() router.register('wmaterial', WMaterialViewSet, basename='wmaterial') +router.register('subplan', WPlanViewSet, basename='wplan') urlpatterns = [ path('', include(router.urls)), ] diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index 8a3dc4e..74ee648 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -3,7 +3,8 @@ from rest_framework.generics import CreateAPIView, GenericAPIView from rest_framework.mixins import ListModelMixin from rest_framework.utils.field_mapping import get_relation_kwargs from rest_framework.viewsets import GenericViewSet, ModelViewSet -from apps.pm.serializers import SubProductionPlanUpdateSerializer +from apps.pm.models import SubProductionPlan +from apps.pm.serializers import SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action @@ -12,6 +13,18 @@ from apps.wpm.models import WMaterial from apps.wpm.serializers import PickSerializer, WMaterialListSerializer from rest_framework.response import Response # Create your views here. +class WPlanViewSet(ListModelMixin, GenericViewSet): + """ + 车间生产计划 + """ + perms_map = {'*': '*'} + queryset = SubProductionPlan.objects.select_related('process', 'workshop', 'subproduction', 'main_product').exclude(state=0) + search_fields = [] + serializer_class = SubProductionPlanListSerializer + filterset_fields = ['production_plan', 'process', 'state', 'main_product', 'workshop'] + ordering_fields = ['process__number'] + ordering = ['process__number'] + class WMaterialViewSet(CreateUpdateModelAMixin, ListModelMixin, GenericViewSet): """ 车间物料表 @@ -19,7 +32,7 @@ class WMaterialViewSet(CreateUpdateModelAMixin, ListModelMixin, GenericViewSet): perms_map={'*':'*'} queryset = WMaterial.objects.select_related('material').all() serializer_class = WMaterialListSerializer - filterset_fields = ['material', 'process', 'workshop'] + filterset_fields = ['material', 'subproduction_plan', 'subproduction_plan__process', 'subproduction_plan__workshop'] ordering_fields = ['material__number'] ordering = ['material__number'] diff --git a/hb_server/utils/model.py b/hb_server/utils/model.py index 05d04ab..fb7fe70 100644 --- a/hb_server/utils/model.py +++ b/hb_server/utils/model.py @@ -2,9 +2,8 @@ from django.db import models import django.utils.timezone as timezone from django.db.models.query import QuerySet + # 自定义软删除查询基类 - - class SoftDeletableQuerySetMixin(object): ''' QuerySet for SoftDeletableModel. Instead of removing instance sets @@ -63,6 +62,24 @@ class BaseModel(models.Model): class Meta: abstract = True + def save(self, *args, **kwargs): + if self.pk: + # If self.pk is not None then it's an update. + cls = self.__class__ + old = cls.objects.get(pk=self.pk) + # This will get the current model state since super().save() isn't called yet. + new = self # This gets the newly instantiated Mode object with the new values. + changed_fields = [] + for field in cls._meta.get_fields(): + field_name = field.name + try: + if getattr(old, field_name) != getattr(new, field_name): + changed_fields.append(field_name) + except Exception as ex: # Catch field does not exist exception + pass + kwargs['update_fields'] = changed_fields + super().save(*args, **kwargs) + class SoftModel(BaseModel): """ 软删除基本表