feat: 从多个订单明细自动排产
This commit is contained in:
parent
aa79df55b5
commit
10aab0226d
|
@ -15,7 +15,6 @@ class MtaskFilter(filters.FilterSet):
|
|||
"process": ["exact"],
|
||||
"process__cate": ["exact"],
|
||||
"material": ["exact"],
|
||||
"order": ["exact"],
|
||||
"parent": ["exact", "isnull"]
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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__'
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
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
|
||||
})
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue