feat: route增加主要输入和输出以及自动排产逻辑修改

This commit is contained in:
caoqianming 2023-10-16 16:50:46 +08:00
parent ad84876712
commit 5e34558c02
9 changed files with 131 additions and 40 deletions

View File

@ -0,0 +1,36 @@
# Generated by Django 3.2.12 on 2023-10-16 08:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mtm', '0015_mgroup_need_enm'),
]
operations = [
migrations.AddField(
model_name='route',
name='material_in',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='route_material_in', to='mtm.material', verbose_name='主要输入物料'),
),
migrations.AddField(
model_name='route',
name='material_out',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='route_material_out', to='mtm.material', verbose_name='主要输出物料'),
),
migrations.AlterField(
model_name='route',
name='material',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='关联产品'),
preserve_default=False,
),
migrations.AlterField(
model_name='route',
name='process',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.process', verbose_name='工序'),
preserve_default=False,
),
]

View File

@ -1,6 +1,7 @@
from django.db import models
from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel
from django.db.models import Subquery, OuterRef
from rest_framework.exceptions import ValidationError
class Process(CommonBModel):
@ -140,11 +141,15 @@ class Route(CommonAModel):
工艺路线
"""
material = models.ForeignKey(
Material, verbose_name='关联产品', on_delete=models.CASCADE, null=True, blank=True)
Material, verbose_name='关联产品', on_delete=models.CASCADE)
process = models.ForeignKey(
Process, verbose_name='工序', on_delete=models.CASCADE, null=True, blank=True)
Process, verbose_name='工序', on_delete=models.CASCADE)
sort = models.PositiveSmallIntegerField('顺序', default=1)
is_autotask = models.BooleanField('是否自动排产', default=False)
material_in = models.ForeignKey(
Material, verbose_name='主要输入物料', on_delete=models.CASCADE, related_name='route_material_in', null=True, blank=True)
material_out = models.ForeignKey(
Material, verbose_name='主要输出物料', on_delete=models.CASCADE, related_name='route_material_out', null=True, blank=True)
out_rate = models.FloatField('出材率', default=100, null=True, blank=True)
@staticmethod
@ -153,6 +158,11 @@ class Route(CommonAModel):
返回工艺路线带车间
"""
kwargs = {'material': material}
# 校验工艺路线是否正常
rq = Route.objects.filter(
**kwargs).order_by('sort', 'process__sort', 'create_time')
if rq.first().material_in is None or rq.last().material_out is None or rq.last().material_out != rq.last().material:
raise ValidationError('首步缺少输入/最后一步缺少输出')
if autotask:
kwargs['is_autotask'] = True
return Route.objects.annotate(mgroups=Subquery(Mgroup.objects.filter(process=OuterRef('pk')))).filter(**kwargs).order_by('sort', 'process__sort' 'create_time')
return Route.objects.annotate(mgroups=Subquery(Mgroup.objects.filter(process=OuterRef('pk')))).filter(**kwargs).order_by('sort', 'process__sort', 'create_time')

View File

@ -87,7 +87,26 @@ class RouteSerializer(CustomModelSerializer):
raise ValidationError('请选择最终产品')
return super().validate(attrs)
def gen_material_out(self, instance):
instance.material_out, _ = Material.objects.get_or_create(type=Material.MA_TYPE_HALFGOOD, parent=instance.material, process=instance.process,
defaults={'parent': instance.material, 'process': instance.process,
'is_hidden': True, 'name': instance.material.name,
'number': instance.material.number,
'specification': instance.material.specification,
'type': Material.MA_TYPE_HALFGOOD,
'create_by': self.request.user,
'update_by': self.request.user,
})
instance.save()
def create(self, validated_data):
instance = super().create(validated_data)
self.gen_material_out()
return instance
def update(self, instance, validated_data):
validated_data.pop('material', None)
validated_data.pop('process', None)
return super().update(instance, validated_data)
instance = super().update(instance, validated_data)
self.gen_material_out()
return instance

View File

@ -18,9 +18,9 @@ class MtaskFilter(filters.FilterSet):
"mgroup__cate": ["exact"],
"mgroup__process": ["exact"],
"mgroup__process__cate": ["exact"],
"material": ["exact"],
"material__type": ["exact"],
"material__is_hidden": ["exact"],
"material_out": ["exact"],
"material_out__type": ["exact"],
"material_out__is_hidden": ["exact"],
"parent": ["exact", "isnull"]
}

View File

@ -0,0 +1,34 @@
# Generated by Django 3.2.12 on 2023-10-16 08:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mtm', '0016_auto_20231016_1648'),
('pm', '0005_alter_mtask_state'),
]
operations = [
migrations.RemoveField(
model_name='mtask',
name='material',
),
migrations.RemoveField(
model_name='mtask',
name='material_before',
),
migrations.AddField(
model_name='mtask',
name='material_in',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='mtask_material_in', to='mtm.material', verbose_name='领用物'),
),
migrations.AddField(
model_name='mtask',
name='material_out',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='mtask_material_out', to='mtm.material', verbose_name='产物'),
preserve_default=False,
),
]

