From 0f6a7289b86ccd87d44bf5250feb224f2515c3a1 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 27 Sep 2023 09:38:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20mtask=20=E5=B7=A5=E5=BA=8F=E6=9B=B4?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=85=B3=E8=81=94mgroup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/pm/filters.py | 20 ++++--- apps/pm/migrations/0003_auto_20230927_0936.py | 29 ++++++++++ apps/pm/models.py | 19 ++++--- apps/pm/serializers.py | 27 +++++---- apps/pm/services.py | 55 +++++++++++++------ apps/pm/views.py | 45 ++++++++++----- 6 files changed, 138 insertions(+), 57 deletions(-) create mode 100644 apps/pm/migrations/0003_auto_20230927_0936.py diff --git a/apps/pm/filters.py b/apps/pm/filters.py index 5e0c8dff..8bb70c76 100644 --- a/apps/pm/filters.py +++ b/apps/pm/filters.py @@ -4,16 +4,20 @@ from django.utils import timezone from datetime import datetime, timedelta from django.db.models import F + class MtaskFilter(filters.FilterSet): tag = filters.CharFilter(method='filter_tag') + class Meta: model = Mtask fields = { "state": ["exact", "in"], "start_date": ["exact", "gte", "lte"], "end_date": ["exact", "gte", "lte"], - "process": ["exact"], - "process__cate": ["exact"], + "mgroup": ["exact"], + "mgroup__cate": ["exact"], + "mgroup__process": ["exact"], + "mgroup__process__cate": ["exact"], "material": ["exact"], "parent": ["exact", "isnull"] } @@ -22,10 +26,10 @@ class MtaskFilter(filters.FilterSet): now = timezone.now() day7_after = now + timedelta(days=7) if value == 'near_done': - queryset = queryset.filter(count_ok__lt=F('count'), - end_date__lte = day7_after.date(), - end_date__gte = now.date()) + queryset = queryset.filter(count_ok__lt=F('count'), + end_date__lte=day7_after.date(), + end_date__gte=now.date()) elif value == 'out_done': - queryset = queryset.filter(count_ok__lt=F('count'), - end_date__lt = now.date()) - return queryset \ No newline at end of file + queryset = queryset.filter(count_ok__lt=F('count'), + end_date__lt=now.date()) + return queryset diff --git a/apps/pm/migrations/0003_auto_20230927_0936.py b/apps/pm/migrations/0003_auto_20230927_0936.py new file mode 100644 index 00000000..539304f1 --- /dev/null +++ b/apps/pm/migrations/0003_auto_20230927_0936.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.12 on 2023-09-27 01:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0013_mgroup_process'), + ('pm', '0002_remove_mtask_order'), + ] + + operations = [ + migrations.RemoveField( + model_name='mtask', + name='belong_dept', + ), + migrations.RemoveField( + model_name='mtask', + name='process', + ), + migrations.AddField( + model_name='mtask', + name='mgroup', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.mgroup', verbose_name='工段'), + preserve_default=False, + ), + ] diff --git a/apps/pm/models.py b/apps/pm/models.py index ce37bb83..9c3c9c98 100644 --- a/apps/pm/models.py +++ b/apps/pm/models.py @@ -1,10 +1,11 @@ from django.db import models -from apps.utils.models import CommonBModel, CommonBDModel -from apps.mtm.models import Material, Process +from apps.utils.models import CommonADModel +from apps.mtm.models import Material, Mgroup # Create your models here. -class Mtask(CommonBDModel): + +class Mtask(CommonADModel): """ 生产任务 """ @@ -18,14 +19,18 @@ class Mtask(CommonBDModel): (MTASK_WORKING, '生产中'), (MTASK_DONE, '已完成') ) - state = models.PositiveIntegerField('状态', choices=MTASK_STATES, default=MTASK_CREATED, help_text=str(MTASK_STATES)) + state = models.PositiveIntegerField( + '状态', choices=MTASK_STATES, default=MTASK_CREATED, help_text=str(MTASK_STATES)) number = models.CharField('编号', max_length=50, unique=True) - process = models.ForeignKey(Process, verbose_name='工序', on_delete=models.CASCADE) - material = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE) + mgroup = models.ForeignKey( + Mgroup, verbose_name='工段', on_delete=models.CASCADE) + material = models.ForeignKey( + Material, verbose_name='产品', on_delete=models.CASCADE) count = models.PositiveIntegerField('任务数', default=1) count_real = models.PositiveIntegerField('实际生产数', default=0) count_ok = models.PositiveIntegerField('合格数', default=0) count_notok = models.PositiveIntegerField('不合格数', default=0) start_date = models.DateField('计划开工日期') end_date = models.DateField('计划完工日期') - parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='父任务') \ No newline at end of file + parent = models.ForeignKey( + 'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='父任务') diff --git a/apps/pm/serializers.py b/apps/pm/serializers.py index ffeec6b1..21be6936 100644 --- a/apps/pm/serializers.py +++ b/apps/pm/serializers.py @@ -1,12 +1,16 @@ -from apps.utils.serializers import CustomModelSerializer -from apps.pm.models import Mtask -from apps.mtm.serializers import MaterialSerializer -from apps.sam.models import OrderItem -from rest_framework.exceptions import ValidationError from rest_framework import serializers +from rest_framework.exceptions import ValidationError + +from apps.mtm.serializers import MaterialSerializer +from apps.pm.models import Mtask +from apps.sam.models import OrderItem +from apps.utils.serializers import CustomModelSerializer + class MtaskSerializer(CustomModelSerializer): material_ = MaterialSerializer(source='material', read_only=True) + mgroup__name = serializers.CharField(source='mgroup.name', read_only=True) + class Meta: model = Mtask fields = '__all__' @@ -14,11 +18,14 @@ class MtaskSerializer(CustomModelSerializer): def update(self, instance, validated_data): if instance.state != Mtask.MTASK_CREATED: raise ValidationError('任务非创建中不可编辑') - new_data = {key: validated_data[key] for key in ['number', 'count', 'start_date', 'end_date']} - return super().update(instance, validated_data) - + new_data = {key: validated_data[key] for key in [ + 'number', 'count', 'start_date', 'end_date']} + return super().update(instance, new_data) + class SchedueSerializer(serializers.Serializer): - orderitems = serializers.PrimaryKeyRelatedField(label='orderitem的ID列表', queryset=OrderItem.objects.all(), many=True) + orderitems = serializers.PrimaryKeyRelatedField( + label='orderitem的ID列表', queryset=OrderItem.objects.all(), many=True) start_date = serializers.DateField(label='计划开工日期') - end_date = serializers.DateField(label='计划完工日期', allow_null=True, required=False) + end_date = serializers.DateField( + label='计划完工日期', allow_null=True, required=False) diff --git a/apps/pm/services.py b/apps/pm/services.py index e7e9e754..53b0618b 100644 --- a/apps/pm/services.py +++ b/apps/pm/services.py @@ -1,5 +1,5 @@ -from apps.sam.models import Order, OrderItem -from apps.mtm.models import Route, Material +from apps.sam.models import OrderItem +from apps.mtm.models import Route, Material, Mgroup from rest_framework.exceptions import ParseError from apps.pm.models import Mtask from django.db.models.query import QuerySet @@ -7,8 +7,9 @@ from datetime import date, timedelta import math from typing import List + class PmService: - + def check_orderitems(cls, orderitems: QuerySet[OrderItem]): """ 校验orderitems并返回整合后的字典以productId为key, [product, count, end_date, orderitems] 为value @@ -19,7 +20,8 @@ class PmService: raise ParseError('订单项已排任务!') productId = item.material.id if productId not in rdict: - rdict[productId] = [item.material, item.count, item.order.delivery_date, [item]] + rdict[productId] = [item.material, item.count, + item.order.delivery_date, [item]] else: order_date = item.order.delivery_date if rdict[productId][2] > order_date: @@ -27,10 +29,9 @@ class PmService: rdict[productId][1] = rdict[productId][1] + item.count rdict[productId][3].append(item) return rdict - @classmethod - def schedue_from_orderitems(cls, orderitemIds: List[str], start_date: date, end_date: date=None): + def schedue_from_orderitems(cls, user, orderitemIds: List[str], start_date: date, end_date: date = None): """ 从多个订单明细自动排产 """ @@ -46,16 +47,30 @@ class PmService: # 计算相差天数 rela_days = (end_date - start_date).days + 1 # 获取工艺路线 - rqs = Route.objects.filter(material=product).order_by('sort', 'create_time') + rqs = Route.get_routes(product) last_route = rqs.last() # 最后一步是产生产品 # 创建父任务 for ind, val in enumerate(rqs): if val.is_autotask: + # 找到工段 + mgroups = Mgroup.objects.filter(process=val.process) + mgroups_count = mgroups.count() + if mgroups_count == 1: + mgroup = mgroups.first() + elif mgroups_count == 0: + raise ParseError(f'{val.name}-工段不存在!') + else: + raise ParseError(f'{val.name}-工段存在多个!') # 找到存在的半成品 - halfgood, _ = Material.objects.get_or_create(type=Material.MA_TYPE_HALFGOOD, parent=product, process=val.process, - defaults={'parent': product, 'process': val.process, - 'is_hidden': True, 'name': product.name, - 'number': product.number, 'specification': product.specification, 'type': Material.MA_TYPE_HALFGOOD}) + halfgood, _ = Material.objects.get_or_create(type=Material.MA_TYPE_HALFGOOD, parent=product, mgroup=mgroup, + defaults={'parent': product, 'mgroup': mgroup, + 'is_hidden': True, 'name': product.name, + 'number': product.number, + 'specification': product.specification, + 'type': Material.MA_TYPE_HALFGOOD, + 'create_by': user, + 'update_by': user, + }) if val == last_route: halfgood = product task_count = count @@ -64,24 +79,28 @@ class PmService: task_count = math.ceil(count / (val.out_rate/100)) else: task_count = math.ceil(count / val.out_rate) - fmtask, _ = Mtask.objects.get_or_create(process=val.process, material=halfgood, defaults={ + fmtask, _ = Mtask.objects.get_or_create(mgroup=mgroup, material=halfgood, defaults={ 'number': f'{product.number}_r{ind+1}_{start_date_str}', 'material': halfgood, - 'process': val.process, + 'mgroup': mgroup, 'count': task_count, 'start_date': start_date, - 'end_date': end_date + 'end_date': end_date, + 'create_by': user, + 'update_by': user, }) task_count_day = math.ceil(task_count/rela_days) if fmtask.parent is None: for i in range(rela_days): task_date = start_date + timedelta(days=i+1) - Mtask.objects.get_or_create(process=val.process, material=halfgood, parent = fmtask, defaults={ + Mtask.objects.get_or_create(mgroup=mgroup, material=halfgood, parent=fmtask, defaults={ 'number': f'{fmtask.number}_{i+1}', 'material': halfgood, - 'process': val.process, + 'mgroup': mgroup, 'count': task_count_day, 'start_date': task_date, 'end_date': task_date, - 'parent': fmtask - }) \ No newline at end of file + 'parent': fmtask, + 'create_by': user, + 'update_by': user + }) diff --git a/apps/pm/views.py b/apps/pm/views.py index ddafc997..0d9f63cc 100644 --- a/apps/pm/views.py +++ b/apps/pm/views.py @@ -1,15 +1,19 @@ -from django.shortcuts import render -from apps.utils.viewsets import CustomModelViewSet -from apps.pm.models import Mtask -from apps.pm.serializers import MtaskSerializer, SchedueSerializer -from apps.pm.filters import MtaskFilter -from rest_framework.decorators import action from django.db import transaction -from apps.pm.services import PmService -from rest_framework.response import Response +from rest_framework.decorators import action from rest_framework.exceptions import ParseError +from rest_framework.response import Response + +from apps.utils.serializers import PkSerializer +from apps.utils.viewsets import CustomModelViewSet + +from .filters import MtaskFilter +from .models import Mtask +from .serializers import MtaskSerializer, SchedueSerializer +from .services import PmService + # Create your views here. + class MtaskViewSet(CustomModelViewSet): """ list: 生产任务 @@ -19,11 +23,11 @@ class MtaskViewSet(CustomModelViewSet): queryset = Mtask.objects.all() serializer_class = MtaskSerializer filterset_class = MtaskFilter - select_related_fields = ['material', 'process', 'order'] - ordering_fields = ['start_date', 'process__sort'] - ordering = ['process__sort', '-start_date', '-create_time'] + select_related_fields = ['material', 'mgroup'] + ordering_fields = ['start_date', 'mgroup__process__sort'] + ordering = ['mgroup__process__sort', '-start_date', '-create_time'] - @action(methods=['post'], detail=False, perms_map={'post': 'mtasks.schedue'}, serializer_class=SchedueSerializer) + @action(methods=['post'], detail=False, perms_map={'post': 'mtask.schedue'}, serializer_class=SchedueSerializer) @transaction.atomic def schedue_from_orderitems(self, request, *args, **kwargs): """从多个订单明细自动排产 @@ -33,11 +37,24 @@ class MtaskViewSet(CustomModelViewSet): sr = SchedueSerializer(data=request.data) sr.is_valid(raise_exception=True) vdata = sr.validated_data - PmService.schedue_from_orderitems(vdata['orderitems'], vdata['start_date'], vdata.get('end_date', None)) + PmService.schedue_from_orderitems(request.user, + vdata['orderitems'], vdata['start_date'], vdata.get('end_date', None)) return Response() - + def perform_destroy(self, instance): if instance.state != Mtask.MTASK_CREATED: raise ParseError('该任务非创建中不可删除') return super().perform_destroy(instance) + @action(methods=['post'], detail=False, perms_map={'post': 'mtask.asgin'}, serializer_class=PkSerializer) + @transaction.atomic + def assgin(self, request): + """下达任务 + + 下达任务 + """ + ids = request.data.get('ids', []) + mtasks = Mtask.objects.filter( + id__in=ids, parent=None, state=Mtask.MTASK_CREATED) + mtasks.update(state=Mtask.MTASK_ASSGINED) + return Response()