feat: 增加utask

This commit is contained in:
caoqianming 2023-10-20 09:08:14 +08:00
parent 53d2276998
commit 3d407795a9
13 changed files with 348 additions and 76 deletions

View File

@ -22,7 +22,6 @@ class MtaskFilter(filters.FilterSet):
"material_out__type": ["exact"],
"material_out__is_hidden": ["exact"],
"mgroup__belong_dept__name": ["exact"],
"parent": ["exact", "isnull"]
}
def filter_tag(self, queryset, name, value):

View File

@ -0,0 +1,55 @@
# Generated by Django 3.2.12 on 2023-10-19 10:12
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 = [
('mtm', '0017_auto_20231018_1033'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('pm', '0006_auto_20231016_1648'),
]
operations = [
migrations.RemoveField(
model_name='mtask',
name='parent',
),
migrations.AlterField(
model_name='mtask',
name='state',
field=models.PositiveIntegerField(choices=[(10, '创建中'), (20, '已下达'), (30, '生产中'), (40, '已提交')], default=10, help_text="((10, '创建中'), (20, '已下达'), (30, '生产中'), (40, '已提交'))", verbose_name='状态'),
),
migrations.CreateModel(
name='Utask',
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='删除标记')),
('state', models.PositiveIntegerField(choices=[(10, '创建中'), (20, '已下达'), (30, '生产中'), (40, '已提交')], default=10, help_text="((10, '创建中'), (20, '已下达'), (30, '生产中'), (40, '已提交'))", verbose_name='状态')),
('number', models.CharField(max_length=50, unique=True, verbose_name='编号')),
('count', models.PositiveIntegerField(default=1, 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='不合格数')),
('start_date', models.DateField(verbose_name='计划开工日期')),
('end_date', models.DateField(verbose_name='计划完工日期')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='utask_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='产品')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='utask_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='mtask',
name='utask',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pm.utask', verbose_name='关联大任务'),
),
]

View File

