From 24ce008d3a16f57a8b277c0002c0d894474dae6d Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 7 Aug 2026 11:15:22 +0800 Subject: [PATCH] perf(wpm): batch load handover inventory validation --- apps/wpm/serializers.py | 40 +++++++++++++++ apps/wpm/tests/test_handover_serializer.py | 58 ++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 apps/wpm/tests/test_handover_serializer.py diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 2b360925..409269aa 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -1265,7 +1265,26 @@ class Handoverbwserializer(CustomModelSerializer): read_only_fields = EXCLUDE_FIELDS_BASE + ["handoverb", "number"] extra_kwargs = {'wpr': {'required': True}} + +class CachedWMaterialPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): + def to_internal_value(self, data): + cache = getattr(self.root, "_handover_wmaterial_cache", None) + if cache is None: + return super().to_internal_value(data) + if not isinstance(data, (str, int)): + self.fail("incorrect_type", data_type=type(data).__name__) + try: + return cache[str(data)] + except KeyError: + self.fail("does_not_exist", pk_value=data) + + class HandoverbSerializer(CustomModelSerializer): + wm = CachedWMaterialPrimaryKeyRelatedField( + queryset=WMaterial.objects.select_related( + "material", "defect", "mgroup", "belong_dept" + ) + ) notok_sign = serializers.CharField(source='wm.notok_sign', read_only=True) defect_name = serializers.CharField(source="wm.defect.name", read_only=True) handoverbw = Handoverbwserializer(many=True, required=False) @@ -1301,6 +1320,27 @@ class HandoverSerializer(CustomModelSerializer): wm_notok_sign = serializers.CharField(source='wm.notok_sign', read_only=True) handoverb = HandoverbSerializer(many=True, required=False) ticket_ = TicketSimpleSerializer(source='ticket', read_only=True) + + def to_internal_value(self, data): + handoverb = data.get("handoverb", []) if hasattr(data, "get") else [] + wm_ids = { + str(item["wm"]) + for item in handoverb + if isinstance(item, dict) and item.get("wm") is not None + } + if not wm_ids: + return super().to_internal_value(data) + + queryset = WMaterial.objects.select_related( + "material", "defect", "mgroup", "belong_dept" + ) + self._handover_wmaterial_cache = { + str(pk): instance for pk, instance in queryset.in_bulk(wm_ids).items() + } + try: + return super().to_internal_value(data) + finally: + del self._handover_wmaterial_cache def validate(self, attrs): if "mtype" not in attrs: diff --git a/apps/wpm/tests/test_handover_serializer.py b/apps/wpm/tests/test_handover_serializer.py new file mode 100644 index 00000000..cd73a488 --- /dev/null +++ b/apps/wpm/tests/test_handover_serializer.py @@ -0,0 +1,58 @@ +from django.db import connection +from django.test import TestCase +from django.test.utils import CaptureQueriesContext + +from apps.mtm.models import Material +from apps.qm.models import Defect +from apps.system.models import Dept, User +from apps.wpm.models import Handover, WMaterial +from apps.wpm.serializers import HandoverSerializer + + +class HandoverSerializerQueryTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.dept = Dept.objects.create(name="合批查询测试车间") + cls.user = User.objects.create_user(username="handover-query-user") + cls.material = Material.objects.create(name="合批查询测试物料") + cls.defect_b = Defect.objects.create( + name="合批查询测试B类缺陷", + cate=Defect.cate_list[0], + okcate=Defect.DEFECT_OK_B, + ) + cls.inventories = [ + WMaterial.objects.create( + material=cls.material, + batch=f"QUERY-{index}", + count=1, + state=WMaterial.WM_OK, + defect=cls.defect_b if index == 2 else None, + belong_dept=cls.dept, + ) + for index in range(3) + ] + + def test_handover_inventory_is_loaded_in_one_query(self): + serializer = HandoverSerializer(data={ + "send_date": "2026-08-07", + "send_user": self.user.id, + "send_dept": self.dept.id, + "recive_dept": self.dept.id, + "handoverb": [ + {"wm": inventory.id, "count": 1} + for inventory in self.inventories + ], + "new_batch": "QUERY-MERGED", + "clear_defect": True, + "type": Handover.H_NORMAL, + "mtype": Handover.H_MERGE, + }) + + with CaptureQueriesContext(connection) as queries: + self.assertTrue(serializer.is_valid(), serializer.errors) + + inventory_queries = [ + query["sql"] for query in queries + if 'FROM "wpm_wmaterial"' in query["sql"] + ] + self.assertEqual(len(inventory_queries), 1, inventory_queries)