1
0
forked from erp-dev/erp

feat: red flush

This commit is contained in:
2026-06-12 13:57:21 +08:00
parent 80db7867f4
commit 0bcaa2db44
33 changed files with 2997 additions and 17 deletions

View File

@@ -1231,6 +1231,332 @@ class ReceiptOrderAPITestCase(TestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('银行账户不存在', response.data['error'])
class BusinessRedFlushAPITestCase(TestCase):
def setUp(self):
self.merchant = Merchant.objects.create(name='红冲商户', type=MerchantTypeEnum.FACTORY)
self.other_merchant = Merchant.objects.create(name='其他红冲商户', type=MerchantTypeEnum.FACTORY)
self.supplier = Supplier.objects.create(merchant=self.merchant, name='红冲供应商')
self.other_supplier = Supplier.objects.create(merchant=self.other_merchant, name='其他供应商')
self.customer = Customer.objects.create(
merchant=self.merchant,
name='红冲客户',
mobile='13800001111',
created_by=None,
)
self.warehouse = WareHouse.objects.create(
merchant=self.merchant,
name='红冲仓',
mode=WareHouseModeEnum.UNRESTRICTED,
)
category = ProductCategory.objects.create(
merchant=self.merchant,
name='红冲品类',
product_prefix='RF',
)
self.product = Product.objects.create(
merchant=self.merchant,
category=category,
name='红冲产品',
human_id='RF-001',
unit=ProductUnitEnum.METER,
)
self.user = User.objects.create_user(username='red_flush_api_user', password='pass123')
self.employee = Employee.objects.create(
merchant=self.merchant,
sys_user=self.user,
name='红冲操作员',
status=EmployeeStatusEnum.ACTIVE,
)
self.other_user = User.objects.create_user(username='other_red_flush_api_user', password='pass123')
self.other_employee = Employee.objects.create(
merchant=self.other_merchant,
sys_user=self.other_user,
name='其他红冲操作员',
status=EmployeeStatusEnum.ACTIVE,
)
self.client = APIClient()
self.client.force_authenticate(user=self.user)
MerchantSetting.objects.filter(
merchant=self.merchant,
key=MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS,
).update(val_bool=False)
def _items(self, quantity='10', price='5'):
return [
{
'product_id': self.product.id,
'quantity': quantity,
'num_of_rolls': 1,
'price': price,
}
]
def _complete_stock_record(self, record_id):
record = stock_models.StockChangeRecord.objects.get(id=record_id)
stock_services.make_stock_change_completed(record)
def _create_approved_purchase_order(self):
order = services.create_purchase_order(
merchant=self.merchant,
supplier=self.supplier,
order_date=datetime.date(2025, 11, 26),
warehouse=self.warehouse,
operator=self.employee,
items=self._items(),
)
services.review_purchase_order(
purchase_order=order,
target_status=business_models.PurchaseOrderStatusEnum.APPROVED,
reviewed_by=self.user,
)
payload = services.create_purchase_order_stock_entries_sync(
purchase_order_id=order.id,
warehouse_id=self.warehouse.id,
items=services._build_stock_flow_items_from_order(order),
created_by_id=self.user.id,
)
self._complete_stock_record(payload['stock_change_record_id'])
return order
def _create_approved_sales_order(self):
order = services.create_sales_order(
merchant=self.merchant,
customer=self.customer,
order_date=datetime.date(2025, 11, 26),
warehouse=self.warehouse,
operator=self.employee,
items=self._items(quantity='8', price='7'),
)
services.review_sales_order(
sales_order=order,
target_status=business_models.SalesOrderStatusEnum.APPROVED,
reviewed_by=self.user,
)
payload = services.create_sales_order_stock_entries_sync(
sales_order_id=order.id,
warehouse_id=self.warehouse.id,
items=services._build_stock_flow_items_from_order(order),
created_by_id=self.user.id,
)
self._complete_stock_record(payload['stock_change_record_id'])
return order
def _create_approved_purchase_return_order(self):
order = services.create_purchase_return_order(
merchant=self.merchant,
supplier=self.supplier,
return_date=datetime.date(2025, 11, 26),
warehouse=self.warehouse,
operator=self.employee,
items=self._items(quantity='6', price='4'),
)
services.review_purchase_return_order(
purchase_return_order=order,
target_status=business_models.PurchaseReturnStatusEnum.APPROVED,
reviewed_by=self.user,
)
payload = services.create_purchase_return_order_stock_entries_sync(
purchase_return_order_id=order.id,
warehouse_id=self.warehouse.id,
items=services._build_stock_flow_items_from_order(order),
created_by_id=self.user.id,
)
self._complete_stock_record(payload['stock_change_record_id'])
return order
def _create_approved_sales_return_order(self):
order = services.create_sales_return_order(
merchant=self.merchant,
customer=self.customer,
return_date=datetime.date(2025, 11, 26),
warehouse=self.warehouse,
operator=self.employee,
items=self._items(quantity='6', price='4'),
)
services.review_sales_return_order(
sales_return_order=order,
target_status=business_models.SalesReturnStatusEnum.APPROVED,
reviewed_by=self.user,
)
payload = services.create_sales_return_order_stock_entries_sync(
sales_return_order_id=order.id,
warehouse_id=self.warehouse.id,
items=services._build_stock_flow_items_from_order(order),
created_by_id=self.user.id,
)
self._complete_stock_record(payload['stock_change_record_id'])
return order
def _create_approved_payment_order(self):
order = services.create_payment_order(
merchant=self.merchant,
supplier=self.supplier,
payment_date=datetime.date(2025, 11, 26),
amount='30',
operator=self.employee,
)
services.review_payment_order(
payment_order=order,
target_status=business_models.PaymentOrderStatusEnum.APPROVED,
reviewed_by=self.user,
)
return order
def _create_approved_receipt_order(self):
order = services.create_receipt_order(
merchant=self.merchant,
customer=self.customer,
receipt_date=datetime.date(2025, 11, 26),
amount='30',
operator=self.employee,
)
services.review_receipt_order(
receipt_order=order,
target_status=business_models.ReceiptOrderStatusEnum.APPROVED,
reviewed_by=self.user,
)
return order
def test_purchase_order_red_flush_requires_reason(self):
order = self._create_approved_purchase_order()
response = self.client.post(f'/api/v1/purchase-orders/{order.id}/red-flush/', {}, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('reason', response.data)
def test_purchase_order_red_flush_success(self):
order = self._create_approved_purchase_order()
response = self.client.post(
f'/api/v1/purchase-orders/{order.id}/red-flush/',
{'reason': '采购接口红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['is_red_flushed'])
self.assertIsNotNone(response.data['red_flush_id'])
order.refresh_from_db()
self.assertEqual(order.status, business_models.PurchaseOrderStatusEnum.APPROVED)
self.assertTrue(order.is_red_flushed)
def test_sales_order_red_flush_success(self):
order = self._create_approved_sales_order()
response = self.client.post(
f'/api/v1/sales-orders/{order.id}/red-flush/',
{'reason': '销售接口红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['is_red_flushed'])
def test_purchase_return_order_red_flush_success(self):
order = self._create_approved_purchase_return_order()
response = self.client.post(
f'/api/v1/purchase-return-orders/{order.id}/red-flush/',
{'reason': '采购退货接口红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['is_red_flushed'])
def test_sales_return_order_red_flush_success(self):
order = self._create_approved_sales_return_order()
response = self.client.post(
f'/api/v1/sales-return-orders/{order.id}/red-flush/',
{'reason': '销售退货接口红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['is_red_flushed'])
def test_payment_order_red_flush_success_and_duplicate_blocked(self):
order = self._create_approved_payment_order()
response = self.client.post(
f'/api/v1/payment-orders/{order.id}/red-flush/',
{'reason': '付款接口红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['is_red_flushed'])
duplicate = self.client.post(
f'/api/v1/payment-orders/{order.id}/red-flush/',
{'reason': '重复红冲'},
format='json',
)
self.assertEqual(duplicate.status_code, status.HTTP_400_BAD_REQUEST)
def test_receipt_order_red_flush_success(self):
order = self._create_approved_receipt_order()
response = self.client.post(
f'/api/v1/receipt-orders/{order.id}/red-flush/',
{'reason': '收款接口红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['is_red_flushed'])
def test_red_flush_rejects_external_payment_order(self):
order = self._create_approved_payment_order()
order.is_external_source = True
order.external_source_id = 'XT-API-EXTERNAL-001'
order.save(update_fields=['is_external_source', 'external_source_id', 'updated_at'])
response = self.client.post(
f'/api/v1/payment-orders/{order.id}/red-flush/',
{'reason': '外部付款红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('外部来源付款单不允许红冲', response.data['error'])
order.refresh_from_db()
self.assertFalse(order.is_red_flushed)
def test_red_flush_rejects_external_receipt_order(self):
order = self._create_approved_receipt_order()
order.external_source_id = 'SK-API-EXTERNAL-001'
order.save(update_fields=['external_source_id', 'updated_at'])
response = self.client.post(
f'/api/v1/receipt-orders/{order.id}/red-flush/',
{'reason': '外部收款红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('外部来源收款单不允许红冲', response.data['error'])
order.refresh_from_db()
self.assertFalse(order.is_red_flushed)
def test_red_flush_rejects_non_current_merchant_order(self):
other_order = services.create_payment_order(
merchant=self.other_merchant,
supplier=self.other_supplier,
payment_date=datetime.date(2025, 11, 26),
amount='20',
operator=self.other_employee,
)
response = self.client.post(
f'/api/v1/payment-orders/{other_order.id}/red-flush/',
{'reason': '跨商户红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_red_flush_rejects_non_approved_order(self):
order = services.create_payment_order(
merchant=self.merchant,
supplier=self.supplier,
payment_date=datetime.date(2025, 11, 26),
amount='20',
operator=self.employee,
)
response = self.client.post(
f'/api/v1/payment-orders/{order.id}/red-flush/',
{'reason': '未审核红冲'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
class CustomerBalanceAPITestCase(TestCase):
def setUp(self):
self.merchant = Merchant.objects.create(name='余额商户', type=MerchantTypeEnum.FACTORY)