@ -5,6 +5,33 @@ from apps.mtm.models import Material, Mgroup
# Create your models here.
class Utask(CommonADModel):
"""
生产大任务
"""
UTASK_CREATED = 10
UTASK_ASSGINED = 20
UTASK_WORKING = 30
UTASK_DONE = 40
UTASK_STATES = (
(UTASK_CREATED, '创建中'),
(UTASK_ASSGINED, '已下达'),
(UTASK_WORKING, '生产中'),
(UTASK_DONE, '已提交')
)
state = models.PositiveIntegerField(
'状态', choices=UTASK_STATES, default=UTASK_CREATED, help_text=str(UTASK_STATES))
number = models.CharField('编号', max_length=50, unique=True)
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('计划完工日期')
class Mtask(CommonADModel):
"""
生产任务
@ -16,7 +43,7 @@ class Mtask(CommonADModel):
MTASK_STATES = (
(MTASK_CREATED, '创建中'),
(MTASK_ASSGINED, '已下达'),
# (MTASK_WORKING, '生产中'),
(MTASK_WORKING, '生产中'),
(MTASK_DONE, '已提交')
)
state = models.PositiveIntegerField(
@ -34,5 +61,5 @@ class Mtask(CommonADModel):
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='父任务')
utask = models.ForeignKey(
Utask, verbose_name='关联大任务', on_delete=models.CASCADE, null=True, blank=True)

View File

@ -2,17 +2,16 @@ 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.pm.models import Mtask, Utask
from apps.sam.models import OrderItem
from apps.utils.serializers import CustomModelSerializer
class MtaskSerializer(CustomModelSerializer):
class UtaskSerializer(CustomModelSerializer):
material_ = MaterialSerializer(source='material', read_only=True)
mgroup_name = serializers.CharField(source='mgroup.name', read_only=True)
class Meta:
model = Mtask
model = Utask
fields = '__all__'
def update(self, instance, validated_data):
@ -23,9 +22,28 @@ class MtaskSerializer(CustomModelSerializer):
return super().update(instance, new_data)
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__'
read_only_fields = ['utask']
def update(self, instance, validated_data):
if instance.state != Mtask.MTASK_CREATED:
raise ValidationError('任务非创建中不可编辑')
if instance.utask is not None:
new_data = {'count': validated_data['count']}
else:
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)
start_date = serializers.DateField(label='计划开工日期')
end_date = serializers.DateField(
label='计划完工日期', allow_null=True, required=False)
end_date = serializers.DateField(label='计划完工日期')

View File

@ -1,7 +1,7 @@
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 apps.pm.models import Mtask, Utask
from django.db.models.query import QuerySet
from datetime import date, timedelta
import math
@ -10,9 +10,103 @@ from typing import List
class PmService:
@classmethod
def make_utasks_from_orderitems(cls, user, orderitemIds: List[str], start_date: date, end_date: date):
start_date_str = start_date.strftime('%Y%m%d')
if start_date >= end_date:
raise ParseError('开始时间不可大于结束时间')
orderitems = OrderItem.objects.filter(pk__in=orderitemIds)
if orderitems.exclude(utask=None).exists():
raise ParseError('存在订单项已排任务!')
rdict = {}
for item in orderitems:
productId = item.material.id
if productId not in rdict:
rdict[productId] = [item.material, item.count, [item.id]]
else:
rdict[productId][1] = rdict[productId][1] + item.count
rdict[productId][2].append(item.id)
# 生成大任务
make_list = []
for k, v in rdict.items():
xproduct, xcount, orderitemsId = v
if xproduct.is_assemb:
for key in xproduct.components:
make_list.append([Material.objects.get(
id=key), xcount*xproduct.components[key], orderitemsId])
else:
make_list = [[xproduct, xcount, orderitemsId]]
for i in make_list:
material, count, orderitemsId = i
utask = Utask.objects.create(
number=f'{material.number}_{start_date_str}',
material=material,
count=count,
start_date=start_date,
end_date=end_date,
create_by=user,
update_by=user
)
OrderItem.objects.filter(id__in=orderitemIds).update(utask=utask)
@classmethod
def schedue_mtasks(cls, user, utask: Utask):
"""
从大任务自动排产出小任务
"""
number, product, count, start_date, end_date = utask.number, utask.material, utask.count, utask.start_date, utask.end_date
# 计算相差天数
rela_days = (end_date - start_date).days + 1
# 获取每个产品的加工路线
rqs = Route.get_routes(product)
# 创建小任务
for ind, val in enumerate(rqs):
if val.material_out:
halfgood = val.material_out
else:
raise ParseError(f'{ind+1}步-无输出物料')
if val.material_in:
material_in = val.material_in
elif ind > 0:
material_in = rqs[ind-1].material_out
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'{ind+1}步-工段不存在!')
else: # 后面可能会指定车间
raise ParseError(f'{ind+1}步-工段存在多个!')
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)
task_count_day = math.ceil(task_count/rela_days)
if rela_days > 1:
for i in range(rela_days):
task_date = start_date + timedelta(days=i)
Mtask.objects.create(**{
'number': f'{number}_r{ind+1}_{i+1}',
'material_out': halfgood,
'material_in': material_in,
'mgroup': mgroup,
'count': task_count_day,
'start_date': task_date,
'end_date': task_date,
'utask': utask,
'create_by': user,
'update_by': user
})
@classmethod
def check_orderitems(cls, orderitems: QuerySet[OrderItem]):
"""
废弃
校验orderitems并返回整合后的字典以productId为key, [product, count, end_date, orderitems] 为value
"""
rdict = {}
@ -34,6 +128,7 @@ class PmService:
@classmethod
def schedue_from_orderitems(cls, user, orderitemIds: List[str], start_date: date, end_date: date = None):
"""
废弃
从多个订单明细自动排产
"""
orderitems = OrderItem.objects.filter(pk__in=orderitemIds)

View File

@ -1,12 +1,13 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.pm.views import (MtaskViewSet)
from apps.pm.views import (MtaskViewSet, UtaskViewSet)
API_BASE_URL = 'api/pm/'
HTML_BASE_URL = 'pm/'
router = DefaultRouter()
router.register('mtask', MtaskViewSet, basename='mtask')
router.register('utask', UtaskViewSet, basename='utask')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -7,13 +7,77 @@ 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 .models import Mtask, Utask
from .serializers import MtaskSerializer, SchedueSerializer, UtaskSerializer
from .services import PmService
# Create your views here.
class UtaskViewSet(CustomModelViewSet):
"""
list: 生产大任务
生产大任务
"""
queryset = Utask.objects.all()
serializer_class = UtaskSerializer
filterset_fields = ['material', 'state']
select_related_fields = ['material']
ordering = ['-start_date']
@action(methods=['post'], detail=False, perms_map={'post': 'utask.schedue'}, serializer_class=SchedueSerializer)
@transaction.atomic
def schedue_utasks(self, request, *args, **kwargs):
"""从多个订单明细生成大任务
从多个订单明细生成大任务
"""
sr = SchedueSerializer(data=request.data)
sr.is_valid(raise_exception=True)
vdata = sr.validated_data
PmService.make_utasks_from_orderitems(request.user,
request.data['orderitems'], vdata['start_date'], vdata['end_date'])
return Response()
@action(methods=['post'], detail=False, perms_map={'post': 'utask.schedue'}, serializer_class=PkSerializer)
@transaction.atomic
def schedue_mtasks(self, request, *args, **kwargs):
"""大任务自动排产为小任务
大任务自动排产为小任务
"""
sr = PkSerializer(data=request.data)
sr.is_valid(raise_exception=True)
vdata = sr.validated_data
utasks = Utask.objects.filter(
id__in=vdata['ids'], state=Utask.UTASK_CREATED)
for i in utasks:
PmService.schedue_mtasks(request.user, i)
return Response()
@action(methods=['post'], detail=False, perms_map={'post': 'utask.assgin'}, serializer_class=PkSerializer)
@transaction.atomic
def assgin(self, request):
"""下达任务
下达任务
"""
ids = request.data.get('ids', [])
utasks = Utask.objects.filter(id__in=ids)
Mtask.objects.filter(utask__in=utasks, state=Mtask.MTASK_CREATED).update(
state=Mtask.MTASK_ASSGINED)
utasks.filter(state=Utask.UTASK_CREATED).update(
state=Utask.UTASK_ASSGINED)
# 此处要更新订单状态
from apps.sam.models import OrderItem, Order
orderIds = OrderItem.objects.filter(
utask__in=utasks).values_list('order', flat=True).distinct()
Order.objects.filter(id__in=orderIds, state=Order.ORDER_SUBMITED).update(
state=Order.ORDER_DOING)
return Response()
class MtaskViewSet(CustomModelViewSet):
"""
list: 生产任务
@ -27,39 +91,25 @@ class MtaskViewSet(CustomModelViewSet):
ordering_fields = ['start_date', 'mgroup__process__sort']
ordering = ['-start_date', 'mgroup__process__sort', '-create_time']
@action(methods=['post'], detail=False, perms_map={'post': 'mtask.schedue'}, serializer_class=SchedueSerializer)
@transaction.atomic
def schedue_from_orderitems(self, request, *args, **kwargs):
"""从多个订单明细自动排产
# @action(methods=['post'], detail=False, perms_map={'post': 'mtask.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(request.user,
request.data['orderitems'], vdata['start_date'], vdata.get('end_date', None))
return Response()
# 从多个订单明细自动排产
# """
# sr = SchedueSerializer(data=request.data)
# sr.is_valid(raise_exception=True)
# vdata = sr.validated_data
# PmService.schedue_from_orderitems(request.user,
# request.data['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.assgin'}, 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 = mtasks | Mtask.objects.filter(parent__in=mtasks)
mtasks.update(state=Mtask.MTASK_ASSGINED)
return Response()
@action(methods=['post'], detail=False, perms_map={'post': 'mtask.submit'}, serializer_class=PkSerializer)
@transaction.atomic
def submit(self, request):