View File

@ -24,10 +24,10 @@ class Mtask(CommonADModel):
number = models.CharField('编号', max_length=50, unique=True)
mgroup = models.ForeignKey(
Mgroup, verbose_name='工段', on_delete=models.CASCADE)
material_before = models.ForeignKey(
Material, verbose_name='领用物', on_delete=models.CASCADE, related_name='mtask_material_before', null=True, blank=True)
material = models.ForeignKey(
Material, verbose_name='产物', on_delete=models.CASCADE, related_name='mtask_material')
material_in = models.ForeignKey(
Material, verbose_name='领用物', on_delete=models.CASCADE, related_name='mtask_material_in', null=True, blank=True)
material_out = models.ForeignKey(
Material, verbose_name='产物', on_delete=models.CASCADE, related_name='mtask_material_out')
count = models.PositiveIntegerField('任务数', default=1)
count_real = models.PositiveIntegerField('实际生产数', default=0)
count_ok = models.PositiveIntegerField('合格数', default=0)

View File

@ -48,12 +48,16 @@ class PmService:
rela_days = (end_date - start_date).days + 1
# 获取工艺路线
rqs = Route.get_routes(product)
last_route = rqs.last() # 最后一步是产生产品
# 创建父任务
for ind, val in enumerate(rqs):
material_before = None
if ind > 0:
material_before = rqs[ind-1].material
if val.material_out:
halfgood = val.material_out
else:
raise ParseError(f'{ind+1}步-无输出物料')
if val.material_in:
material_before = val.material_in
elif ind > 0:
material_before = rqs[ind-1].material_out
if val.is_autotask:
# 找到工段
mgroups = val.mgroups
@ -61,21 +65,9 @@ class PmService:
if mgroups_count == 1:
mgroup = mgroups.first()
elif mgroups_count == 0:
raise ParseError(f'{val.name}-工段不存在!')
raise ParseError(f'{ind+1}-工段不存在!')
else: # 后面可能会指定车间
raise ParseError(f'{val.name}-工段存在多个!')
# 找到存在的半成品
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
raise ParseError(f'{ind+1}步-工段存在多个!')
task_count = count
if val.out_rate:
if val.out_rate > 1:

View File

@ -23,7 +23,7 @@ class MtaskViewSet(CustomModelViewSet):
queryset = Mtask.objects.all()
serializer_class = MtaskSerializer
filterset_class = MtaskFilter
select_related_fields = ['material', 'mgroup']
select_related_fields = ['material_in', 'material_out', 'mgroup']
ordering_fields = ['start_date', 'mgroup__process__sort']
ordering = ['mgroup__process__sort', '-start_date', '-create_time']

View File

@ -110,27 +110,27 @@ def mlog_confirm(mlog: Mlog):
"""
mtask = mlog.mtask
belong_dept = mtask.belong_dept
material = mtask.material
material_before = mtask.material_before
if material_before and material_before.is_hidden is False: # 需要进行车间库存管理
material_out = mtask.material_out
material_in = mtask.material_in
if material_in and material_in.is_hidden is False: # 需要进行车间库存管理
# 需要判断领用数是否合理
material_has_qs = WMaterial.objects.filter(
batch=mlog.batch, material=material_before, belong_dept=belong_dept)
batch=mlog.batch, material=material_in, belong_dept=belong_dept)
count_x = material_has_qs.count()
if count_x == 1:
material_has = material_has_qs.first()
elif count_x == 0:
raise ParseError(f'{material_before.name}-{mlog.batch}-批次库存不存在!')
raise ParseError(f'{material_in.name}-{mlog.batch}-批次库存不存在!')
else:
raise ParseError(f'{material_before.name}-{mlog.batch}-存在多个相同批次!')
raise ParseError(f'{material_in.name}-{mlog.batch}-存在多个相同批次!')
if mlog.count_use > material_has.count:
raise ParseError(f'{material_before.name}-{mlog.batch}-该批次车间库存不足!')
raise ParseError(f'{material_in.name}-{mlog.batch}-该批次车间库存不足!')
else:
material_has.count = material_has.count - mlog.count_use
material_has.save()
if material.is_hidden is False: # 需要入库
wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material, belong_dept=belong_dept, defaults={
'batch': mlog.batch, 'material': material, 'belong_dept': belong_dept
if material_out.is_hidden is False: # 需要入库
wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material_out, belong_dept=belong_dept, defaults={
'batch': mlog.batch, 'material': material_out, 'belong_dept': belong_dept
})
wmaterial.count = wmaterial.count + mlog.count_ok
wmaterial.save()