1
0
forked from erp-dev/erp

feat: balance api

This commit is contained in:
2025-11-30 23:04:21 +08:00
parent 9006a530d1
commit 9acc4e14fc
21 changed files with 714 additions and 64 deletions

View File

@@ -1,7 +1,10 @@
from typing import List, Dict, Any
from decimal import Decimal
from concurrent.futures import ThreadPoolExecutor
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
from django.test import TestCase, TransactionTestCase, override_settings
from django.db import connections
from django.utils import timezone
from unittest.mock import patch, MagicMock
@@ -171,6 +174,11 @@ class PurchaseOrderServiceTestCase(TestCase):
items=[{'product_id': self.product.id, 'quantities': ['10', '5']}],
created_by_id=self.user.id,
)
balance = business_models.SupplierBalance.objects.get(
merchant=self.merchant,
supplier=self.supplier,
)
self.assertEqual(balance.balance, purchase_order.get_total_amount())
def test_create_purchase_order_without_items_raises(self):
with self.assertRaises(ValueError):
@@ -324,6 +332,11 @@ class SalesOrderServiceTestCase(TestCase):
items=[{'product_id': self.product.id, 'quantities': ['6', '4']}],
created_by_id=self.user.id,
)
balance = business_models.CustomerBalance.objects.get(
merchant=self.merchant,
customer=self.customer,
)
self.assertEqual(balance.balance, sales_order.get_total_amount())
def test_sales_order_cancel_blocked_after_stock_created(self):
sales_order = services.create_sales_order(
@@ -392,7 +405,6 @@ class SalesOrderServiceTestCase(TestCase):
created_by=self.user,
)
class PaymentReceiptServiceTestCase(TestCase):
def setUp(self):
(
@@ -426,6 +438,17 @@ class PaymentReceiptServiceTestCase(TestCase):
reviewed_by=self.operator,
)
self.assertEqual(reviewed.status, business_models.PaymentOrderStatusEnum.APPROVED)
balance = business_models.SupplierBalance.objects.get(
merchant=self.merchant,
supplier=self.supplier,
)
self.assertEqual(balance.balance, Decimal('-120.50'))
with self.assertRaises(ValueError):
services.review_payment_order(
payment_order=order,
target_status=business_models.PaymentOrderStatusEnum.CANCELLED,
reviewed_by=self.operator,
)
def test_create_receipt_order_and_cancel(self):
order = services.create_receipt_order(
@@ -472,6 +495,11 @@ class PaymentReceiptServiceTestCase(TestCase):
reviewed_by=self.operator,
)
self.assertEqual(reviewed_again.status, business_models.ReceiptOrderStatusEnum.APPROVED)
balance = business_models.CustomerBalance.objects.get(
merchant=self.merchant,
customer=self.customer,
)
self.assertEqual(balance.balance, Decimal('-10'))
class PurchaseOrderStockServiceTestCase(TestCase):
@@ -593,6 +621,58 @@ class SalesOrderStockServiceTestCase(TestCase):
self.assertEqual(payload['stock_change_record_id'], 987)
class SalesOrderConcurrencyTestCase(TransactionTestCase):
reset_sequences = True
def setUp(self):
(
self.merchant,
self.customer,
self.warehouse_strict,
self.warehouse_relaxed,
self.warehouse_strict_out,
self.product,
self.operator,
) = create_sales_fixtures()
basic_models.MerchantSetting.objects.filter(
merchant=self.merchant,
key=basic_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS,
).update(val_bool=False)
User = get_user_model()
self.user = User.objects.create_user(username='concurrent', password='pass123')
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=[{'product_id': self.product.id, 'numbers': [5], 'price': '12', 'unit': ''}],
created_by=self.user,
)
def test_concurrent_sales_order_approval_updates_balance_once(self):
def approve():
services.review_sales_order(
sales_order_id=self.sales_order.id,
target_status=business_models.SalesOrderStatusEnum.APPROVED,
reviewed_by=self.user,
)
with ThreadPoolExecutor(max_workers=2) as executor:
futures = [executor.submit(approve) for _ in range(2)]
for future in futures:
future.result()
balance = business_models.CustomerBalance.objects.get(
merchant=self.merchant,
customer=self.customer,
)
self.assertEqual(balance.balance, self.sales_order.get_total_amount())
def tearDown(self):
connections.close_all()
@override_settings(
CELERY_TASK_ALWAYS_EAGER=True,
CELERY_TASK_EAGER_PROPAGATES=True,