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 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')

View File

@ -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

View File

@ -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"]
} }

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) 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)

View File

@ -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:

View File

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

View File

@ -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()