feat: 从多个订单明细自动排产

This commit is contained in:
caoqianming 2023-09-26 11:00:31 +08:00
parent aa79df55b5
commit 10aab0226d
6 changed files with 138 additions and 14 deletions

View File

@ -15,7 +15,6 @@ class MtaskFilter(filters.FilterSet):
"process": ["exact"], "process": ["exact"],
"process__cate": ["exact"], "process__cate": ["exact"],
"material": ["exact"], "material": ["exact"],
"order": ["exact"],
"parent": ["exact", "isnull"] "parent": ["exact", "isnull"]
} }

View File

@ -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',
),
]

View File

@ -1,11 +1,10 @@
from django.db import models from django.db import models
from apps.utils.models import CommonBModel from apps.utils.models import CommonBModel, CommonBDModel
from apps.sam.models import Order
from apps.mtm.models import Material, Process from apps.mtm.models import Material, Process
# Create your models here. # 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)) state = models.PositiveIntegerField('状态', choices=MTASK_STATES, default=MTASK_CREATED, help_text=str(MTASK_STATES))
number = models.CharField('编号', max_length=50, unique=True) 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) process = models.ForeignKey(Process, verbose_name='工序', on_delete=models.CASCADE)
material = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE)
count = models.PositiveIntegerField('任务数', default=1) count = models.PositiveIntegerField('任务数', default=1)

View File

@ -1,9 +1,24 @@
from apps.utils.serializers import CustomModelSerializer from apps.utils.serializers import CustomModelSerializer
from apps.pm.models import Mtask from apps.pm.models import Mtask
from apps.mtm.serializers import MaterialSerializer 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): class MtaskSerializer(CustomModelSerializer):
material_ = MaterialSerializer(source='material', read_only=True) material_ = MaterialSerializer(source='material', read_only=True)
class Meta: class Meta:
model = Mtask model = Mtask
fields = '__all__' 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)

View File

@ -1,15 +1,87 @@
from apps.sam.models import Order, OrderItem 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 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: 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 @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): orderitems = OrderItem.objects.filter(pk__in=orderitemIds)
routes = Route.objects.filter(material=orderitem.material, is_autotask=True) rdict = cls.check_orderitems(orderitems)
if routes.exists(): start_date_str = start_date.strftime('%Y%m%d')
pass 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
})

View File

@ -1,8 +1,13 @@
from django.shortcuts import render from django.shortcuts import render
from apps.utils.viewsets import CustomModelViewSet from apps.utils.viewsets import CustomModelViewSet
from apps.pm.models import Mtask 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 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. # Create your views here.
class MtaskViewSet(CustomModelViewSet): class MtaskViewSet(CustomModelViewSet):
@ -18,3 +23,21 @@ class MtaskViewSet(CustomModelViewSet):
ordering_fields = ['start_date', 'process__sort'] ordering_fields = ['start_date', 'process__sort']
ordering = ['process__sort', '-start_date', '-create_time'] 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)