统一车间库存归属策略
This commit is contained in:
parent
dde5b0a6e8
commit
1700a58409
|
|
@ -5,7 +5,7 @@ from apps.mtm.models import Material, Shift, Mgroup, Process
|
||||||
|
|
||||||
@admin.register(Process)
|
@admin.register(Process)
|
||||||
class ProcessAdmin(admin.ModelAdmin):
|
class ProcessAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'name', 'cate', 'sort', 'into_wm_mgroup', 'store_notok', 'batch_append_equip', 'mlog_need_ticket')
|
list_display = ('id', 'name', 'cate', 'sort', 'into_wm_scope', 'store_notok', 'batch_append_equip', 'mlog_need_ticket')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Material)
|
@admin.register(Material)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_scope_forward(apps, schema_editor):
|
||||||
|
Process = apps.get_model('mtm', 'Process')
|
||||||
|
Process.objects.filter(into_wm_mgroup=True).update(into_wm_scope=10)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_scope_backward(apps, schema_editor):
|
||||||
|
Process = apps.get_model('mtm', 'Process')
|
||||||
|
Process.objects.filter(into_wm_scope=10).update(into_wm_mgroup=True)
|
||||||
|
Process.objects.exclude(into_wm_scope=10).update(into_wm_mgroup=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mtm', '0067_material_test_mode_in'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='process',
|
||||||
|
name='into_wm_scope',
|
||||||
|
field=models.PositiveSmallIntegerField(
|
||||||
|
choices=[(10, '工段'), (20, '部门'), (30, '全局')],
|
||||||
|
default=20,
|
||||||
|
verbose_name='产出库存归属',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_scope_forward, migrate_scope_backward),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='process',
|
||||||
|
name='into_wm_mgroup',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -9,6 +9,28 @@ from django.utils import timezone
|
||||||
from django.db.models import Sum, Q, Value, F, ExpressionWrapper, DecimalField
|
from django.db.models import Sum, Q, Value, F, ExpressionWrapper, DecimalField
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
|
|
||||||
|
|
||||||
|
class WmScope(models.IntegerChoices):
|
||||||
|
MGROUP = 10, '工段'
|
||||||
|
DEPT = 20, '部门'
|
||||||
|
GLOBAL = 30, '全局'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_location(cls, scope, mgroup):
|
||||||
|
"""将库存归属策略统一解析为 WMaterial 的位置字段。"""
|
||||||
|
scope = cls(scope)
|
||||||
|
if scope == cls.GLOBAL:
|
||||||
|
return {'mgroup': None, 'belong_dept': None}
|
||||||
|
if mgroup is None:
|
||||||
|
raise ParseError('工段或部门库存必须提供工段')
|
||||||
|
if scope == cls.MGROUP:
|
||||||
|
return {'mgroup': mgroup, 'belong_dept': mgroup.belong_dept}
|
||||||
|
return {'mgroup': None, 'belong_dept': mgroup.belong_dept}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def requires_mgroup(cls, scope):
|
||||||
|
return cls(scope) == cls.MGROUP
|
||||||
|
|
||||||
class Process(CommonBModel):
|
class Process(CommonBModel):
|
||||||
"""
|
"""
|
||||||
TN:工序
|
TN:工序
|
||||||
|
|
@ -29,7 +51,8 @@ class Process(CommonBModel):
|
||||||
instruction = models.ForeignKey(
|
instruction = models.ForeignKey(
|
||||||
File, verbose_name='指导书', on_delete=models.SET_NULL, null=True, blank=True)
|
File, verbose_name='指导书', on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
instruction_content = models.TextField('指导书内容', null=True, blank=True)
|
instruction_content = models.TextField('指导书内容', null=True, blank=True)
|
||||||
into_wm_mgroup = models.BooleanField('交接到工段', default=False)
|
into_wm_scope = models.PositiveSmallIntegerField(
|
||||||
|
'产出库存归属', choices=WmScope.choices, default=WmScope.DEPT)
|
||||||
store_notok = models.BooleanField('不合格品是否入库', default=False)
|
store_notok = models.BooleanField('不合格品是否入库', default=False)
|
||||||
batch_append_equip = models.BooleanField('批号追加设备', default=False)
|
batch_append_equip = models.BooleanField('批号追加设备', default=False)
|
||||||
mlog_need_ticket = models.BooleanField('日志提交是否需要审批', default=False)
|
mlog_need_ticket = models.BooleanField('日志提交是否需要审批', default=False)
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class WMaterialFilter(filters.FilterSet):
|
||||||
"material__name": ["exact", "in", "contains"],
|
"material__name": ["exact", "in", "contains"],
|
||||||
"material__process": ["exact", "in"],
|
"material__process": ["exact", "in"],
|
||||||
"material__process__name": ["exact", "contains", "in"],
|
"material__process__name": ["exact", "contains", "in"],
|
||||||
"belong_dept": ["exact"],
|
"belong_dept": ["exact", "isnull"],
|
||||||
"belong_dept__name": ["exact", "in"],
|
"belong_dept__name": ["exact", "in"],
|
||||||
"batch": ["exact", "contains"],
|
"batch": ["exact", "contains"],
|
||||||
"mgroup": ["exact", "in", "isnull"],
|
"mgroup": ["exact", "in", "isnull"],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_scope_forward(apps, schema_editor):
|
||||||
|
Mlog = apps.get_model('wpm', 'Mlog')
|
||||||
|
Mlog.objects.filter(stored_mgroup=True).update(stored_scope=10)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_scope_backward(apps, schema_editor):
|
||||||
|
Mlog = apps.get_model('wpm', 'Mlog')
|
||||||
|
Mlog.objects.filter(stored_scope=10).update(stored_mgroup=True)
|
||||||
|
Mlog.objects.exclude(stored_scope=10).update(stored_mgroup=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mtm', '0068_process_into_wm_scope'),
|
||||||
|
('wpm', '0134_alter_mlog_clear_defect'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mlog',
|
||||||
|
name='stored_scope',
|
||||||
|
field=models.PositiveSmallIntegerField(
|
||||||
|
choices=[(10, '工段'), (20, '部门'), (30, '全局')],
|
||||||
|
default=20,
|
||||||
|
verbose_name='产出库存归属',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_scope_forward, migrate_scope_backward),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='mlog',
|
||||||
|
name='stored_mgroup',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from apps.utils.models import CommonADModel, CommonBDModel, BaseModel
|
from apps.utils.models import CommonADModel, CommonBDModel, BaseModel
|
||||||
from apps.mtm.models import Mgroup, Team, Shift, Material, Route, Process
|
from apps.mtm.models import Mgroup, Team, Shift, Material, Route, Process, WmScope
|
||||||
from apps.pm.models import Mtask, Mtaskb
|
from apps.pm.models import Mtask, Mtaskb
|
||||||
from apps.system.models import User
|
from apps.system.models import User
|
||||||
from django.utils.timezone import localtime
|
from django.utils.timezone import localtime
|
||||||
|
|
@ -103,6 +103,9 @@ class WmStateOption(models.IntegerChoices):
|
||||||
class WMaterial(CommonBDModel):
|
class WMaterial(CommonBDModel):
|
||||||
"""
|
"""
|
||||||
TN: 车间库存
|
TN: 车间库存
|
||||||
|
|
||||||
|
归属范围分三级:mgroup 指定工段,只有 belong_dept 指定部门,
|
||||||
|
两者都为空表示全局库存。
|
||||||
"""
|
"""
|
||||||
WM_OK = 10
|
WM_OK = 10
|
||||||
WM_NOTOK = 20
|
WM_NOTOK = 20
|
||||||
|
|
@ -144,7 +147,11 @@ class WMaterial(CommonBDModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def belong_dept_or_mgroup_id(self):
|
def belong_dept_or_mgroup_id(self):
|
||||||
return self.mgroup.id if self.mgroup else self.belong_dept.id
|
if self.mgroup_id:
|
||||||
|
return ("mgroup", self.mgroup_id)
|
||||||
|
if self.belong_dept_id:
|
||||||
|
return ("belong_dept", self.belong_dept_id)
|
||||||
|
return ("global", None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def count_working(self):
|
def count_working(self):
|
||||||
|
|
@ -171,7 +178,6 @@ class WMaterial(CommonBDModel):
|
||||||
if qs is None:
|
if qs is None:
|
||||||
qs = cls.objects
|
qs = cls.objects
|
||||||
return WMaterial.ava_qs(mgroup=mtask.mgroup, qs=qs).filter(
|
return WMaterial.ava_qs(mgroup=mtask.mgroup, qs=qs).filter(
|
||||||
mgroup=mtask.mgroup,
|
|
||||||
material=mtask.material_in,
|
material=mtask.material_in,
|
||||||
batch__in=Subquery(
|
batch__in=Subquery(
|
||||||
Mlogb.objects.filter(
|
Mlogb.objects.filter(
|
||||||
|
|
@ -240,7 +246,8 @@ class Mlog(CommonADModel):
|
||||||
work_end_time = models.DateTimeField('生产结束时间', null=True, blank=True)
|
work_end_time = models.DateTimeField('生产结束时间', null=True, blank=True)
|
||||||
hour_work = models.FloatField('预计工时', null=True, blank=True)
|
hour_work = models.FloatField('预计工时', null=True, blank=True)
|
||||||
reminder_interval_list = models.JSONField('提醒间隔', default=list, blank=True)
|
reminder_interval_list = models.JSONField('提醒间隔', default=list, blank=True)
|
||||||
stored_mgroup = models.BooleanField('入库到工段', default=False)
|
stored_scope = models.PositiveSmallIntegerField(
|
||||||
|
'产出库存归属', choices=WmScope.choices, default=WmScope.DEPT)
|
||||||
stored_notok = models.BooleanField('不合格品是否已入库', default=False)
|
stored_notok = models.BooleanField('不合格品是否已入库', default=False)
|
||||||
clear_defect = models.BooleanField('合格B类记为合格', default=False)
|
clear_defect = models.BooleanField('合格B类记为合格', default=False)
|
||||||
route = models.ForeignKey(Route, verbose_name='生产路线', on_delete=models.SET_NULL, null=True, blank=True)
|
route = models.ForeignKey(Route, verbose_name='生产路线', on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from apps.system.serializers import UserSimpleSerializer
|
||||||
from apps.pm.models import Mtask, Mtaskb
|
from apps.pm.models import Mtask, Mtaskb
|
||||||
from apps.wpm.tasks import cal_enstat_when_pcoal_heat_change, cal_enstat_when_team_change, cal_exp_duration_sec
|
from apps.wpm.tasks import cal_enstat_when_pcoal_heat_change, cal_enstat_when_team_change, cal_exp_duration_sec
|
||||||
from apps.wpm.services import get_sflog, find_material_can_change, generate_new_batch
|
from apps.wpm.services import get_sflog, find_material_can_change, generate_new_batch
|
||||||
from apps.mtm.models import Mgroup, TeamMember, Shift, Material, Route, Process
|
from apps.mtm.models import Mgroup, TeamMember, Shift, Material, Route, Process, WmScope
|
||||||
from apps.mtm.serializers import MaterialSimpleSerializer
|
from apps.mtm.serializers import MaterialSimpleSerializer
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
@ -232,16 +232,19 @@ class WMaterialSerializer(CustomModelSerializer):
|
||||||
class WMaterialCreateSerializer(CustomModelSerializer):
|
class WMaterialCreateSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WMaterial
|
model = WMaterial
|
||||||
fields = ['material', 'count', 'batch', 'mgroup']
|
fields = ['material', 'count', 'batch', 'mgroup', 'belong_dept']
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'material': {'required': True},
|
'material': {'required': True},
|
||||||
'count': {'required': True},
|
'count': {'required': True},
|
||||||
'batch': {'required': True},
|
'batch': {'required': True},
|
||||||
'mgroup': {'required': True, 'allow_null': False},
|
'mgroup': {'required': False, 'allow_null': True},
|
||||||
|
'belong_dept': {'required': False, 'allow_null': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
attrs['belong_dept'] = attrs['mgroup'].belong_dept
|
mgroup = attrs.get('mgroup')
|
||||||
|
if mgroup:
|
||||||
|
attrs['belong_dept'] = mgroup.belong_dept
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1300,7 +1303,12 @@ class HandoverSerializer(CustomModelSerializer):
|
||||||
attrs['send_dept'] = wm.belong_dept
|
attrs['send_dept'] = wm.belong_dept
|
||||||
if wm.mgroup:
|
if wm.mgroup:
|
||||||
attrs['send_mgroup'] = wm.mgroup
|
attrs['send_mgroup'] = wm.mgroup
|
||||||
if attrs['material'].process and attrs['material'].process.into_wm_mgroup and 'recive_mgroup' not in attrs:
|
material_process = attrs['material'].process
|
||||||
|
if (
|
||||||
|
material_process
|
||||||
|
and WmScope.requires_mgroup(material_process.into_wm_scope)
|
||||||
|
and not attrs.get('recive_mgroup')
|
||||||
|
):
|
||||||
raise ParseError('必须指定交接工段')
|
raise ParseError('必须指定交接工段')
|
||||||
if 'recive_mgroup' in attrs and attrs['recive_mgroup']:
|
if 'recive_mgroup' in attrs and attrs['recive_mgroup']:
|
||||||
attrs['recive_dept'] = attrs['recive_mgroup'].belong_dept
|
attrs['recive_dept'] = attrs['recive_mgroup'].belong_dept
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from apps.wpmw.models import Wpr, WprDefect
|
||||||
from ..qm.models import Defect, Ftest
|
from ..qm.models import Defect, Ftest
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
from apps.utils.tasks import ctask_run
|
from apps.utils.tasks import ctask_run
|
||||||
from apps.mtm.models import Process
|
from apps.mtm.models import Process, WmScope
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
|
||||||
myLogger = logging.getLogger('log')
|
myLogger = logging.getLogger('log')
|
||||||
|
|
@ -187,7 +187,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
|
|
||||||
mgroup = mlog.mgroup
|
mgroup = mlog.mgroup
|
||||||
process = mgroup.process
|
process = mgroup.process
|
||||||
stored_mgroup = process.into_wm_mgroup
|
stored_scope = process.into_wm_scope
|
||||||
stored_notok = process.store_notok
|
stored_notok = process.store_notok
|
||||||
belong_dept = mgroup.belong_dept
|
belong_dept = mgroup.belong_dept
|
||||||
material_out: Material = mlog.material_out
|
material_out: Material = mlog.material_out
|
||||||
|
|
@ -195,7 +195,8 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
supplier = mlog.supplier # 外协
|
supplier = mlog.supplier # 外协
|
||||||
is_fix = mlog.is_fix
|
is_fix = mlog.is_fix
|
||||||
if is_fix: # 如果是返工,直接放到工段下
|
if is_fix: # 如果是返工,直接放到工段下
|
||||||
stored_mgroup = True
|
stored_scope = WmScope.MGROUP
|
||||||
|
stored_location = WmScope.resolve_location(stored_scope, mgroup)
|
||||||
m_ins_list = []
|
m_ins_list = []
|
||||||
m_ins_bl_list = []
|
m_ins_bl_list = []
|
||||||
|
|
||||||
|
|
@ -237,15 +238,23 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
continue
|
continue
|
||||||
if mi_count <= 0:
|
if mi_count <= 0:
|
||||||
raise ParseError('存在非正数!')
|
raise ParseError('存在非正数!')
|
||||||
# 需要判断领用数是否合理
|
# 优先使用日志已选库存;未选择时按工段、部门、全局依次匹配并记录实际来源
|
||||||
# 优先使用工段库存
|
wm_in = getattr(mlog_or_b, 'wm_in', None)
|
||||||
if isinstance(mlog_or_b, Mlogb) and mlog_or_b.wm_in:
|
if wm_in:
|
||||||
wm = WMaterial.objects.select_for_update().get(id=mlog_or_b.wm_in.id)
|
wm = WMaterial.objects.select_for_update().get(id=wm_in.id)
|
||||||
else:
|
else:
|
||||||
wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma, mgroup=mgroup, state=WMaterial.WM_OK)
|
wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma, mgroup=mgroup, state=WMaterial.WM_OK)
|
||||||
if not wm_qs.exists():
|
if not wm_qs.exists():
|
||||||
wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma,
|
wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma,
|
||||||
belong_dept=belong_dept, mgroup=None, state=WMaterial.WM_OK)
|
belong_dept=belong_dept, mgroup=None, state=WMaterial.WM_OK)
|
||||||
|
if not wm_qs.exists():
|
||||||
|
wm_qs = WMaterial.objects.filter(
|
||||||
|
batch=mi_batch,
|
||||||
|
material=mi_ma,
|
||||||
|
belong_dept=None,
|
||||||
|
mgroup=None,
|
||||||
|
state=WMaterial.WM_OK,
|
||||||
|
)
|
||||||
count_x = wm_qs.count()
|
count_x = wm_qs.count()
|
||||||
if count_x == 1:
|
if count_x == 1:
|
||||||
wm = WMaterial.objects.select_for_update().get(id=wm_qs.first().id)
|
wm = WMaterial.objects.select_for_update().get(id=wm_qs.first().id)
|
||||||
|
|
@ -255,6 +264,8 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
else:
|
else:
|
||||||
raise ParseError(
|
raise ParseError(
|
||||||
f'{str(mi_ma)}-{mi_batch}-存在多个相同批次!')
|
f'{str(mi_ma)}-{mi_batch}-存在多个相同批次!')
|
||||||
|
mlog_or_b.wm_in = wm
|
||||||
|
mlog_or_b.save(update_fields=['wm_in'])
|
||||||
|
|
||||||
if mi_count > wm.count:
|
if mi_count > wm.count:
|
||||||
raise ParseError(
|
raise ParseError(
|
||||||
|
|
@ -280,8 +291,14 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
c_state = WMaterial.WM_NOTOK
|
c_state = WMaterial.WM_NOTOK
|
||||||
if isinstance(defect, Defect) and defect.okcate == Defect.DEFECT_OK_B:
|
if isinstance(defect, Defect) and defect.okcate == Defect.DEFECT_OK_B:
|
||||||
c_state = WMaterial.WM_OK
|
c_state = WMaterial.WM_OK
|
||||||
lookup = {'batch': batch, 'material': material, 'mgroup': mgroup, 'defect': defect, 'state': c_state}
|
lookup = {
|
||||||
wm, is_create = WMaterial.locked_get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
'batch': batch,
|
||||||
|
'material': material,
|
||||||
|
'defect': defect,
|
||||||
|
'state': c_state,
|
||||||
|
**stored_location,
|
||||||
|
}
|
||||||
|
wm, is_create = WMaterial.locked_get_or_create(**lookup, defaults={})
|
||||||
wm.count = wm.count + count
|
wm.count = wm.count + count
|
||||||
if is_create:
|
if is_create:
|
||||||
wm.create_by = user
|
wm.create_by = user
|
||||||
|
|
@ -359,18 +376,19 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or (
|
wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or (
|
||||||
isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B]
|
isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B]
|
||||||
) else WMaterial.WM_NOTOK
|
) else WMaterial.WM_NOTOK
|
||||||
lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None,
|
lookup = {
|
||||||
'notok_sign': None, 'defect': None, 'state': wm_state}
|
'batch': mo_batch,
|
||||||
|
'material': mo_ma,
|
||||||
|
'notok_sign': None,
|
||||||
|
'defect': None,
|
||||||
|
'state': wm_state,
|
||||||
|
**stored_location,
|
||||||
|
}
|
||||||
if isinstance(notok_sign_or_defect, Defect):
|
if isinstance(notok_sign_or_defect, Defect):
|
||||||
lookup['defect'] = notok_sign_or_defect
|
lookup['defect'] = notok_sign_or_defect
|
||||||
elif notok_sign_or_defect is not None:
|
elif notok_sign_or_defect is not None:
|
||||||
lookup['notok_sign'] = notok_sign_or_defect
|
lookup['notok_sign'] = notok_sign_or_defect
|
||||||
if stored_mgroup:
|
wm, is_create2 = WMaterial.locked_get_or_create(**lookup, defaults={})
|
||||||
lookup['mgroup'] = mgroup
|
|
||||||
else:
|
|
||||||
lookup['belong_dept'] = belong_dept
|
|
||||||
|
|
||||||
wm, is_create2 = WMaterial.locked_get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
|
||||||
wm.count = wm.count + mo_count
|
wm.count = wm.count + mo_count
|
||||||
wm.count_eweight = mo_count_eweight
|
wm.count_eweight = mo_count_eweight
|
||||||
wm.update_by = user
|
wm.update_by = user
|
||||||
|
|
@ -415,7 +433,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
mlog.submit_time = now
|
mlog.submit_time = now
|
||||||
mlog.submit_user = user
|
mlog.submit_user = user
|
||||||
mlog.stored_notok = stored_notok
|
mlog.stored_notok = stored_notok
|
||||||
mlog.stored_mgroup = stored_mgroup
|
mlog.stored_scope = stored_scope
|
||||||
if mlog.work_end_time is None and mlog.work_start_time is not None:
|
if mlog.work_end_time is None and mlog.work_start_time is not None:
|
||||||
mlog.work_end_time = now
|
mlog.work_end_time = now
|
||||||
mlog.save()
|
mlog.save()
|
||||||
|
|
@ -448,7 +466,8 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
material_out:Material = mlog.material_out
|
material_out:Material = mlog.material_out
|
||||||
material_in:Material = mlog.material_in
|
material_in:Material = mlog.material_in
|
||||||
stored_notok = mlog.stored_notok
|
stored_notok = mlog.stored_notok
|
||||||
stored_mgroup = mlog.stored_mgroup
|
stored_scope = mlog.stored_scope
|
||||||
|
stored_location = WmScope.resolve_location(stored_scope, mgroup)
|
||||||
is_fix = mlog.is_fix
|
is_fix = mlog.is_fix
|
||||||
|
|
||||||
if is_fix:
|
if is_fix:
|
||||||
|
|
@ -523,15 +542,18 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or (
|
wm_state = WMaterial.WM_OK if notok_sign_or_defect is None or (
|
||||||
isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B]
|
isinstance(notok_sign_or_defect, Defect) and notok_sign_or_defect.okcate in [Defect.DEFECT_OK, Defect.DEFECT_OK_B]
|
||||||
) else WMaterial.WM_NOTOK
|
) else WMaterial.WM_NOTOK
|
||||||
lookup = {'batch': mo_batch, 'material': mo_ma, 'mgroup': None, 'notok_sign': None, 'defect': None, 'state': wm_state}
|
lookup = {
|
||||||
|
'batch': mo_batch,
|
||||||
|
'material': mo_ma,
|
||||||
|
'notok_sign': None,
|
||||||
|
'defect': None,
|
||||||
|
'state': wm_state,
|
||||||
|
**stored_location,
|
||||||
|
}
|
||||||
if isinstance(notok_sign_or_defect, Defect):
|
if isinstance(notok_sign_or_defect, Defect):
|
||||||
lookup['defect'] = notok_sign_or_defect
|
lookup['defect'] = notok_sign_or_defect
|
||||||
else:
|
else:
|
||||||
lookup['notok_sign'] = notok_sign_or_defect
|
lookup['notok_sign'] = notok_sign_or_defect
|
||||||
if stored_mgroup:
|
|
||||||
lookup['mgroup'] = mgroup
|
|
||||||
else:
|
|
||||||
lookup['belong_dept'] = belong_dept
|
|
||||||
wm_qs = WMaterial.objects.filter(**lookup)
|
wm_qs = WMaterial.objects.filter(**lookup)
|
||||||
count_x = wm_qs.count()
|
count_x = wm_qs.count()
|
||||||
if count_x == 1:
|
if count_x == 1:
|
||||||
|
|
@ -584,18 +606,18 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
continue
|
continue
|
||||||
if mi_count <= 0:
|
if mi_count <= 0:
|
||||||
raise ParseError('存在非正数!')
|
raise ParseError('存在非正数!')
|
||||||
if isinstance(mlog_or_b, Mlogb) and mlog_or_b.wm_in:
|
wm_in = getattr(mlog_or_b, 'wm_in', None)
|
||||||
wm = WMaterial.objects.select_for_update().get(id=mlog_or_b.wm_in.id)
|
if wm_in:
|
||||||
|
wm = WMaterial.objects.select_for_update().get(id=wm_in.id)
|
||||||
else:
|
else:
|
||||||
# 针对光子的情况,实际上必须需要wm_in
|
# 针对光子的情况,实际上必须需要wm_in
|
||||||
lookup = {'batch': mi_batch, 'material': mi_ma, 'mgroup': None, 'state': WMaterial.WM_OK}
|
lookup = {
|
||||||
if stored_mgroup:
|
'batch': mi_batch,
|
||||||
# 退回到本工段
|
'material': mi_ma,
|
||||||
lookup['mgroup'] = mgroup
|
'state': WMaterial.WM_OK,
|
||||||
else:
|
**stored_location,
|
||||||
lookup['belong_dept'] = belong_dept
|
}
|
||||||
|
wm, _ = WMaterial.locked_get_or_create(**lookup, defaults={})
|
||||||
wm, _ = WMaterial.locked_get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
|
||||||
wm.count = wm.count + mi_count
|
wm.count = wm.count + mi_count
|
||||||
wm.update_by = user
|
wm.update_by = user
|
||||||
wm.save()
|
wm.save()
|
||||||
|
|
@ -615,12 +637,14 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
c_state = WMaterial.WM_NOTOK
|
c_state = WMaterial.WM_NOTOK
|
||||||
if isinstance(defect, Defect) and defect.okcate == Defect.DEFECT_OK_B:
|
if isinstance(defect, Defect) and defect.okcate == Defect.DEFECT_OK_B:
|
||||||
c_state = WMaterial.WM_OK
|
c_state = WMaterial.WM_OK
|
||||||
lookup = {'batch': batch, 'material': material, 'mgroup': None, 'defect': defect, 'state': c_state}
|
lookup = {
|
||||||
if stored_mgroup:
|
'batch': batch,
|
||||||
lookup['mgroup'] = mgroup
|
'material': material,
|
||||||
else:
|
'defect': defect,
|
||||||
lookup['belong_dept'] = belong_dept
|
'state': c_state,
|
||||||
wm, is_create = WMaterial.locked_get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
**stored_location,
|
||||||
|
}
|
||||||
|
wm, is_create = WMaterial.locked_get_or_create(**lookup, defaults={})
|
||||||
wm.count = wm.count - count
|
wm.count = wm.count - count
|
||||||
if wm.count < 0:
|
if wm.count < 0:
|
||||||
raise ParseError('加工前不良数量大于库存量')
|
raise ParseError('加工前不良数量大于库存量')
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
from apps.mtm.models import Material
|
from apps.mtm.models import Material, WmScope
|
||||||
|
from apps.wpm.filters import WMaterialFilter
|
||||||
|
from apps.wpm.models import WMaterial
|
||||||
|
from apps.wpm.serializers import WMaterialCreateSerializer
|
||||||
from apps.wpm.views import MlogbwViewSet
|
from apps.wpm.views import MlogbwViewSet
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -59,3 +62,80 @@ class MlogbwViewSetTests(SimpleTestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(cal_count_notok.call_count, 2)
|
self.assertEqual(cal_count_notok.call_count, 2)
|
||||||
mlog.cal_mlog_count_from_mlogb.assert_called_once_with()
|
mlog.cal_mlog_count_from_mlogb.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
class WMaterialScopeTests(SimpleTestCase):
|
||||||
|
def test_scope_resolver_returns_consistent_location_fields(self):
|
||||||
|
dept = object()
|
||||||
|
mgroup = SimpleNamespace(belong_dept=dept)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
WmScope.resolve_location(WmScope.MGROUP, mgroup),
|
||||||
|
{"mgroup": mgroup, "belong_dept": dept},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
WmScope.resolve_location(WmScope.DEPT, mgroup),
|
||||||
|
{"mgroup": None, "belong_dept": dept},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
WmScope.resolve_location(WmScope.GLOBAL, mgroup),
|
||||||
|
{"mgroup": None, "belong_dept": None},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_only_mgroup_scope_requires_handover_mgroup(self):
|
||||||
|
self.assertTrue(WmScope.requires_mgroup(WmScope.MGROUP))
|
||||||
|
self.assertFalse(WmScope.requires_mgroup(WmScope.DEPT))
|
||||||
|
self.assertFalse(WmScope.requires_mgroup(WmScope.GLOBAL))
|
||||||
|
|
||||||
|
def test_global_scope_key_does_not_access_missing_relations(self):
|
||||||
|
wm = WMaterial()
|
||||||
|
|
||||||
|
self.assertEqual(wm.belong_dept_or_mgroup_id, ("global", None))
|
||||||
|
|
||||||
|
def test_department_and_mgroup_scope_keys_cannot_collide(self):
|
||||||
|
dept_wm = WMaterial(belong_dept_id=10)
|
||||||
|
mgroup_wm = WMaterial(mgroup_id=10)
|
||||||
|
|
||||||
|
self.assertNotEqual(
|
||||||
|
dept_wm.belong_dept_or_mgroup_id,
|
||||||
|
mgroup_wm.belong_dept_or_mgroup_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_manual_create_allows_global_scope(self):
|
||||||
|
serializer = WMaterialCreateSerializer()
|
||||||
|
attrs = {
|
||||||
|
"material": Material(),
|
||||||
|
"count": 1,
|
||||||
|
"batch": "GLOBAL-001",
|
||||||
|
}
|
||||||
|
|
||||||
|
validated = serializer.validate(attrs)
|
||||||
|
|
||||||
|
self.assertFalse(serializer.fields["mgroup"].required)
|
||||||
|
self.assertFalse(serializer.fields["belong_dept"].required)
|
||||||
|
self.assertNotIn("mgroup", validated)
|
||||||
|
self.assertNotIn("belong_dept", validated)
|
||||||
|
|
||||||
|
def test_global_scope_can_be_filtered_explicitly(self):
|
||||||
|
self.assertIn(
|
||||||
|
"isnull",
|
||||||
|
WMaterialFilter.Meta.fields["belong_dept"],
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"isnull",
|
||||||
|
WMaterialFilter.Meta.fields["mgroup"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_manual_create_derives_department_from_mgroup(self):
|
||||||
|
dept = SimpleNamespace(id=20)
|
||||||
|
mgroup = SimpleNamespace(id=10, belong_dept=dept)
|
||||||
|
attrs = {
|
||||||
|
"material": Material(),
|
||||||
|
"count": 1,
|
||||||
|
"batch": "MGROUP-001",
|
||||||
|
"mgroup": mgroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
validated = WMaterialCreateSerializer().validate(attrs)
|
||||||
|
|
||||||
|
self.assertIs(validated["belong_dept"], dept)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue