diff --git a/api_v1/tests.py b/api_v1/tests.py index ce6a353..136b314 100644 --- a/api_v1/tests.py +++ b/api_v1/tests.py @@ -434,6 +434,10 @@ class SalesOrderAPITestCase(TestCase): self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertIn('id', response.data) + self.assertIn('human_id', response.data) + order = business_models.SalesOrder.objects.get(id=response.data['id']) + expected_human_id = f"{order.created_at.strftime('%Y%m%d')}{order.id:06d}" + self.assertEqual(response.data['human_id'], expected_human_id) self.assertEqual(response.data['status'], business_models.SalesOrderStatusEnum.PENDING) mock_delay.assert_not_called() @@ -485,6 +489,16 @@ class SalesOrderAPITestCase(TestCase): self.assertEqual(response.data['remarks'], '更新后的销售单') self.assertEqual(response.data['quantity_of_rolls'], [[]]) + def test_get_sales_order_detail_includes_human_id(self): + order_id = self._create_sales_order(self.strict_in_payload) + response = self.client.get(f'/api/v1/sales-orders/{order_id}/') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['id'], order_id) + self.assertIn('human_id', response.data) + order = business_models.SalesOrder.objects.get(id=order_id) + expected_human_id = f"{order.created_at.strftime('%Y%m%d')}{order.id:06d}" + self.assertEqual(response.data['human_id'], expected_human_id) + def test_update_sales_order_rejects_non_pending(self): order_id = self._create_sales_order(self.strict_in_payload) self.client.post( diff --git a/api_v1/views/business/sales/views.py b/api_v1/views/business/sales/views.py index 9c789ef..740fec6 100644 --- a/api_v1/views/business/sales/views.py +++ b/api_v1/views/business/sales/views.py @@ -24,6 +24,7 @@ class SalesOrderItemSerializer(serializers.ModelSerializer): class SalesOrderSerializer(serializers.ModelSerializer): + human_id = serializers.CharField(read_only=True) total_amount = serializers.SerializerMethodField(read_only=True) diff_quantity = serializers.SerializerMethodField(read_only=True) total_quantity = serializers.SerializerMethodField(read_only=True) @@ -49,7 +50,7 @@ class SalesOrderSerializer(serializers.ModelSerializer): class Meta: model = business_models.SalesOrder fields = [ - 'id', 'customer', 'customer_name', 'sales_date', 'kind', + 'id', 'human_id', 'customer', 'customer_name', 'sales_date', 'kind', 'total_amount', 'diff_quantity', 'total_quantity', 'operator', 'operator_name', 'warehouse', 'warehouse_name', 'status', 'remarks', 'created_at', 'updated_at', 'items', @@ -133,6 +134,7 @@ class SalesOrderView(StockChangeViewMixin, views.APIView): return Response( { 'id': sales_order.id, + 'human_id': sales_order.human_id, 'status': sales_order.status, 'message': '销售单创建成功,等待审批', }, @@ -161,6 +163,12 @@ class SalesOrderDetailView(StockChangeViewMixin, views.APIView): def patch(self, request, pk: int): return self._update(request, pk) + def get(self, request, pk: int): + sales_order, error_response = self._get_order(request, pk) + if error_response: + return error_response + return Response(SalesOrderSerializer(sales_order).data, status=status.HTTP_200_OK) + def _update(self, request, pk: int): sales_order, error_response = self._get_order(request, pk) if error_response: diff --git a/business/admin.py b/business/admin.py index 01f79f7..8ad4066 100644 --- a/business/admin.py +++ b/business/admin.py @@ -127,7 +127,7 @@ class SalesReturnOrderItemAdmin(admin.ModelAdmin): @admin.register(models.SalesOrder) class SalesOrderAdmin(admin.ModelAdmin): - list_display = ('id', 'customer', 'sales_date', 'operator', 'status', '_total_amount', '_diff_quantity', '_total_quantity', 'created_at') + list_display = ('id', 'human_id', 'customer', 'sales_date', 'operator', 'status', '_total_amount', '_diff_quantity', '_total_quantity', 'created_at') search_fields = ('customer__name',) list_filter = ('operator', 'warehouse', 'status', 'created_at') ordering = ('-created_at',) diff --git a/business/models.py b/business/models.py index 342491c..f31ba49 100644 --- a/business/models.py +++ b/business/models.py @@ -255,7 +255,19 @@ class SalesOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpa remarks = models.TextField(blank=True, null=True, verbose_name='备注') def __str__(self): - return f'销售订单 {self.id} - {self.customer.name}' + order_id = self.human_id or str(self.id) + return f'销售订单 {order_id} - {self.customer.name}' + + @property + def human_id(self) -> str: + """ + 人类可读编号(虚拟编号,不落库)。 + + 规则与 printing.PrintingOrder.human_id 保持一致:YYYYMMDD{6位id} + """ + if self.id is None or self.created_at is None: + return "" + return f"{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" class Meta: verbose_name = '销售单' diff --git a/business/tests.py b/business/tests.py index dbcbc6d..3c85d2d 100644 --- a/business/tests.py +++ b/business/tests.py @@ -389,6 +389,24 @@ class SalesOrderServiceTestCase(TestCase): self.assertEqual(item.split_quantity_of_rolls(), [6, 4]) mock_delay.assert_not_called() + def test_sales_order_human_id_generation(self): + sales_order = services.create_sales_order( + merchant=self.merchant, + customer=self.customer, + order_date=timezone.now().date(), + warehouse=self.warehouse_strict, + operator=self.operator, + items=self.strict_items, + created_by=self.user, + ) + + self.assertIsNotNone(sales_order.human_id) + self.assertTrue(len(sales_order.human_id) >= 14) + self.assertEqual( + sales_order.human_id, + f"{sales_order.created_at.strftime('%Y%m%d')}{sales_order.id:06d}", + ) + def test_review_sales_order_triggers_task(self): sales_order = services.create_sales_order( merchant=self.merchant,