diff --git a/api_v1/views/shipment/test_api.py b/api_v1/views/shipment/test_api.py index 53df44d..8aaa4a1 100644 --- a/api_v1/views/shipment/test_api.py +++ b/api_v1/views/shipment/test_api.py @@ -1307,6 +1307,29 @@ class ShipmentQueryAPITestCase(TestCase): self.shipment1.refresh_from_db() self.assertEqual(getattr(self.shipment1, "area", ""), "更新地区") + def test_patch_shipment_rejects_non_draft(self): + """ + 非草稿状态的出货单不允许修改业务数据。 + """ + from shipment.services import modify_status + + modify_status( + self.shipment1, + target_status=shipment_models.ShipmentStatus.PUBLISHED, + operator=self.user1, + ) + + resp = self.client.patch( + f"/api/v1/shipment/shipments/{self.shipment1.id}/", + data={"area": "不允许修改"}, + format="json", + ) + self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("仅草稿状态", resp.json()["detail"]) + + self.shipment1.refresh_from_db() + self.assertEqual(self.shipment1.area, "A1") + def test_list_shipments_includes_external_finished_product_remark(self): """ Shipments list 需要附带 external_finished_products 明细数据(包含 remark 字段)。 diff --git a/api_v1/views/shipment/views.py b/api_v1/views/shipment/views.py index de79f0d..e434ea2 100644 --- a/api_v1/views/shipment/views.py +++ b/api_v1/views/shipment/views.py @@ -239,33 +239,20 @@ class ShipmentDetailView(RetrieveModelMixin, GenericAPIView): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) data = serializer.validated_data - # customer 变更需校验 merchant 一致 - if "customer" in data: - from basic_info.models import Customer + from shipment.services import update_shipment - try: - customer = Customer.objects.get(id=data["customer"]) - except Customer.DoesNotExist: - return Response( - {"detail": f"客户 {data['customer']} 不存在"}, - status=status.HTTP_400_BAD_REQUEST, - ) - if customer.merchant_id != shipment.merchant_id: - return Response( - {"detail": "无权限绑定该客户"}, status=status.HTTP_400_BAD_REQUEST - ) - shipment.customer = customer + try: + shipment = update_shipment( + shipment, + customer_id=data.get("customer"), + shipment_date=data.get("shipment_date"), + area=data.get("area"), + remark=data.get("remark"), + external_id=data.get("external_id"), + ) + except ValueError as e: + return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST) - if "shipment_date" in data: - shipment.shipment_date = data["shipment_date"] - if "area" in data: - shipment.area = (data.get("area") or "").strip() - if "remark" in data: - shipment.remark = data.get("remark") or "" - if "external_id" in data: - shipment.external_id = (data.get("external_id") or "").strip() or None - - shipment.save() return Response(ShipmentSerializer(shipment).data, status=status.HTTP_200_OK) def put(self, request, pk: int): diff --git a/docs/shipment_api.md b/docs/shipment_api.md index 9bf7138..959bd17 100644 --- a/docs/shipment_api.md +++ b/docs/shipment_api.md @@ -84,6 +84,38 @@ --- +### 更新出货单 + +- **URL**: `/api/v1/shipment/shipments//` +- **Method**: `PATCH` / `PUT` +- **认证**: 需要登录(JWT Token) + +说明: + +- 仅允许修改业务数据字段:`customer`、`shipment_date`、`area`、`remark`、`external_id` +- 该接口不允许修改 `status` +- 仅 `草稿(未发布)` 状态的出货单允许修改 +- 非草稿状态会返回 `400 Bad Request` + +请求体示例: + +```json +{ + "area": "更新地区", + "remark": "更新备注" +} +``` + +错误示例: + +```json +{ + "detail": "仅草稿状态的出货单允许修改" +} +``` + +--- + ## 创建出货单 创建出货单并关联销售品。 diff --git a/flower/settings.py b/flower/settings.py index 867d245..239d336 100644 --- a/flower/settings.py +++ b/flower/settings.py @@ -223,6 +223,7 @@ DATABASES = { 'PORT': env('DB_PORT', default='6432'), 'CONN_HEALTH_CHECK': True, 'CONN_MAX_AGE': env.int('CONN_MAX_AGE', default=0), + 'DISABLE_SERVER_SIDE_CURSORS': True, } } diff --git a/shipment/services.py b/shipment/services.py index 02bae6c..41d59a6 100644 --- a/shipment/services.py +++ b/shipment/services.py @@ -207,6 +207,49 @@ def modify_status( return shipment +@transaction.atomic +def update_shipment( + shipment: Shipment, + *, + customer_id: int | None = None, + shipment_date=None, + area: str | None = None, + remark: str | None = None, + external_id: str | None = None, +) -> Shipment: + """ + 更新出货单业务数据。 + + 说明: + - 仅允许在草稿状态下修改 + - 不处理状态变更 + """ + from basic_info.models import Customer + + if shipment.status != ShipmentStatus.DRAFT: + raise ValueError("仅草稿状态的出货单允许修改") + + if customer_id is not None: + customer = Customer.objects.filter(id=customer_id).first() + if customer is None: + raise ValueError(f"客户 {customer_id} 不存在") + if customer.merchant_id != shipment.merchant_id: + raise ValueError("无权限绑定该客户") + shipment.customer = customer + + if shipment_date is not None: + shipment.shipment_date = shipment_date + if area is not None: + shipment.area = (area or "").strip() + if remark is not None: + shipment.remark = remark or "" + if external_id is not None: + shipment.external_id = (external_id or "").strip() or None + + shipment.save() + return shipment + + @transaction.atomic def create_shipment( customer_id: int,