From a8c61667a9bfa35fe8bd92519cc1706fe53db062 Mon Sep 17 00:00:00 2001 From: colaftc Date: Wed, 4 Feb 2026 20:14:59 +0800 Subject: [PATCH] feat: field pre_purchase_order_id for purchase_order (allow null) --- api_v1/tests.py | 26 +++++++++++++++++++ api_v1/views/business/purchase/views.py | 4 +++ .../0027_purchaseorder_from_pre_purchase.py | 16 ++++++++++++ business/models.py | 5 ++++ business/services.py | 14 +++++++++- flower/settings.py | 20 +++++++------- 6 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 business/migrations/0027_purchaseorder_from_pre_purchase.py diff --git a/api_v1/tests.py b/api_v1/tests.py index b93b7ed..67a4f2c 100644 --- a/api_v1/tests.py +++ b/api_v1/tests.py @@ -230,6 +230,22 @@ class PurchaseOrderAPITestCase(TestCase): self.assertEqual(response.data['human_id'], expected_human_id) mock_delay.assert_not_called() + def test_create_purchase_order_with_pre_purchase_binding(self): + pre_order = business_models.PrePurchaseOrder.objects.create( + merchant=self.merchant, + supplier=self.supplier, + warehouse=self.warehouse_strict, + operator=self.employee, + created_by=self.user, + ) + payload = {**self.strict_payload, 'from_pre_purchase_order_id': pre_order.id} + with patch('business.services.create_purchase_order_stock_entries.delay'): + response = self.client.post('/api/v1/purchase-orders/', payload, format='json') + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + order = business_models.PurchaseOrder.objects.get(id=response.data['id']) + self.assertEqual(order.from_pre_purchase_order_id, pre_order.id) + def test_bind_purchase_order_stock_change_record(self): MerchantSetting.objects.filter( merchant=self.merchant, @@ -321,6 +337,13 @@ class PurchaseOrderAPITestCase(TestCase): def test_update_purchase_order_success(self): order_id = self._create_purchase_order(self.strict_payload) + pre_order = business_models.PrePurchaseOrder.objects.create( + merchant=self.merchant, + supplier=self.supplier, + warehouse=self.warehouse_relaxed, + operator=self.employee, + created_by=self.user, + ) payload = { 'supplier': self.supplier.id, 'warehouse': self.warehouse_relaxed.id, @@ -334,10 +357,13 @@ class PurchaseOrderAPITestCase(TestCase): } ], 'remarks': '更新采购单', + 'from_pre_purchase_order_id': pre_order.id, } response = self.client.put(f'/api/v1/purchase-orders/{order_id}/', payload, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['remarks'], '更新采购单') + updated_order = business_models.PurchaseOrder.objects.get(id=order_id) + self.assertEqual(updated_order.from_pre_purchase_order_id, pre_order.id) def test_update_purchase_order_rejects_non_pending(self): order_id = self._create_purchase_order(self.relaxed_payload) diff --git a/api_v1/views/business/purchase/views.py b/api_v1/views/business/purchase/views.py index 5d665b8..44955a2 100644 --- a/api_v1/views/business/purchase/views.py +++ b/api_v1/views/business/purchase/views.py @@ -95,6 +95,7 @@ class PurchaseOrderView(StockChangeViewMixin, views.APIView): order_date = data.get('order_date') items = data.get('items', []) remarks = data.get('remarks', '') + from_pre_purchase_order_id = data.get('from_pre_purchase_order_id') if not supplier_id: return Response({'error': '缺少供应商 ID'}, status=status.HTTP_400_BAD_REQUEST) @@ -126,6 +127,7 @@ class PurchaseOrderView(StockChangeViewMixin, views.APIView): operator=operator, items=items, remarks=remarks, + from_pre_purchase_order_id=from_pre_purchase_order_id, created_by=request.user, ) except ValueError as exc: @@ -192,6 +194,7 @@ class PurchaseOrderDetailView(StockChangeViewMixin, views.APIView): if not isinstance(items, list) or not items: return Response({'error': 'items 需要为非空数组'}, status=status.HTTP_400_BAD_REQUEST) remarks = data.get('remarks', purchase_order.remarks) + from_pre_purchase_order_id = data.get('from_pre_purchase_order_id') try: updated_order = business_services.update_purchase_order( @@ -201,6 +204,7 @@ class PurchaseOrderDetailView(StockChangeViewMixin, views.APIView): warehouse=warehouse, items=items, remarks=remarks, + from_pre_purchase_order_id=from_pre_purchase_order_id, ) except ValueError as exc: return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST) diff --git a/business/migrations/0027_purchaseorder_from_pre_purchase.py b/business/migrations/0027_purchaseorder_from_pre_purchase.py new file mode 100644 index 0000000..7d2ca50 --- /dev/null +++ b/business/migrations/0027_purchaseorder_from_pre_purchase.py @@ -0,0 +1,16 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('business', '0026_salesorder_from_pre_sales_order'), + ] + + operations = [ + migrations.AddField( + model_name='purchaseorder', + name='from_pre_purchase_order_id', + field=models.BigIntegerField(blank=True, null=True, verbose_name='关联预采购单ID'), + ), + ] diff --git a/business/models.py b/business/models.py index 8eb18a5..617f6a2 100644 --- a/business/models.py +++ b/business/models.py @@ -130,6 +130,11 @@ class PurchaseOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounte default=PurchaseOrderStatusEnum.PENDING, verbose_name='状态', ) + from_pre_purchase_order_id = models.BigIntegerField( + null=True, + blank=True, + verbose_name='关联预采购单ID', + ) remarks = models.TextField(blank=True, null=True, verbose_name='备注') def __str__(self): diff --git a/business/services.py b/business/services.py index d0ee3d4..2c9d4b8 100644 --- a/business/services.py +++ b/business/services.py @@ -181,6 +181,7 @@ def create_purchase_order( items: List[Dict[str, Any]], remarks: str | None = '', created_by=None, + from_pre_purchase_order_id: int | None = None, ) -> models.PurchaseOrder: """ 创建采购订单并触发异步创建入库单任务。 @@ -214,6 +215,7 @@ def create_purchase_order( operator=operator, warehouse=warehouse, remarks=remarks, + from_pre_purchase_order_id=from_pre_purchase_order_id, ) bulk_objects = [ models.PurchaseOrderItem( @@ -246,6 +248,7 @@ def update_purchase_order( warehouse: basic_info_models.WareHouse | None = None, items: List[Dict[str, Any]] | None = None, remarks: str | None = '', + from_pre_purchase_order_id: int | None = None, ) -> models.PurchaseOrder: """ 更新采购订单(仅限审批中状态)。 @@ -273,8 +276,17 @@ def update_purchase_order( purchase_order.purchase_date = new_order_date purchase_order.warehouse = new_warehouse purchase_order.remarks = remarks + if from_pre_purchase_order_id is not None: + purchase_order.from_pre_purchase_order_id = from_pre_purchase_order_id purchase_order.save( - update_fields=['supplier', 'purchase_date', 'warehouse', 'remarks', 'updated_at'] + update_fields=[ + 'supplier', + 'purchase_date', + 'warehouse', + 'remarks', + 'from_pre_purchase_order_id', + 'updated_at', + ] ) purchase_order.items.all().delete() diff --git a/flower/settings.py b/flower/settings.py index 7ab3fcf..a1ecde2 100644 --- a/flower/settings.py +++ b/flower/settings.py @@ -586,16 +586,16 @@ CELERY_BEAT_SCHEDULE = { if MDY_ISOLATED_MERCHANT_ID and MDY_ISOLATED_MERCHANT_ID > 0: CELERY_BEAT_SCHEDULE.update( { - 'mdy_product_sync_isolated': { - 'task': 'api_v1.tasks.sync_mdy_products_for_merchant', - 'schedule': crontab(minute='3,13,23,33,43,53'), - 'kwargs': { - 'merchant_id': MDY_ISOLATED_MERCHANT_ID, - 'page_size': MDY_SYNC_PAGE_SIZE, - 'max_pages': MDY_SYNC_MAX_PAGES, - 'max_records': MDY_SYNC_MAX_RECORDS, - }, - }, + # 'mdy_product_sync_isolated': { + # 'task': 'api_v1.tasks.sync_mdy_products_for_merchant', + # 'schedule': crontab(minute='3,13,23,33,43,53'), + # 'kwargs': { + # 'merchant_id': MDY_ISOLATED_MERCHANT_ID, + # 'page_size': MDY_SYNC_PAGE_SIZE, + # 'max_pages': MDY_SYNC_MAX_PAGES, + # 'max_records': MDY_SYNC_MAX_RECORDS, + # }, + # }, # 'mdy_customer_sync_isolated': { # 'task': 'api_v1.tasks.sync_mdy_customers_for_merchant', # 'schedule': crontab(minute='1,11,21,31,41,51'),