From b7ba095de700254cd15dc0e0a9abf12585b689c8 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Sun, 8 Oct 2023 11:27:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=94=9F=E4=BA=A7?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E8=A1=A8=E5=8F=8A=E5=88=9B=E5=BB=BA=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/mtm/serializers.py | 37 ++++++++++++++---- apps/pm/migrations/0004_auto_20231008_1126.py | 30 +++++++++++++++ apps/pm/models.py | 8 ++-- apps/pm/services.py | 5 +++ apps/wpm/migrations/0012_mlog.py | 38 +++++++++++++++++++ apps/wpm/models.py | 14 +++++++ apps/wpm/serializers.py | 14 ++++++- apps/wpm/services.py | 31 ++++++++++++++- apps/wpm/urls.py | 3 +- apps/wpm/views.py | 24 +++++++++++- 10 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 apps/pm/migrations/0004_auto_20231008_1126.py create mode 100644 apps/wpm/migrations/0012_mlog.py diff --git a/apps/mtm/serializers.py b/apps/mtm/serializers.py index b170c24d..4b959193 100644 --- a/apps/mtm/serializers.py +++ b/apps/mtm/serializers.py @@ -2,32 +2,43 @@ from apps.utils.serializers import CustomModelSerializer from apps.mtm.models import Shift, Material, Mgroup, Team, Goal, Process, Route from apps.utils.constants import EXCLUDE_FIELDS from rest_framework import serializers +from rest_framework.exceptions import ValidationError from apps.system.models import Dept + class ShiftSerializer(CustomModelSerializer): class Meta: model = Shift fields = '__all__' read_only_fields = EXCLUDE_FIELDS + class MaterialSerializer(CustomModelSerializer): class Meta: model = Material fields = '__all__' read_only_fields = EXCLUDE_FIELDS + class MgroupSerializer(CustomModelSerializer): - belong_dept = serializers.PrimaryKeyRelatedField(label="所属部门", queryset=Dept.objects.all(), required=True) - belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True) + belong_dept = serializers.PrimaryKeyRelatedField( + label="所属部门", queryset=Dept.objects.all(), required=True) + belong_dept_name = serializers.CharField( + source='belong_dept.name', read_only=True) + class Meta: model = Mgroup fields = '__all__' read_only_fields = EXCLUDE_FIELDS + class TeamSerializer(CustomModelSerializer): - belong_dept = serializers.PrimaryKeyRelatedField(label="所属部门", queryset=Dept.objects.all(), required=True) + belong_dept = serializers.PrimaryKeyRelatedField( + label="所属部门", queryset=Dept.objects.all(), required=True) leader_name = serializers.CharField(source='leader.name', read_only=True) - belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True) + belong_dept_name = serializers.CharField( + source='belong_dept.name', read_only=True) + class Meta: model = Team fields = '__all__' @@ -35,36 +46,48 @@ class TeamSerializer(CustomModelSerializer): class GoalSerializer(CustomModelSerializer): - goal_cate_name = serializers.CharField(source='goal_cate.name', read_only=True) + goal_cate_name = serializers.CharField( + source='goal_cate.name', read_only=True) mgroup_name = serializers.CharField(source='mgroup.name', read_only=True) + class Meta: model = Goal fields = '__all__' read_only_fields = EXCLUDE_FIELDS + class MgroupGoalYearSerializer(serializers.Serializer): mgroup = serializers.CharField(label='ID或名称') year = serializers.IntegerField(label='年') class ProcessSerializer(CustomModelSerializer): - belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True) + belong_dept_name = serializers.CharField( + source='belong_dept.name', read_only=True) + class Meta: model = Process fields = '__all__' read_only_fields = EXCLUDE_FIELDS + class RouteSerializer(CustomModelSerializer): material_ = MaterialSerializer(source='material', read_only=True) process_name = serializers.CharField(source='process.name', read_only=True) process_cate = serializers.CharField(source='process.cate', read_only=True) + class Meta: model = Route fields = '__all__' read_only_fields = EXCLUDE_FIELDS + def validate(self, attrs): + material = attrs['material'] + if material.type != Material.MA_TYPE_GOOD: + raise ValidationError('请选择最终产品') + return super().validate(attrs) + def update(self, instance, validated_data): validated_data.pop('material', None) validated_data.pop('process', None) return super().update(instance, validated_data) - diff --git a/apps/pm/migrations/0004_auto_20231008_1126.py b/apps/pm/migrations/0004_auto_20231008_1126.py new file mode 100644 index 00000000..7a289185 --- /dev/null +++ b/apps/pm/migrations/0004_auto_20231008_1126.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.12 on 2023-10-08 03:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0014_alter_process_options'), + ('pm', '0003_auto_20230927_0936'), + ] + + operations = [ + migrations.AddField( + model_name='mtask', + name='material_before', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='mtask_material_before', to='mtm.material', verbose_name='领用物'), + ), + migrations.AlterField( + model_name='mtask', + name='material', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mtask_material', to='mtm.material', verbose_name='产物'), + ), + migrations.AlterField( + model_name='mtask', + name='state', + field=models.PositiveIntegerField(choices=[(10, '创建中'), (20, '已下达')], default=10, help_text="((10, '创建中'), (20, '已下达'))", verbose_name='状态'), + ), + ] diff --git a/apps/pm/models.py b/apps/pm/models.py index 9c3c9c98..8c54667b 100644 --- a/apps/pm/models.py +++ b/apps/pm/models.py @@ -16,16 +16,18 @@ class Mtask(CommonADModel): MTASK_STATES = ( (MTASK_CREATED, '创建中'), (MTASK_ASSGINED, '已下达'), - (MTASK_WORKING, '生产中'), - (MTASK_DONE, '已完成') + # (MTASK_WORKING, '生产中'), + # (MTASK_DONE, '已结束') ) state = models.PositiveIntegerField( '状态', choices=MTASK_STATES, default=MTASK_CREATED, help_text=str(MTASK_STATES)) number = models.CharField('编号', max_length=50, unique=True) mgroup = models.ForeignKey( Mgroup, verbose_name='工段', on_delete=models.CASCADE) + material_before = models.ForeignKey( + Material, verbose_name='领用物', on_delete=models.CASCADE, related_name='mtask_material_before', null=True, blank=True) material = models.ForeignKey( - Material, verbose_name='产品', on_delete=models.CASCADE) + Material, verbose_name='产物', on_delete=models.CASCADE, related_name='mtask_material') count = models.PositiveIntegerField('任务数', default=1) count_real = models.PositiveIntegerField('实际生产数', default=0) count_ok = models.PositiveIntegerField('合格数', default=0) diff --git a/apps/pm/services.py b/apps/pm/services.py index f4754ec5..4de7fa73 100644 --- a/apps/pm/services.py +++ b/apps/pm/services.py @@ -51,6 +51,9 @@ class PmService: last_route = rqs.last() # 最后一步是产生产品 # 创建父任务 for ind, val in enumerate(rqs): + material_before = None + if ind > 0: + material_before = rqs[ind-1].material if val.is_autotask: # 找到工段 mgroups = val.mgroups @@ -82,6 +85,7 @@ class PmService: fmtask, _ = Mtask.objects.get_or_create(mgroup=mgroup, material=halfgood, defaults={ 'number': f'{product.number}_r{ind+1}_{start_date_str}', 'material': halfgood, + 'material_before': material_before, 'mgroup': mgroup, 'count': task_count, 'start_date': start_date, @@ -96,6 +100,7 @@ class PmService: Mtask.objects.get_or_create(mgroup=mgroup, material=halfgood, parent=fmtask, defaults={ 'number': f'{fmtask.number}_{i+1}', 'material': halfgood, + 'material_before': material_before, 'mgroup': mgroup, 'count': task_count_day, 'start_date': task_date, diff --git a/apps/wpm/migrations/0012_mlog.py b/apps/wpm/migrations/0012_mlog.py new file mode 100644 index 00000000..86a6dd80 --- /dev/null +++ b/apps/wpm/migrations/0012_mlog.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.12 on 2023-10-08 01:17 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('pm', '0003_auto_20230927_0936'), + ('wpm', '0011_wmaterial'), + ] + + operations = [ + migrations.CreateModel( + name='Mlog', + 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='批次号')), + ('count_use', models.PositiveIntegerField(default=0, verbose_name='领用数')), + ('count_real', models.PositiveIntegerField(default=0, verbose_name='实际生产数')), + ('count_ok', models.PositiveIntegerField(default=0, verbose_name='合格数')), + ('count_notok', models.PositiveIntegerField(default=0, verbose_name='不合格数')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mlog_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('mtask', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pm.mtask', verbose_name='关联生产')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mlog_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 9a06834c..397eae92 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -1,6 +1,7 @@ from django.db import models from apps.utils.models import CommonADModel, CommonBDModel from apps.mtm.models import Mgroup, Team, Shift, Material +from apps.pm.models import Mtask from django.utils.timezone import localtime # Create your models here. @@ -73,3 +74,16 @@ class WMaterial(CommonBDModel): Material, verbose_name='物料', on_delete=models.CASCADE) batch = models.CharField('批次号', max_length=50) count = models.PositiveIntegerField('当前数量', default=0) + + +class Mlog(CommonADModel): + """ + 生产日志 + """ + mtask = models.ForeignKey( + Mtask, verbose_name='关联生产', on_delete=models.CASCADE) + batch = models.CharField('批次号', max_length=50) + count_use = models.PositiveIntegerField('领用数', default=0) + count_real = models.PositiveIntegerField('实际生产数', default=0) + count_ok = models.PositiveIntegerField('合格数', default=0) + count_notok = models.PositiveIntegerField('不合格数', default=0) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 5ccaa8dd..4230aab7 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -2,7 +2,7 @@ from apps.utils.constants import EXCLUDE_FIELDS from apps.utils.serializers import CustomModelSerializer from rest_framework import serializers -from .models import SfLog, StLog, SfLogExp, WMaterial +from .models import SfLog, StLog, SfLogExp, WMaterial, Mlog from apps.system.models import Dictionary from apps.wpm.tasks import cal_enstat_when_pcoal_heat_change, cal_enstat_when_team_change from apps.mtm.serializers import MaterialSerializer @@ -73,3 +73,15 @@ class WMaterialSerializer(CustomModelSerializer): class Meta: model = WMaterial fields = '__all__' + + +class MlogSerializer(CustomModelSerializer): + create_by_name = serializers.CharField( + source='create_by.name', read_only=True) + update_by_name = serializers.CharField( + source='update_by.name', read_only=True) + + class Meta: + model = Mlog + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS diff --git a/apps/wpm/services.py b/apps/wpm/services.py index cc049717..1eabbb82 100644 --- a/apps/wpm/services.py +++ b/apps/wpm/services.py @@ -8,9 +8,9 @@ from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from rest_framework.exceptions import ParseError from apps.inm.models import MIO, MIOItem -from apps.mtm.models import Mgroup, Shift +from apps.mtm.models import Mgroup, Shift, Material -from .models import SfLog, SfLogExp, WMaterial +from .models import SfLog, SfLogExp, WMaterial, Mlog def make_sflogs(mgroup: Mgroup, start_date: datetime.date, end_date: datetime.date): @@ -101,3 +101,30 @@ def do_in(mio: MIO): wm.delete() else: raise ParseError('车间物料不足') + + +def mlog_create(mlog: Mlog): + """ + 生产日志创建后需要执行的操作 + """ + mtask = mlog.mtask + material = mtask.material + material_before = mtask.material_before + if material_before and material_before.is_hidden is False: # 需要进行车间库存管理 + # 需要判断领用数是否合理 + material_has_qs = WMaterial.objects.filter( + batch=mlog.batch, material=material_before) + count_x = material_has_qs.count() + if count_x == 1: + material_has = material_has_qs.first() + elif count_x == 0: + raise ParseError(f'{material_before.name}-批次库存不存在!') + else: + raise ParseError(f'{material_before.name}-存在多个相同批次!') + if mlog.count_use > material_has.count: + raise ParseError(f'{material_before.name}-该批次车间库存不足!') + else: + material_has.count = material_has.count - mlog.count_use + material_has.save() + # if material.is_hidden is False: # 需要入库 + # wmaterial = WMaterial.objects.get_or_create(batch=mlog.batch, material=material, defaults={}) diff --git a/apps/wpm/urls.py b/apps/wpm/urls.py index f82126da..f51a1459 100644 --- a/apps/wpm/urls.py +++ b/apps/wpm/urls.py @@ -1,7 +1,7 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.wpm.views import SfLogViewSet, StLogViewSet, SfLogExpViewSet, WMaterialViewSet +from apps.wpm.views import SfLogViewSet, StLogViewSet, SfLogExpViewSet, WMaterialViewSet, MlogViewSet API_BASE_URL = 'api/wpm/' @@ -12,6 +12,7 @@ router.register('sflog', SfLogViewSet, basename='sflog') router.register('stlog', StLogViewSet, basename='stlog') router.register('sflogexp', SfLogExpViewSet, basename='sflogexp') router.register('wmaterial', WMaterialViewSet, basename='wmaterial') +router.register('mlog', MlogViewSet, basename='mlog') urlpatterns = [ path(API_BASE_URL, include(router.urls)), ] diff --git a/apps/wpm/views.py b/apps/wpm/views.py index cecd2931..68d34279 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -7,10 +7,12 @@ from rest_framework.response import Response from apps.mtm.models import Material from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet +from apps.utils.mixins import BulkCreateModelMixin from .filters import SfLogExpFilter, SfLogFilter -from .models import SfLog, SfLogExp, StLog, WMaterial -from .serializers import SflogExpSerializer, SfLogSerializer, StLogSerializer, WMaterialSerializer +from .models import SfLog, SfLogExp, StLog, WMaterial, Mlog +from .serializers import SflogExpSerializer, SfLogSerializer, StLogSerializer, WMaterialSerializer, MlogSerializer +from .services import mlog_create # Create your views here. @@ -93,3 +95,21 @@ class WMaterialViewSet(ListModelMixin, CustomGenericViewSet): search_fields = ['material__name', 'material__number', 'material__specification'] filterset_fields = ['material', 'belong_dept'] + + +class MlogViewSet(ListModelMixin, BulkCreateModelMixin, DestroyModelMixin, CustomGenericViewSet): + """ + list: 生产日志 + + 生产日志 + """ + perms_map = {'get': '', 'post': 'mlog.create', 'delete': 'mlog.delete'} + queryset = Mlog.objects.all() + serializer_class = MlogSerializer + select_related_fields = ['create_by', 'update_by', 'mtask'] + filterset_fields = ['mtask'] + + def perform_create(self, serializer): + with transaction.atomic(): + ins = serializer.save() + mlog_create(ins)