View File

@ -9,5 +9,5 @@ class OrderItemFilter(filters.FilterSet):
fields = {
"order": ["exact", "in"],
"order__state": ["exact", "in"],
"mtask": ["exact", "isnull"]
"utask": ["exact", "isnull"]
}

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.12 on 2023-10-19 10:12
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('pm', '0007_auto_20231019_1812'),
('sam', '0004_alter_order_state'),
]
operations = [
migrations.RemoveField(
model_name='orderitem',
name='mtask',
),
migrations.AddField(
model_name='orderitem',
name='utask',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pm.utask', verbose_name='关联生产大任务'),
),
]

View File

@ -50,15 +50,13 @@ class Order(CommonBModel):
"""
ORDER_CREATE = 10
ORDER_SUBMITED = 20
ORDER_PLANING = 30
ORDER_PLANED = 40
ORDER_DELIVERED = 50
ORDER_DOING = 30
ORDER_DELIVERED = 40
ORDER_STATES = (
(10, '创建中'),
(20, '已提交'),
(30, '排产中'),
(40, '排产完成'),
(50, '已交付')
(30, '进行中'),
(40, '已交付')
)
state = models.PositiveSmallIntegerField(
'订单状态', default=ORDER_CREATE, choices=ORDER_STATES, help_text=str(ORDER_STATES))
@ -87,5 +85,5 @@ class OrderItem(BaseModel):
Material, verbose_name='所需产品', on_delete=models.CASCADE)
count = models.PositiveIntegerField('所需数量', default=1)
delivered_count = models.PositiveIntegerField('已交货数量', default=0)
mtask = models.ForeignKey('pm.mtask', verbose_name='关联生产任务',
utask = models.ForeignKey('pm.utask', verbose_name='关联生产任务',
on_delete=models.SET_NULL, null=True, blank=True)

View File

@ -3,6 +3,7 @@ from rest_framework.exceptions import ValidationError
from django.db.models import F
from apps.inm.models import MIO, MIOItem
class SamService:
def mio_saleout(mio: MIO):
@ -18,7 +19,8 @@ class SamService:
orderitem.delivered_count = delivered_count
orderitem.save()
# 更新order的状态
qs = OrderItem.objects.filter(order=order, count__lte=F('delivered_count'))
qs = OrderItem.objects.filter(
order=order, count__lte=F('delivered_count'))
if qs.exists():
pass
else:

View File

@ -5,18 +5,18 @@ from celery import shared_task
from .models import Order, OrderItem
@shared_task(base=CustomTask)
def change_order_state_when_schedue(orderitemIds):
"""排产后更新orderstate
"""
orderIds = list(OrderItem.objects.filter(
id__in=orderitemIds).values_list('order__id', flat=True).distinct())
for i in orderIds:
order = Order.objects.get(id=i)
state = Order.ORDER_PLANED
orderitems = OrderItem.objects.filter(order__id=i)
for item in orderitems:
if item.mtask is None:
state = Order.ORDER_PLANING
order.state = state
order.save()
# @shared_task(base=CustomTask)
# def change_order_state_when_schedue(orderitemIds):
# """排产后更新orderstate
# """
# orderIds = list(OrderItem.objects.filter(
# id__in=orderitemIds).values_list('order__id', flat=True).distinct())
# for i in orderIds:
# order = Order.objects.get(id=i)
# state = Order.ORDER_PLANED
# orderitems = OrderItem.objects.filter(order__id=i)
# for item in orderitems:
# if item.mtask is None:
# state = Order.ORDER_PLANING
# order.state = state
# order.save()

