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

@@ -55,6 +55,58 @@ def create_basic_fixtures():
return merchant, supplier, warehouse_strict, warehouse_relaxed, product, operator
def create_sales_fixtures():
merchant = basic_models.Merchant.objects.create(
name='销售商户',
type=basic_models.MerchantTypeEnum.FACTORY,
)
basic_models.MerchantSetting.objects.create(
merchant=merchant,
key=basic_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS,
type=basic_models.MerchantSettingTypeEnum.BOOL,
val_bool=True,
)
customer = basic_models.Customer.objects.create(
merchant=merchant,
name='客户A',
mobile='13800000000',
created_by=None,
)
warehouse_strict = basic_models.WareHouse.objects.create(
merchant=merchant,
name='销售严进仓',
mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
)
warehouse_relaxed = basic_models.WareHouse.objects.create(
merchant=merchant,
name='销售宽进仓',
mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
)
warehouse_strict_out = basic_models.WareHouse.objects.create(
merchant=merchant,
name='销售严出仓',
mode=basic_models.WareHouseModeEnum.RESTRICT_IN_OUT,
)
category = basic_models.ProductCategory.objects.create(
merchant=merchant,
name='销售品类',
product_prefix='SAL',
)
product = basic_models.Product.objects.create(
merchant=merchant,
category=category,
name='销售面料',
human_id='SAL-001',
unit=basic_models.ProductUnitEnum.METER,
)
operator = basic_models.Employee.objects.create(
merchant=merchant,
name='销售员',
status=basic_models.EmployeeStatusEnum.ACTIVE,
)
return merchant, customer, warehouse_strict, warehouse_relaxed, warehouse_strict_out, product, operator
class PurchaseOrderServiceTestCase(TestCase):
def setUp(self):
(
@@ -201,6 +253,128 @@ class PurchaseOrderServiceTestCase(TestCase):
)
class SalesOrderServiceTestCase(TestCase):
def setUp(self):
(
self.merchant,
self.customer,
self.warehouse_strict,
self.warehouse_relaxed,
self.warehouse_strict_out,
self.product,
self.operator,
) = create_sales_fixtures()
User = get_user_model()
self.user = User.objects.create_user(username='sales-creator', password='pass123')
self.strict_items = [
{'product_id': self.product.id, 'numbers': [6, 4], 'price': '30.5', 'unit': ''}
]
self.relaxed_items = [
{'product_id': self.product.id, 'quantity': 80, 'num_of_rolls': 2, 'price': '28.0', 'unit': ''}
]
self.strict_out_items = [
{
'product_id': self.product.id,
'consume_detail_ids': [1, 2],
'quantity': 40,
'price': '32.0',
'unit': '',
}
]
def test_create_sales_order_success(self):
with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay:
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,
remarks='销售测试',
created_by=self.user,
)
self.assertIsInstance(sales_order, business_models.SalesOrder)
self.assertEqual(sales_order.status, business_models.SalesOrderStatusEnum.PENDING)
self.assertEqual(sales_order.items.count(), 1)
self.assertEqual(sales_order.items.first().quantity_of_rolls, '6,4')
mock_delay.assert_not_called()
def test_review_sales_order_triggers_task(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,
)
with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay:
reviewed = services.review_sales_order(
sales_order=sales_order,
target_status=business_models.SalesOrderStatusEnum.APPROVED,
reviewed_by=self.user,
)
self.assertEqual(reviewed.status, business_models.SalesOrderStatusEnum.APPROVED)
mock_delay.assert_called_once_with(
sales_order_id=sales_order.id,
warehouse_id=self.warehouse_strict.id,
items=[{'product_id': self.product.id, 'quantities': ['6', '4']}],
created_by_id=self.user.id,
)
def test_sales_order_cancel_blocked_after_stock_created(self):
sales_order = services.create_sales_order(
merchant=self.merchant,
customer=self.customer,
order_date=timezone.now().date(),
warehouse=self.warehouse_relaxed,
operator=self.operator,
items=self.relaxed_items,
created_by=self.user,
)
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=sales_order.id,
)
with self.assertRaises(ValueError):
services.review_sales_order(
sales_order=sales_order,
target_status=business_models.SalesOrderStatusEnum.CANCELLED,
reviewed_by=self.user,
)
def test_sales_order_requires_consume_ids_for_strict_out(self):
with self.assertRaises(ValueError):
services.create_sales_order(
merchant=self.merchant,
customer=self.customer,
order_date=timezone.now().date(),
warehouse=self.warehouse_strict_out,
operator=self.operator,
items=[{'product_id': self.product.id, 'quantity': 10, 'price': '20', 'unit': ''}],
created_by=self.user,
)
def test_sales_order_stores_consume_ids(self):
sales_order = services.create_sales_order(
merchant=self.merchant,
customer=self.customer,
order_date=timezone.now().date(),
warehouse=self.warehouse_strict_out,
operator=self.operator,
items=self.strict_out_items,
created_by=self.user,
)
item = sales_order.items.first()
self.assertEqual(item.consume_detail_ids, '1,2')
class PurchaseOrderStockServiceTestCase(TestCase):
def setUp(self):
(
@@ -245,6 +419,51 @@ class PurchaseOrderStockServiceTestCase(TestCase):
self.assertEqual(payload['stock_change_record_id'], 321)
class SalesOrderStockServiceTestCase(TestCase):
def setUp(self):
(
self.merchant,
self.customer,
self.warehouse_strict,
self.warehouse_relaxed,
self.warehouse_strict_out,
self.product,
self.operator,
) = create_sales_fixtures()
User = get_user_model()
self.user = User.objects.create_user(username='sales-svc', password='pass123')
self.sales_order = business_models.SalesOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
sales_date=timezone.now().date(),
operator=self.operator,
warehouse=self.warehouse_strict,
)
self.items = [{'product_id': self.product.id, 'quantities': ['5']}]
def test_service_calls_stock_out(self):
with patch('business.services.StockFlowService') as mock_flow_cls:
mock_instance = mock_flow_cls.return_value
mock_instance.stock_out.return_value = (MagicMock(id=654), [], 1)
payload = services.create_sales_order_stock_entries_sync(
sales_order_id=self.sales_order.id,
warehouse_id=self.warehouse_strict.id,
items=self.items,
created_by_id=self.user.id,
)
mock_flow_cls.assert_called_once_with(merchant=self.merchant, created_by=self.user)
mock_instance.stock_out.assert_called_once_with(
warehouse_id=self.warehouse_strict.id,
source_type=stock_models.StockChangeSourceEnum.SALES,
source_id=self.sales_order.id,
items=[{'product_id': self.product.id, 'quantities': ['5']}],
)
self.assertEqual(payload['sales_order_id'], self.sales_order.id)
self.assertEqual(payload['stock_change_record_id'], 654)
@override_settings(
CELERY_TASK_ALWAYS_EAGER=True,
CELERY_TASK_EAGER_PROPAGATES=True,
@@ -275,6 +494,35 @@ class PurchaseOrderStockTaskTestCase(TestCase):
)
self.assertEqual(payload['purchase_order_id'], self.purchase_order_id)
self.assertIn('task_id', payload)
from django.test import TestCase
# Create your tests here.
@override_settings(
CELERY_TASK_ALWAYS_EAGER=True,
CELERY_TASK_EAGER_PROPAGATES=True,
)
class SalesOrderStockTaskTestCase(TestCase):
def setUp(self):
self.sales_order_id = 321
self.warehouse_id = 654
self.items = [{'product_id': 2, 'quantities': ['4']}]
def test_task_delegates_to_service(self):
with patch('business.tasks.business_services.create_sales_order_stock_entries_sync') as mock_sync:
mock_sync.return_value = {'sales_order_id': self.sales_order_id}
async_result = tasks.create_sales_order_stock_entries.delay(
sales_order_id=self.sales_order_id,
warehouse_id=self.warehouse_id,
items=self.items,
created_by_id=777,
)
payload = async_result.get(timeout=5)
mock_sync.assert_called_once_with(
sales_order_id=self.sales_order_id,
warehouse_id=self.warehouse_id,
items=self.items,
created_by_id=777,
)
self.assertEqual(payload['sales_order_id'], self.sales_order_id)
self.assertIn('task_id', payload)