diff --git a/apps/pm/filters.py b/apps/pm/filters.py index 0e63bf68..5e0c8dff 100644 --- a/apps/pm/filters.py +++ b/apps/pm/filters.py @@ -15,7 +15,6 @@ class MtaskFilter(filters.FilterSet): "process": ["exact"], "process__cate": ["exact"], "material": ["exact"], - "order": ["exact"], "parent": ["exact", "isnull"] } diff --git a/apps/pm/migrations/0002_remove_mtask_order.py b/apps/pm/migrations/0002_remove_mtask_order.py new file mode 100644 index 00000000..82ea0a90 --- /dev/null +++ b/apps/pm/migrations/0002_remove_mtask_order.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.12 on 2023-09-26 02:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='mtask', + name='order', + ), + ] diff --git a/apps/pm/models.py b/apps/pm/models.py index 960b6027..ce37bb83 100644 --- a/apps/pm/models.py +++ b/apps/pm/models.py @@ -1,11 +1,10 @@ from django.db import models -from apps.utils.models import CommonBModel -from apps.sam.models import Order +from apps.utils.models import CommonBModel, CommonBDModel from apps.mtm.models import Material, Process # Create your models here. -class Mtask(CommonBModel): +class Mtask(CommonBDModel): """ 生产任务 """ @@ -21,7 +20,6 @@ class Mtask(CommonBModel): ) state = models.PositiveIntegerField('状态', choices=MTASK_STATES, default=MTASK_CREATED, help_text=str(MTASK_STATES)) number = models.CharField('编号', max_length=50, unique=True) - order = models.ForeignKey(Order, verbose_name='所属订单', null=True, blank=True, on_delete=models.SET_NULL, related_name='mtask_order') process = models.ForeignKey(Process, verbose_name='工序', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE) count = models.PositiveIntegerField('任务数', default=1) diff --git a/apps/pm/serializers.py b/apps/pm/serializers.py index 7bad5eda..ffeec6b1 100644 --- a/apps/pm/serializers.py +++ b/apps/pm/serializers.py @@ -1,9 +1,24 @@ 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 class MtaskSerializer(CustomModelSerializer): material_ = MaterialSerializer(source='material', read_only=True) class Meta: model = Mtask - fields = '__all__' \ No newline at end of file + fields = '__all__' + + 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) + + +class SchedueSerializer(serializers.Serializer): + 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) diff --git a/apps/pm/services.py b/apps/pm/services.py index f91eb3b2..e7e9e754 100644 --- a/apps/pm/services.py +++ b/apps/pm/services.py @@ -1,15 +1,87 @@ from apps.sam.models import Order, OrderItem -from apps.mtm.models import Route +from apps.mtm.models import Route, Material from rest_framework.exceptions import ParseError +from apps.pm.models import Mtask +from django.db.models.query import QuerySet +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 + """ + rdict = {} + for item in orderitems: + if item.mtask: + raise ParseError('订单项已排任务!') + productId = item.material.id + if productId not in rdict: + rdict[productId] = [item.material, item.count, item.order.delivery_date, [item]] + else: + order_date = item.order.delivery_date + if rdict[productId][2] > order_date: + rdict[productId][2] = order_date + rdict[productId][1] = rdict[productId][1] + item.count + rdict[productId][3].append(item) + return rdict + + @classmethod - def make_mtasks_by_order(cls, order: Order): + def schedue_from_orderitems(cls, orderitemIds: List[str], start_date: date, end_date: date=None): """ - 从订单自动生成生产任务 + 从多个订单明细自动排产 """ - for orderitem in OrderItem.objects.filter(order=order): - routes = Route.objects.filter(material=orderitem.material, is_autotask=True) - if routes.exists(): - pass \ No newline at end of file + orderitems = OrderItem.objects.filter(pk__in=orderitemIds) + rdict = cls.check_orderitems(orderitems) + start_date_str = start_date.strftime('%Y%m%d') + for k, v in enumerate(rdict): + product, count, end_date_cal, orderitems = v + if end_date is None: + end_date = end_date_cal + if start_date >= end_date: + raise ParseError('开始时间不可大于结束时间') + # 计算相差天数 + rela_days = (end_date - start_date).days + 1 + # 获取工艺路线 + rqs = Route.objects.filter(material=product).order_by('sort', 'create_time') + last_route = rqs.last() # 最后一步是产生产品 + # 创建父任务 + for ind, val in enumerate(rqs): + if val.is_autotask: + # 找到存在的半成品 + 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}) + if val == last_route: + halfgood = product + task_count = count + if val.out_rate: + if val.out_rate > 1: + 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={ + 'number': f'{product.number}_r{ind+1}_{start_date_str}', + 'material': halfgood, + 'process': val.process, + 'count': task_count, + 'start_date': start_date, + 'end_date': end_date + }) + 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={ + 'number': f'{fmtask.number}_{i+1}', + 'material': halfgood, + 'process': val.process, + 'count': task_count_day, + 'start_date': task_date, + 'end_date': task_date, + 'parent': fmtask + }) \ No newline at end of file diff --git a/apps/pm/views.py b/apps/pm/views.py index 3498c930..a2e4a334 100644 --- a/apps/pm/views.py +++ b/apps/pm/views.py @@ -1,8 +1,13 @@ from django.shortcuts import render from apps.utils.viewsets import CustomModelViewSet from apps.pm.models import Mtask -from apps.pm.serializers import MtaskSerializer +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.exceptions import ParseError # Create your views here. class MtaskViewSet(CustomModelViewSet): @@ -18,3 +23,21 @@ class MtaskViewSet(CustomModelViewSet): ordering_fields = ['start_date', 'process__sort'] ordering = ['process__sort', '-start_date', '-create_time'] + @action(methods=['post'], detail=True, perms_map={'post': 'mtasks.schedue'}, serializer_class=SchedueSerializer) + @transaction.atomic + def schedue_from_orderitems(self, request, *args, **kwargs): + """从多个订单明细自动排产 + + 从多个订单明细自动排产 + """ + 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)) + return Response() + + def perform_destroy(self, instance): + if instance.state != Mtask.MTASK_CREATED: + raise ParseError('该任务非创建中不可删除') + return super().perform_destroy(instance) +