View File

@ -137,20 +137,23 @@ def mlog_confirm(mlog: Mlog):
def update_mtask(mtask: Mtask):
from apps.pm.models import Utask
res = Mlog.objects.filter(mtask=mtask).aggregate(sum_count_real=Sum(
'count_real'), sum_count_ok=Sum('count_ok'), sum_count_notok=Sum('count_notok'))
mtask.count_real = res['sum_count_real'] if res['sum_count_real'] else 0
mtask.count_ok = res['sum_count_ok'] if res['sum_count_ok'] else 0
mtask.count_notok = res['sum_count_notok'] if res['sum_count_notok'] else 0
mtask.save()
if mtask.parent:
fmtask = mtask.parent
res2 = Mtask.objects.filter(parent=fmtask).aggregate(sum_count_real=Sum(
utask = mtask
if utask and mtask.material_out == utask.material:
res2 = Mtask.objects.filter(utask=utask, material_out=utask.material_out).aggregate(sum_count_real=Sum(
'count_real'), sum_count_ok=Sum('count_ok'), sum_count_notok=Sum('count_notok'))
fmtask.count_real = res2['sum_count_real'] if res2['sum_count_real'] else 0
fmtask.count_ok = res2['sum_count_ok'] if res2['sum_count_ok'] else 0
fmtask.count_notok = res2['sum_count_notok'] if res2['sum_count_notok'] else 0
fmtask.save()
if Mtask.objects.filter(parent=fmtask).exclude(state=Mtask.MTASK_DONE).count() == 0:
fmtask.state = Mtask.MTASK_DONE
fmtask.save()
utask.count_real = res2['sum_count_real'] if res2['sum_count_real'] else 0
utask.count_ok = res2['sum_count_ok'] if res2['sum_count_ok'] else 0
utask.count_notok = res2['sum_count_notok'] if res2['sum_count_notok'] else 0
if utask.count_ok > 0 and utask.state == Utask.UTASK_ASSGINED:
utask.state = Utask.UTASK_WORKING
utask.save()
if Mtask.objects.filter(utask=utask).exclude(state=Mtask.MTASK_DONE).count() == 0:
utask.state = Mtask.MTASK_DONE
utask.save()