1
0
forked from erp-dev/erp

feat: sales_order api

This commit is contained in:
2025-11-30 17:06:48 +08:00
parent f6f0ec2ae9
commit ce1954ad58
15 changed files with 1426 additions and 44 deletions

View File

@@ -9,6 +9,7 @@ from django.contrib.auth.models import User, Permission
from rest_framework.test import APIClient
from rest_framework import status
from basic_info.models import (
Customer,
Employee,
Merchant,
MerchantTypeEnum,
@@ -240,7 +241,7 @@ class PurchaseOrderAPITestCase(TestCase):
payload['warehouse'] = self.warehouse_strict.id # 严进仓却传宽进参数
response = self.client.post('/api/v1/purchase-orders/', payload, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('严进模式', response.data['error'])
self.assertIn('numbers', response.data['error'])
def test_review_purchase_order_requires_action(self):
order_id = self._create_purchase_order(self.strict_payload)
@@ -279,6 +280,176 @@ class PurchaseOrderAPITestCase(TestCase):
self.assertEqual(order.status, business_models.PurchaseOrderStatusEnum.PENDING)
@override_settings(
CELERY_TASK_ALWAYS_EAGER=True,
CELERY_TASK_EAGER_PROPAGATES=True,
)
class SalesOrderAPITestCase(TestCase):
"""销售单 API 测试"""
def setUp(self):
self.merchant = Merchant.objects.create(name='销售商户', type=MerchantTypeEnum.FACTORY)
self.customer = Customer.objects.create(
merchant=self.merchant,
name='客户A',
mobile='13800000000',
created_by=None,
)
self.warehouse_strict = WareHouse.objects.create(
merchant=self.merchant,
name='销售严进仓',
mode=WareHouseModeEnum.RESTRICT_IN,
)
self.warehouse_relaxed = WareHouse.objects.create(
merchant=self.merchant,
name='销售宽进仓',
mode=WareHouseModeEnum.UNRESTRICTED,
)
self.warehouse_strict_out = WareHouse.objects.create(
merchant=self.merchant,
name='销售严出仓',
mode=WareHouseModeEnum.RESTRICT_IN_OUT,
)
category = ProductCategory.objects.create(
merchant=self.merchant,
name='品类',
product_prefix='SAL',
)
self.product = Product.objects.create(
merchant=self.merchant,
category=category,
name='销售产品1',
human_id='SAL-001',
unit=ProductUnitEnum.METER,
)
self.user = User.objects.create_user(username='sales_user', password='pass123')
self.employee = Employee.objects.create(
merchant=self.merchant,
sys_user=self.user,
name='销售员',
)
self.client = APIClient()
self.client.force_authenticate(user=self.user)
self.strict_in_payload = {
'customer': self.customer.id,
'warehouse': self.warehouse_strict.id,
'order_date': '2025-11-26',
'items': [
{
'product_id': self.product.id,
'numbers': [8, 4],
'price': '15.0',
'unit': '',
}
],
'remarks': '销售接口测试',
}
self.strict_out_payload = {
'customer': self.customer.id,
'warehouse': self.warehouse_strict_out.id,
'order_date': '2025-11-26',
'items': [
{
'product_id': self.product.id,
'consume_detail_ids': [101, 102],
'quantity': 30,
'price': '18.5',
'unit': '',
}
],
}
self.relaxed_payload = {
'customer': self.customer.id,
'warehouse': self.warehouse_relaxed.id,
'order_date': '2025-11-26',
'items': [
{
'product_id': self.product.id,
'quantity': 90,
'num_of_rolls': 3,
'price': '16.5',
}
],
}
def _create_sales_order(self, payload):
body = copy.deepcopy(payload)
with patch('business.services.create_sales_order_stock_entries.delay'):
response = self.client.post('/api/v1/sales-orders/', body, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
return response.data['id']
def test_create_sales_order_success_strict(self):
with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay:
response = self.client.post('/api/v1/sales-orders/', self.strict_in_payload, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertIn('id', response.data)
self.assertEqual(response.data['status'], business_models.SalesOrderStatusEnum.PENDING)
mock_delay.assert_not_called()
def test_create_sales_order_invalid_customer(self):
payload = {**self.strict_in_payload, 'customer': 999}
response = self.client.post('/api/v1/sales-orders/', payload, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('不存在', response.data['error'])
def test_create_sales_order_relaxed_mode(self):
with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay:
response = self.client.post('/api/v1/sales-orders/', self.relaxed_payload, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['status'], business_models.SalesOrderStatusEnum.PENDING)
mock_delay.assert_not_called()
def test_create_sales_order_strict_out_success(self):
with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay:
response = self.client.post('/api/v1/sales-orders/', self.strict_out_payload, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['status'], business_models.SalesOrderStatusEnum.PENDING)
mock_delay.assert_not_called()
def test_create_sales_order_strict_out_requires_consume_ids(self):
payload = copy.deepcopy(self.strict_out_payload)
payload['items'][0].pop('consume_detail_ids')
response = self.client.post('/api/v1/sales-orders/', payload, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('consume_detail_ids', response.data['error'])
def test_review_sales_order_requires_action(self):
order_id = self._create_sales_order(self.strict_in_payload)
response = self.client.post(f'/api/v1/sales-orders/{order_id}/review/', {}, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('action', response.data)
def test_review_sales_order_approve_success(self):
order_id = self._create_sales_order(self.strict_in_payload)
response = self.client.post(
f'/api/v1/sales-orders/{order_id}/review/',
{'action': 'approve'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['status'], business_models.SalesOrderStatusEnum.APPROVED)
order = business_models.SalesOrder.objects.get(id=order_id)
self.assertEqual(order.status, business_models.SalesOrderStatusEnum.APPROVED)
def test_review_sales_order_cancel_blocked_after_stock_exists(self):
order_id = self._create_sales_order(self.relaxed_payload)
stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.REMOVE,
warehouse=self.warehouse_relaxed,
source_type=stock_models.StockChangeSourceEnum.SALES,
source_id=order_id,
)
response = self.client.post(
f'/api/v1/sales-orders/{order_id}/review/',
{'action': 'cancel'},
format='json',
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
order = business_models.SalesOrder.objects.get(id=order_id)
self.assertEqual(order.status, business_models.SalesOrderStatusEnum.PENDING)
@override_settings(
CELERY_TASK_ALWAYS_EAGER=True,
CELERY_TASK_EAGER_PROPAGATES=True,