From d86d70641d939ad75aefb63df9cd4c5d622a7231 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 3 Aug 2026 10:22:01 +0800 Subject: [PATCH] feat(wpm): add files to individual outputs --- apps/wpm/migrations/0138_mlogbw_files.py | 24 ++++++++++++++++ apps/wpm/models.py | 6 ++++ apps/wpm/serializers.py | 16 +++++++++-- apps/wpm/tests/__init__.py | 1 + apps/wpm/tests/test_mlogbw_files.py | 36 ++++++++++++++++++++++++ apps/wpm/views.py | 17 +++++++++++ 6 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 apps/wpm/migrations/0138_mlogbw_files.py create mode 100644 apps/wpm/tests/__init__.py create mode 100644 apps/wpm/tests/test_mlogbw_files.py diff --git a/apps/wpm/migrations/0138_mlogbw_files.py b/apps/wpm/migrations/0138_mlogbw_files.py new file mode 100644 index 00000000..4a48ed69 --- /dev/null +++ b/apps/wpm/migrations/0138_mlogbw_files.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.27 on 2026-08-03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("system", "0007_alter_dept_create_by_alter_dept_third_info_and_more"), + ("wpm", "0137_mlog_wpr_defect_snapshot"), + ] + + operations = [ + migrations.AddField( + model_name="mlogbw", + name="files", + field=models.ManyToManyField( + blank=True, + related_name="mlogbw_outputs", + to="system.file", + verbose_name="产出附件", + ), + ), + ] diff --git a/apps/wpm/models.py b/apps/wpm/models.py index c9b49681..9a3c0e51 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -740,6 +740,12 @@ class Mlogbw(BaseModel): """TN: 单个产品生产/检验日志 """ number = models.TextField('单个编号', db_index=True) + files = models.ManyToManyField( + "system.File", + verbose_name="产出附件", + blank=True, + related_name="mlogbw_outputs", + ) mlogb = models.ForeignKey(Mlogb, verbose_name='生产记录', on_delete=models.CASCADE, related_name="w_mlogb") mlogbw_from = models.ForeignKey("self", verbose_name='来源个', on_delete=models.CASCADE, null=True, blank=True, related_name="w_mlogbw_from") wpr = models.ForeignKey("wpmw.wpr", verbose_name='关联产品', on_delete=models.SET_NULL diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index cae7fe3e..fc7fa8e9 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -8,7 +8,7 @@ from .models import (SfLog, StLog, SfLogExp, WMaterial, Mlog, Handover, Handoverb, Mlogb, AttLog, OtherLog, Fmlog, BatchSt, Mlogbw, Handoverbw, MlogbDefect, MlogUser, BatchLog) from apps.system.models import Dept, User -from apps.system.serializers import UserSimpleSerializer +from apps.system.serializers import FileSerializer, UserSimpleSerializer 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.services import get_sflog, find_material_can_change, generate_new_batch @@ -923,6 +923,8 @@ class MlogbInUpdateSerializer(CustomModelSerializer): return ins class MlogbwCreateUpdateSerializer(CustomModelSerializer): + files = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + files_ = FileSerializer(source="files", many=True, read_only=True) ftest = FtestProcessSerializer(required=False, allow_null=True) equip_name = serializers.CharField(source='equip.name', read_only=True) equip_number = serializers.CharField(source='equip.number', read_only=True) @@ -936,7 +938,8 @@ class MlogbwCreateUpdateSerializer(CustomModelSerializer): fields = ["id", "number", "wpr", "note", "mlogb", "ftest", "equip", "tooling", "work_start_time", "work_end_time", "mlogbw_from", "equip_name", "equip_number", - "tooling_name", "tooling_number", "wpr_number_out", "wpr_pre_info", "mlogb__batch"] + "tooling_name", "tooling_number", "wpr_number_out", "wpr_pre_info", "mlogb__batch", + "files", "files_"] read_only_fields = ["mlogbw_from"] def validate(self, attrs): @@ -998,6 +1001,15 @@ class MlogbwCreateUpdateSerializer(CustomModelSerializer): class MlogbwListSerializer(MlogbwCreateUpdateSerializer): ftest = FtestProcessListSerializer(read_only=True) + +class MlogbwFilesSerializer(CustomModelSerializer): + files_ = FileSerializer(source="files", many=True, read_only=True) + + class Meta: + model = Mlogbw + fields = ["id", "files", "files_"] + read_only_fields = ["id", "files_"] + class MlogbwStartTestSerializer(serializers.Serializer): mlogbw_ids = serializers.ListField(child=serializers.CharField(), label="mlogbwId列表") test_equip = serializers.CharField(label="测试设备", allow_null=True, required=False, allow_blank=True) diff --git a/apps/wpm/tests/__init__.py b/apps/wpm/tests/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/apps/wpm/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/wpm/tests/test_mlogbw_files.py b/apps/wpm/tests/test_mlogbw_files.py new file mode 100644 index 00000000..8d2f39c1 --- /dev/null +++ b/apps/wpm/tests/test_mlogbw_files.py @@ -0,0 +1,36 @@ +from django.test import SimpleTestCase +from django.urls import reverse + +from apps.system.models import File +from apps.wpm.models import Mlogbw +from apps.wpm.serializers import MlogbwFilesSerializer, MlogbwListSerializer +from apps.wpm.views import MlogbwViewSet + + +class MlogbwFilesDefinitionTests(SimpleTestCase): + def test_files_field_links_system_file(self): + field = Mlogbw._meta.get_field("files") + + self.assertTrue(field.blank) + self.assertIs(field.remote_field.model, File) + self.assertEqual(field.remote_field.related_name, "mlogbw_outputs") + + def test_list_serializer_exposes_file_ids_and_details(self): + fields = MlogbwListSerializer().fields + + self.assertTrue(fields["files"].read_only) + self.assertTrue(fields["files_"].read_only) + + def test_files_serializer_only_writes_file_relations(self): + fields = MlogbwFilesSerializer().fields + + self.assertEqual(set(fields), {"id", "files", "files_"}) + self.assertFalse(fields["files"].read_only) + + def test_files_action_supports_read_and_update(self): + self.assertEqual(MlogbwViewSet.files.mapping["get"], "files") + self.assertEqual(MlogbwViewSet.files.mapping["put"], "files") + self.assertEqual( + reverse("mlogbw-files", kwargs={"pk": "single-id"}), + "/api/wpm/mlogbw/single-id/files/", + ) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index f2b4bb0c..d7f01582 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -46,6 +46,7 @@ from .serializers import ( BatchStSerializer, MlogbwCreateUpdateSerializer, MlogbwListSerializer, + MlogbwFilesSerializer, HandoverMgroupSerializer, MlogListSerializer, MlogbSerializer, @@ -1147,6 +1148,7 @@ class MlogbwViewSet(CustomModelViewSet): filterset_class = MlogbwFilter select_related_fields = ["ftest", "equip", "tooling", "wpr", "mlogb"] prefetch_related_fields = [ + "files", Prefetch( "ftest__items_ftest", queryset=FtestItem.objects.select_related( @@ -1166,6 +1168,21 @@ class MlogbwViewSet(CustomModelViewSet): ordering = ["mlogb", "number", "create_time"] ordering_fields = ["number", "create_time", "mlogb"] + @action(detail=True, methods=["get", "put"], url_path="files") + @transaction.atomic + def files(self, request, *args, **kwargs): + instance = self.get_object() + if instance.mlogb.material_out_id is None: + raise ParseError("只能给输出单件上传附件") + if request.method == "PUT": + MlogViewSet.lock_and_check_can_update(instance.mlogb.mlog) + serializer = MlogbwFilesSerializer(instance, data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + else: + serializer = MlogbwFilesSerializer(instance) + return Response(serializer.data) + def perform_create(self, serializer): ins: Mlogbw = serializer.save() mlog: Mlog = None