feat: route增加主要输入和输出以及自动排产逻辑修改
This commit is contained in:
parent
ad84876712
commit
5e34558c02
|
@ -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,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel
|
from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel
|
||||||
from django.db.models import Subquery, OuterRef
|
from django.db.models import Subquery, OuterRef
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
class Process(CommonBModel):
|
class Process(CommonBModel):
|
||||||
|
@ -140,11 +141,15 @@ class Route(CommonAModel):
|
||||||
工艺路线
|
工艺路线
|
||||||
"""
|
"""
|
||||||
material = models.ForeignKey(
|
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 = 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)
|
sort = models.PositiveSmallIntegerField('顺序', default=1)
|
||||||
is_autotask = models.BooleanField('是否自动排产', default=False)
|
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)
|
out_rate = models.FloatField('出材率', default=100, null=True, blank=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -153,6 +158,11 @@ class Route(CommonAModel):
|
||||||
返回工艺路线带车间
|
返回工艺路线带车间
|
||||||
"""
|
"""
|
||||||
kwargs = {'material': material}
|
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:
|
if autotask:
|
||||||
kwargs['is_autotask'] = True
|
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')
|
||||||
|
|
|
@ -87,7 +87,26 @@ class RouteSerializer(CustomModelSerializer):
|
||||||
raise ValidationError('请选择最终产品')
|
raise ValidationError('请选择最终产品')
|
||||||
return super().validate(attrs)
|
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):
|
def update(self, instance, validated_data):
|
||||||
validated_data.pop('material', None)
|
validated_data.pop('material', None)
|
||||||
validated_data.pop('process', None)
|
validated_data.pop('process', None)
|
||||||
return super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
|
self.gen_material_out()
|
||||||
|
return instance
|
||||||
|
|
|
@ -18,9 +18,9 @@ class MtaskFilter(filters.FilterSet):
|
||||||
"mgroup__cate": ["exact"],
|
"mgroup__cate": ["exact"],
|
||||||
"mgroup__process": ["exact"],
|
"mgroup__process": ["exact"],
|
||||||
"mgroup__process__cate": ["exact"],
|
"mgroup__process__cate": ["exact"],
|
||||||
"material": ["exact"],
|
"material_out": ["exact"],
|
||||||
"material__type": ["exact"],
|
"material_out__type": ["exact"],
|
||||||
"material__is_hidden": ["exact"],
|
"material_out__is_hidden": ["exact"],
|
||||||
"parent": ["exact", "isnull"]
|
"parent": ["exact", "isnull"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
]
|
|
@ -24,10 +24,10 @@ class Mtask(CommonADModel):
|
||||||
number = models.CharField('编号', max_length=50, unique=True)
|
number = models.CharField('编号', max_length=50, unique=True)
|
||||||
mgroup = models.ForeignKey(
|
mgroup = models.ForeignKey(
|
||||||
Mgroup, verbose_name='工段', on_delete=models.CASCADE)
|
Mgroup, verbose_name='工段', on_delete=models.CASCADE)
|
||||||
material_before = models.ForeignKey(
|
material_in = models.ForeignKey(
|
||||||
Material, verbose_name='领用物', on_delete=models.CASCADE, related_name='mtask_material_before', null=True, blank=True)
|
Material, verbose_name='领用物', on_delete=models.CASCADE, related_name='mtask_material_in', null=True, blank=True)
|
||||||
material = models.ForeignKey(
|
material_out = models.ForeignKey(
|
||||||
Material, verbose_name='产物', on_delete=models.CASCADE, related_name='mtask_material')
|
Material, verbose_name='产物', on_delete=models.CASCADE, related_name='mtask_material_out')
|
||||||
count = models.PositiveIntegerField('任务数', default=1)
|
count = models.PositiveIntegerField('任务数', default=1)
|
||||||
count_real = models.PositiveIntegerField('实际生产数', default=0)
|
count_real = models.PositiveIntegerField('实际生产数', default=0)
|
||||||
count_ok = models.PositiveIntegerField('合格数', default=0)
|
count_ok = models.PositiveIntegerField('合格数', default=0)
|
||||||
|
|
|
@ -48,12 +48,16 @@ class PmService:
|
||||||
rela_days = (end_date - start_date).days + 1
|
rela_days = (end_date - start_date).days + 1
|
||||||
# 获取工艺路线
|
# 获取工艺路线
|
||||||
rqs = Route.get_routes(product)
|
rqs = Route.get_routes(product)
|
||||||
last_route = rqs.last() # 最后一步是产生产品
|
|
||||||
# 创建父任务
|
# 创建父任务
|
||||||
for ind, val in enumerate(rqs):
|
for ind, val in enumerate(rqs):
|
||||||
material_before = None
|
if val.material_out:
|
||||||
if ind > 0:
|
halfgood = val.material_out
|
||||||
material_before = rqs[ind-1].material
|
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:
|
if val.is_autotask:
|
||||||
# 找到工段
|
# 找到工段
|
||||||
mgroups = val.mgroups
|
mgroups = val.mgroups
|
||||||
|
@ -61,21 +65,9 @@ class PmService:
|
||||||
if mgroups_count == 1:
|
if mgroups_count == 1:
|
||||||
mgroup = mgroups.first()
|
mgroup = mgroups.first()
|
||||||
elif mgroups_count == 0:
|
elif mgroups_count == 0:
|
||||||
raise ParseError(f'{val.name}-工段不存在!')
|
raise ParseError(f'第{ind+1}步-工段不存在!')
|
||||||
else: # 后面可能会指定车间
|
else: # 后面可能会指定车间
|
||||||
raise ParseError(f'{val.name}-工段存在多个!')
|
raise ParseError(f'第{ind+1}步-工段存在多个!')
|
||||||
# 找到存在的半成品
|
|
||||||
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
|
|
||||||
task_count = count
|
task_count = count
|
||||||
if val.out_rate:
|
if val.out_rate:
|
||||||
if val.out_rate > 1:
|
if val.out_rate > 1:
|
||||||
|
|
|
@ -23,7 +23,7 @@ class MtaskViewSet(CustomModelViewSet):
|
||||||
queryset = Mtask.objects.all()
|
queryset = Mtask.objects.all()
|
||||||
serializer_class = MtaskSerializer
|
serializer_class = MtaskSerializer
|
||||||
filterset_class = MtaskFilter
|
filterset_class = MtaskFilter
|
||||||
select_related_fields = ['material', 'mgroup']
|
select_related_fields = ['material_in', 'material_out', 'mgroup']
|
||||||
ordering_fields = ['start_date', 'mgroup__process__sort']
|
ordering_fields = ['start_date', 'mgroup__process__sort']
|
||||||
ordering = ['mgroup__process__sort', '-start_date', '-create_time']
|
ordering = ['mgroup__process__sort', '-start_date', '-create_time']
|
||||||
|
|
||||||
|
|
|
@ -110,27 +110,27 @@ def mlog_confirm(mlog: Mlog):
|
||||||
"""
|
"""
|
||||||
mtask = mlog.mtask
|
mtask = mlog.mtask
|
||||||
belong_dept = mtask.belong_dept
|
belong_dept = mtask.belong_dept
|
||||||
material = mtask.material
|
material_out = mtask.material_out
|
||||||
material_before = mtask.material_before
|
material_in = mtask.material_in
|
||||||
if material_before and material_before.is_hidden is False: # 需要进行车间库存管理
|
if material_in and material_in.is_hidden is False: # 需要进行车间库存管理
|
||||||
# 需要判断领用数是否合理
|
# 需要判断领用数是否合理
|
||||||
material_has_qs = WMaterial.objects.filter(
|
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()
|
count_x = material_has_qs.count()
|
||||||
if count_x == 1:
|
if count_x == 1:
|
||||||
material_has = material_has_qs.first()
|
material_has = material_has_qs.first()
|
||||||
elif count_x == 0:
|
elif count_x == 0:
|
||||||
raise ParseError(f'{material_before.name}-{mlog.batch}-批次库存不存在!')
|
raise ParseError(f'{material_in.name}-{mlog.batch}-批次库存不存在!')
|
||||||
else:
|
else:
|
||||||
raise ParseError(f'{material_before.name}-{mlog.batch}-存在多个相同批次!')
|
raise ParseError(f'{material_in.name}-{mlog.batch}-存在多个相同批次!')
|
||||||
if mlog.count_use > material_has.count:
|
if mlog.count_use > material_has.count:
|
||||||
raise ParseError(f'{material_before.name}-{mlog.batch}-该批次车间库存不足!')
|
raise ParseError(f'{material_in.name}-{mlog.batch}-该批次车间库存不足!')
|
||||||
else:
|
else:
|
||||||
material_has.count = material_has.count - mlog.count_use
|
material_has.count = material_has.count - mlog.count_use
|
||||||
material_has.save()
|
material_has.save()
|
||||||
if material.is_hidden is False: # 需要入库
|
if material_out.is_hidden is False: # 需要入库
|
||||||
wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material, belong_dept=belong_dept, defaults={
|
wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material_out, belong_dept=belong_dept, defaults={
|
||||||
'batch': mlog.batch, 'material': material, 'belong_dept': belong_dept
|
'batch': mlog.batch, 'material': material_out, 'belong_dept': belong_dept
|
||||||
})
|
})
|
||||||
wmaterial.count = wmaterial.count + mlog.count_ok
|
wmaterial.count = wmaterial.count + mlog.count_ok
|
||||||
wmaterial.save()
|
wmaterial.save()
|
||||||
|
|
Loading…
Reference in New Issue