From 554a2c423442c8d26bd56ef86592a9d9b31bcd07 Mon Sep 17 00:00:00 2001 From: colaftc Date: Thu, 15 Jan 2026 20:52:51 +0800 Subject: [PATCH] feat: added remark field into shipment module --- api_v1/views/shipment/serializers.py | 2 ++ api_v1/views/shipment/test_api.py | 28 ++++++++++++++++++- docs/2026-01-15_summary.md | 1 + docs/shipment_api.md | 3 +- ...add_remark_to_external_finished_product.py | 18 ++++++++++++ shipment/models.py | 6 ++++ shipment/services.py | 1 + 7 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 shipment/migrations/0007_add_remark_to_external_finished_product.py diff --git a/api_v1/views/shipment/serializers.py b/api_v1/views/shipment/serializers.py index 7938cde..292be57 100644 --- a/api_v1/views/shipment/serializers.py +++ b/api_v1/views/shipment/serializers.py @@ -92,6 +92,7 @@ class ExternalFinishedProductSerializer(serializers.ModelSerializer): 'id', 'style_name', 'num_of_rolls', + 'remark', 'created_at', 'created_by_id', 'created_by_name', @@ -129,6 +130,7 @@ class ExternalFinishedProductInputSerializer(serializers.Serializer): """外部成品表写入结构(external create 专用)""" style_name = serializers.CharField(max_length=200) num_of_rolls = serializers.IntegerField(min_value=0) + remark = serializers.CharField(max_length=200, required=False, allow_blank=True, allow_null=True, default='') class ShipmentCreateExternalSerializer(serializers.Serializer): diff --git a/api_v1/views/shipment/test_api.py b/api_v1/views/shipment/test_api.py index 5d3b46f..836eb44 100644 --- a/api_v1/views/shipment/test_api.py +++ b/api_v1/views/shipment/test_api.py @@ -491,7 +491,7 @@ class ShipmentExternalCreateAPITestCase(TestCase): 'remark': 'external 备注', 'external_id': 'EXT-ORDER-001', 'external_finished_products': [ - {'style_name': '款式A', 'num_of_rolls': 2}, + {'style_name': '款式A', 'num_of_rolls': 2, 'remark': 'A备注'}, {'style_name': '款式B', 'num_of_rolls': 5}, ] } @@ -512,6 +512,9 @@ class ShipmentExternalCreateAPITestCase(TestCase): shipment_models.ExternalFinishedProduct.objects.filter(shipment_id=shipment_id).count(), 2 ) + efp_a = shipment_models.ExternalFinishedProduct.objects.filter(shipment_id=shipment_id, style_name='款式A').first() + self.assertIsNotNone(efp_a) + self.assertEqual(getattr(efp_a, 'remark', None) or '', 'A备注') # 验证不会绑定任何销售品 self.sales_item.refresh_from_db() @@ -655,6 +658,29 @@ class ShipmentQueryAPITestCase(TestCase): self.assertIn('external_finished_products', result) self.assertIsInstance(result['external_finished_products'], list) + def test_list_shipments_includes_external_finished_product_remark(self): + """ + Shipments list 需要附带 external_finished_products 明细数据(包含 remark 字段)。 + """ + # 给 shipment1 挂一个外部成品表 + shipment_models.ExternalFinishedProduct.objects.create( + shipment=self.shipment1, + style_name='款式X', + num_of_rolls=1, + remark='X备注', + created_by=self.user1, + ) + + resp = self.client.get('/api/v1/shipment/shipments/') + self.assertEqual(resp.status_code, status.HTTP_200_OK) + data = resp.json() + item = next(it for it in data['results'] if it['id'] == self.shipment1.id) + efps = item['external_finished_products'] + self.assertIsInstance(efps, list) + target = next(p for p in efps if p['style_name'] == '款式X') + self.assertIn('remark', target) + self.assertEqual(target['remark'], 'X备注') + def test_retrieve_other_merchant_shipment_404(self): resp = self.client.get(f'/api/v1/shipment/shipments/{self.shipment2.id}/') self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND) diff --git a/docs/2026-01-15_summary.md b/docs/2026-01-15_summary.md index 0a7862c..6452d4b 100644 --- a/docs/2026-01-15_summary.md +++ b/docs/2026-01-15_summary.md @@ -41,5 +41,6 @@ - `GET /api/v1/shipment/shipments/`:出货单列表(merchant 隔离 + LimitOffsetPagination) - `GET /api/v1/shipment/shipments//`:出货单详情(merchant 隔离) - 出货单列表序列化器增强:`sales_items` 与 `external_finished_products` 字段稳定输出(无数据也返回空数组) +- ExternalFinishedProduct 增加 `remark` 字段(可空),并同步更新相关序列化器/文档/测试(包含 shipments list 附带数据) - 更新 `docs/shipment_api.md`:补充“查询出货单(列表/详情)”说明 - 新增 API 测试:覆盖列表/详情的商户隔离与未登录行为 diff --git a/docs/shipment_api.md b/docs/shipment_api.md index 5456f32..8595cb0 100644 --- a/docs/shipment_api.md +++ b/docs/shipment_api.md @@ -190,6 +190,7 @@ |------|------|------|------| | style_name | string | 是 | 款式名称 | | num_of_rolls | int | 是 | 卷数 | +| remark | string | 否 | 备注(可空) | ### 请求示例 @@ -200,7 +201,7 @@ "remark": "external 备注(可选)", "external_id": "EXT-ORDER-001", "external_finished_products": [ - {"style_name": "款式A", "num_of_rolls": 2}, + {"style_name": "款式A", "num_of_rolls": 2, "remark": "A备注"}, {"style_name": "款式B", "num_of_rolls": 5} ] } diff --git a/shipment/migrations/0007_add_remark_to_external_finished_product.py b/shipment/migrations/0007_add_remark_to_external_finished_product.py new file mode 100644 index 0000000..47f3547 --- /dev/null +++ b/shipment/migrations/0007_add_remark_to_external_finished_product.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2026-01-15 12:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shipment', '0006_add_merchant_to_shipment_and_salesitem'), + ] + + operations = [ + migrations.AddField( + model_name='externalfinishedproduct', + name='remark', + field=models.CharField(blank=True, max_length=200, null=True, verbose_name='备注'), + ), + ] diff --git a/shipment/models.py b/shipment/models.py index ecff951..73099e7 100644 --- a/shipment/models.py +++ b/shipment/models.py @@ -41,6 +41,12 @@ class ExternalFinishedProduct(ModelBase): num_of_rolls = models.PositiveIntegerField( verbose_name='卷数', ) + remark = models.CharField( + max_length=200, + null=True, + blank=True, + verbose_name='备注', + ) created_by = models.ForeignKey( User, on_delete=models.SET_NULL, diff --git a/shipment/services.py b/shipment/services.py index 59c397f..596e0fd 100644 --- a/shipment/services.py +++ b/shipment/services.py @@ -171,6 +171,7 @@ def create_external_shipment( shipment=shipment, style_name=item.get('style_name', ''), num_of_rolls=item.get('num_of_rolls', 0), + remark=item.get('remark') or '', created_by=created_by, ))