1
0
forked from erp-dev/erp

feat: sales return order and purchase return order

This commit is contained in:
2025-12-01 16:52:59 +08:00
parent a3b4af2602
commit fae33809ac
17 changed files with 1745 additions and 6 deletions

View File

@@ -437,6 +437,202 @@ class SalesOrderServiceTestCase(TestCase):
created_by=self.user,
)
class PurchaseReturnServiceTestCase(TestCase):
def setUp(self):
(
self.merchant,
self.supplier,
self.warehouse_strict,
self.warehouse_relaxed,
self.product,
self.operator,
) = create_basic_fixtures()
self.warehouse_strict_out = basic_models.WareHouse.objects.create(
merchant=self.merchant,
name='采退严出仓',
mode=basic_models.WareHouseModeEnum.RESTRICT_IN_OUT,
)
User = get_user_model()
self.user = User.objects.create_user(username='purchase-return', password='pass123')
self.strict_items = [
{'product_id': self.product.id, 'numbers': [4, 2], 'price': '11.5', 'unit': ''}
]
self.strict_out_items = [
{
'product_id': self.product.id,
'consume_detail_ids': [10, 11],
'quantity': 30,
'price': '12.0',
'unit': '',
}
]
def test_create_purchase_return_order_success(self):
order = services.create_purchase_return_order(
merchant=self.merchant,
supplier=self.supplier,
return_date=timezone.now().date(),
warehouse=self.warehouse_strict,
operator=self.operator,
items=self.strict_items,
created_by=self.user,
)
self.assertEqual(order.status, business_models.PurchaseReturnStatusEnum.PENDING)
self.assertEqual(order.items.count(), 1)
def test_review_purchase_return_order_triggers_task_and_balance(self):
order = services.create_purchase_return_order(
merchant=self.merchant,
supplier=self.supplier,
return_date=timezone.now().date(),
warehouse=self.warehouse_relaxed,
operator=self.operator,
items=[{'product_id': self.product.id, 'quantity': 40, 'num_of_rolls': 2, 'price': '10.0'}],
created_by=self.user,
)
with patch('business.services.create_purchase_return_order_stock_entries.delay') as mock_delay:
reviewed = services.review_purchase_return_order(
purchase_return_order=order,
target_status=business_models.PurchaseReturnStatusEnum.APPROVED,
reviewed_by=self.user,
)
self.assertEqual(reviewed.status, business_models.PurchaseReturnStatusEnum.APPROVED)
mock_delay.assert_called_once()
balance = business_models.SupplierBalance.objects.get(
merchant=self.merchant,
supplier=self.supplier,
)
self.assertEqual(balance.balance, Decimal('-400.00'))
record = business_models.BalanceChangeRecord.objects.get(
merchant=self.merchant,
source_type=business_models.BalanceChangeSourceEnum.PURCHASE_RETURN_ORDER,
source_id=order.id,
)
self.assertEqual(record.direction, business_models.BalanceChangeDirectionEnum.DECREASE)
def test_purchase_return_cancel_blocked_after_stock_created(self):
order = services.create_purchase_return_order(
merchant=self.merchant,
supplier=self.supplier,
return_date=timezone.now().date(),
warehouse=self.warehouse_relaxed,
operator=self.operator,
items=[{'product_id': self.product.id, 'quantity': 20, 'num_of_rolls': 2, 'price': '9.5'}],
)
stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.REMOVE,
warehouse=self.warehouse_relaxed,
source_type=stock_models.StockChangeSourceEnum.PURCHASE_RETURN,
source_id=order.id,
)
with self.assertRaises(ValueError):
services.review_purchase_return_order(
purchase_return_order=order,
target_status=business_models.PurchaseReturnStatusEnum.CANCELLED,
)
def test_purchase_return_strict_out_requires_consume_ids(self):
payload = [
{
'product_id': self.product.id,
'quantity': 10,
'price': '8.5',
'unit': '',
}
]
with self.assertRaises(ValueError):
services.create_purchase_return_order(
merchant=self.merchant,
supplier=self.supplier,
return_date=timezone.now().date(),
warehouse=self.warehouse_strict_out,
operator=self.operator,
items=payload,
)
class SalesReturnServiceTestCase(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-return', password='pass123')
self.strict_items = [
{'product_id': self.product.id, 'numbers': [6, 2], 'price': '20.0', 'unit': ''}
]
def test_create_sales_return_order_success(self):
order = services.create_sales_return_order(
merchant=self.merchant,
customer=self.customer,
return_date=timezone.now().date(),
warehouse=self.warehouse_relaxed,
operator=self.operator,
items=[{'product_id': self.product.id, 'quantity': 50, 'num_of_rolls': 2, 'price': '19.5'}],
)
self.assertEqual(order.status, business_models.SalesReturnStatusEnum.PENDING)
def test_review_sales_return_order_updates_balance(self):
order = services.create_sales_return_order(
merchant=self.merchant,
customer=self.customer,
return_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_return_order_stock_entries.delay') as mock_delay:
reviewed = services.review_sales_return_order(
sales_return_order=order,
target_status=business_models.SalesReturnStatusEnum.APPROVED,
reviewed_by=self.user,
)
self.assertEqual(reviewed.status, business_models.SalesReturnStatusEnum.APPROVED)
mock_delay.assert_called_once()
balance = business_models.CustomerBalance.objects.get(
merchant=self.merchant,
customer=self.customer,
)
self.assertLess(balance.balance, Decimal('0'))
record = business_models.BalanceChangeRecord.objects.get(
merchant=self.merchant,
source_type=business_models.BalanceChangeSourceEnum.SALES_RETURN_ORDER,
source_id=order.id,
)
self.assertEqual(record.direction, business_models.BalanceChangeDirectionEnum.DECREASE)
def test_sales_return_cancel_blocked_after_stock_created(self):
order = services.create_sales_return_order(
merchant=self.merchant,
customer=self.customer,
return_date=timezone.now().date(),
warehouse=self.warehouse_relaxed,
operator=self.operator,
items=[{'product_id': self.product.id, 'quantity': 15, 'num_of_rolls': 1, 'price': '18.0'}],
)
stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.ADD,
warehouse=self.warehouse_relaxed,
source_type=stock_models.StockChangeSourceEnum.SALES_RETURN,
source_id=order.id,
)
with self.assertRaises(ValueError):
services.review_sales_return_order(
sales_return_order=order,
target_status=business_models.SalesReturnStatusEnum.CANCELLED,
)
class PaymentReceiptServiceTestCase(TestCase):
def setUp(self):
(