Restrict global inventory handovers

This commit is contained in:
caoqianming 2026-07-28 12:15:21 +08:00
parent 1700a58409
commit 8af19b8a9a
4 changed files with 160 additions and 9 deletions

View File

@ -0,0 +1,36 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wpm', '0135_mlog_stored_scope'),
]
operations = [
migrations.AlterField(
model_name='handover',
name='send_dept',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='handover_send_dept',
to='system.dept',
verbose_name='送料部门',
),
),
migrations.AlterField(
model_name='handover',
name='recive_dept',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='handover_recive_dept',
to='system.dept',
verbose_name='接收部门',
),
),
]

View File

@ -698,7 +698,8 @@ class Handover(CommonADModel):
send_mgroup = models.ForeignKey(
Mgroup, verbose_name='送料工段', on_delete=models.CASCADE, null=True, blank=True)
send_dept = models.ForeignKey(
Dept, verbose_name='送料部门', on_delete=models.CASCADE, related_name='handover_send_dept')
Dept, verbose_name='送料部门', on_delete=models.CASCADE, related_name='handover_send_dept',
null=True, blank=True)
batch = models.TextField('批次号', null=True, blank=True, db_index=True)
material = models.ForeignKey(
Material, verbose_name='物料', on_delete=models.CASCADE, related_name='h_ma')
@ -707,7 +708,8 @@ class Handover(CommonADModel):
count = models.DecimalField('送料数', default=0, max_digits=11, decimal_places=1)
count_eweight = models.FloatField('单数重量', default=0)
recive_dept = models.ForeignKey(
Dept, verbose_name='接收部门', on_delete=models.CASCADE, related_name='handover_recive_dept')
Dept, verbose_name='接收部门', on_delete=models.CASCADE, related_name='handover_recive_dept',
null=True, blank=True)
recive_mgroup = models.ForeignKey(Mgroup, verbose_name='接收工段', on_delete=models.CASCADE, related_name='handover_recive_mgroup', null=True, blank=True)
recive_user = models.ForeignKey(
User, verbose_name='接收人', on_delete=models.CASCADE, related_name='handover_recive_user', null=True, blank=True)

View File

@ -1301,20 +1301,62 @@ class HandoverSerializer(CustomModelSerializer):
raise ParseError('必须指定车间库存')
attrs['material'] = wm.material
attrs['send_dept'] = wm.belong_dept
if wm.mgroup:
attrs['send_mgroup'] = wm.mgroup
attrs['send_mgroup'] = wm.mgroup
is_global_source = wm.mgroup_id is None and wm.belong_dept_id is None
if (
is_global_source
and attrs['type'] == Handover.H_NORMAL
and mtype == Handover.H_NORMAL
):
raise ParseError('全局库存无需正常交接')
is_global_rebatch = (
is_global_source
and attrs['type'] == Handover.H_NORMAL
and mtype in [Handover.H_DIV, Handover.H_MERGE]
)
if is_global_rebatch:
# 全局库存的拆批、合批不改变库存归属。
attrs['recive_mgroup'] = None
attrs['recive_dept'] = None
if new_wm and (
new_wm.mgroup_id is not None
or new_wm.belong_dept_id is not None
):
raise ParseError('全局库存合批目标必须是全局库存')
elif (
is_global_source
and attrs['type'] in [
Handover.H_REPAIR,
Handover.H_SCRAP,
Handover.H_CHANGE,
]
and not attrs.get('recive_mgroup')
):
raise ParseError('全局库存返修、报废或改版必须指定接收工段')
material_process = attrs['material'].process
if (
material_process
not is_global_rebatch
and material_process
and WmScope.requires_mgroup(material_process.into_wm_scope)
and not attrs.get('recive_mgroup')
):
raise ParseError('必须指定交接工段')
if 'recive_mgroup' in attrs and attrs['recive_mgroup']:
attrs['recive_dept'] = attrs['recive_mgroup'].belong_dept
if not attrs.get('recive_dept', None) and not attrs.get('recive_mgroup', None):
if (
not is_global_rebatch
and not attrs.get('recive_dept', None)
and not attrs.get('recive_mgroup', None)
):
raise ParseError('必须指定收料车间或收料工段')
if not attrs.get('send_dept', None) and not attrs.get('send_mgroup', None):
if (
not is_global_source
and not attrs.get('send_dept', None)
and not attrs.get('send_mgroup', None)
):
raise ParseError('必须指定送料车间或送料工段')
# if attrs["mtype"] == Handover.H_NORMAL and attrs.get("recive_mgroup", None) == attrs.get("send_mgroup", None):

View File

@ -5,9 +5,10 @@ from django.test import SimpleTestCase
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.models import Handover, WMaterial
from apps.wpm.serializers import HandoverSerializer, WMaterialCreateSerializer
from apps.wpm.views import MlogbwViewSet
from rest_framework.exceptions import ParseError
class MlogbwViewSetTests(SimpleTestCase):
@ -139,3 +140,73 @@ class WMaterialScopeTests(SimpleTestCase):
validated = WMaterialCreateSerializer().validate(attrs)
self.assertIs(validated["belong_dept"], dept)
def test_global_inventory_cannot_enter_normal_handover(self):
wm = WMaterial(
material=Material(tracking=Material.MA_TRACKING_BATCH),
batch="GLOBAL-001",
count=1,
)
with self.assertRaisesMessage(ParseError, "全局库存无需正常交接"):
HandoverSerializer().validate({
"wm": wm,
"count": 1,
"type": Handover.H_NORMAL,
"mtype": Handover.H_NORMAL,
})
def test_global_inventory_split_stays_global(self):
wm = WMaterial(
material=Material(tracking=Material.MA_TRACKING_BATCH),
batch="GLOBAL-001",
count=1,
)
validated = HandoverSerializer().validate({
"wm": wm,
"count": 1,
"type": Handover.H_NORMAL,
"mtype": Handover.H_DIV,
})
self.assertIsNone(validated["send_dept"])
self.assertIsNone(validated["recive_dept"])
self.assertIsNone(validated["recive_mgroup"])
def test_global_inventory_cannot_merge_into_scoped_inventory(self):
material = Material(tracking=Material.MA_TRACKING_BATCH)
wm = WMaterial(material=material, batch="GLOBAL-001", count=1)
target = WMaterial(
material=material,
batch="GLOBAL-MERGED",
count=0,
belong_dept_id=20,
)
with self.assertRaisesMessage(ParseError, "全局库存合批目标必须是全局库存"):
HandoverSerializer().validate({
"wm": wm,
"count": 1,
"new_wm": target,
"type": Handover.H_NORMAL,
"mtype": Handover.H_MERGE,
})
def test_global_inventory_scrap_requires_receiving_mgroup(self):
wm = WMaterial(
material=Material(tracking=Material.MA_TRACKING_BATCH),
batch="GLOBAL-001",
count=1,
)
with self.assertRaisesMessage(
ParseError,
"全局库存返修、报废或改版必须指定接收工段",
):
HandoverSerializer().validate({
"wm": wm,
"count": 1,
"type": Handover.H_SCRAP,
"mtype": Handover.H_NORMAL,
})