forked from erp-dev/erp
feat: added custom signal for pre sales order created
This commit is contained in:
@@ -1,7 +1,26 @@
|
||||
import logging
|
||||
from django.apps import AppConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BusinessConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'business'
|
||||
verbose_name = '业务模块'
|
||||
|
||||
def ready(self):
|
||||
from .models import PreSalesOrder
|
||||
from . import handlers
|
||||
from .signals import pre_sales_order_created
|
||||
|
||||
pre_sales_order_created.connect(
|
||||
handlers.on_pre_sales_order_created,
|
||||
sender=PreSalesOrder,
|
||||
dispatch_uid='business.on_pre_sales_order_created',
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f'[business.apps] 已注册 pre_sales_order_created 信号处理器, '
|
||||
f'sender={PreSalesOrder}, handler={handlers.on_pre_sales_order_created}'
|
||||
)
|
||||
|
||||
34
business/handlers.py
Normal file
34
business/handlers.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def on_pre_sales_order_created(sender, **kwargs):
|
||||
"""Handle PreSalesOrder created domain event.
|
||||
|
||||
For now: log only (no side effects).
|
||||
"""
|
||||
|
||||
order = kwargs.get('instance')
|
||||
created_by = kwargs.get('created_by')
|
||||
operator = kwargs.get('operator')
|
||||
items_count = kwargs.get('items_count')
|
||||
|
||||
if order is None:
|
||||
logger.warning('[business.handlers] pre_sales_order_created 缺少 instance,已跳过')
|
||||
return
|
||||
|
||||
created_by_label = getattr(created_by, 'username', None) if created_by else None
|
||||
operator_label = getattr(operator, 'name', None) if operator else None
|
||||
|
||||
logger.info(
|
||||
'[business.handlers] pre_sales_order_created: id=%s human_id=%s merchant_id=%s customer_id=%s '
|
||||
'created_by=%s operator=%s items_count=%s',
|
||||
getattr(order, 'id', None),
|
||||
getattr(order, 'human_id', None),
|
||||
getattr(order, 'merchant_id', None),
|
||||
getattr(order, 'customer_id', None),
|
||||
created_by_label or '-',
|
||||
operator_label or '-',
|
||||
items_count if items_count is not None else '-',
|
||||
)
|
||||
@@ -211,6 +211,28 @@ def create_pre_sales_order(
|
||||
]
|
||||
)
|
||||
|
||||
# domain event: pre sales order created
|
||||
# must be fired only after commit to avoid triggering on rollbacks
|
||||
def _send_created_signal():
|
||||
try:
|
||||
from .signals import pre_sales_order_created
|
||||
|
||||
pre_sales_order_created.send(
|
||||
sender=models.PreSalesOrder,
|
||||
instance=pre_sales_order,
|
||||
created_by=created_by,
|
||||
operator=operator,
|
||||
items_count=len(normalized_items),
|
||||
)
|
||||
except Exception:
|
||||
import logging
|
||||
|
||||
logging.getLogger(__name__).exception(
|
||||
'[business.pre_order_services] 触发 pre_sales_order_created signal 失败(已忽略)'
|
||||
)
|
||||
|
||||
transaction.on_commit(_send_created_signal)
|
||||
|
||||
pre_sales_order.refresh_from_db()
|
||||
return pre_sales_order
|
||||
|
||||
|
||||
21
business/signals.py
Normal file
21
business/signals.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""business domain signals.
|
||||
|
||||
We use domain-level signals (instead of model post_save) to:
|
||||
- keep semantics clean (only fire on business "create" flow)
|
||||
- allow passing explicit actor (created_by / operator)
|
||||
- keep integrations in handlers
|
||||
|
||||
NOTE:
|
||||
Signals should be triggered via transaction.on_commit to avoid firing when a
|
||||
transaction rolls back.
|
||||
"""
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
# Fired when a PreSalesOrder (and its items) is created via service layer.
|
||||
# Payload:
|
||||
# - instance: PreSalesOrder
|
||||
# - created_by: Django User (may be None)
|
||||
# - operator: Employee (may be None)
|
||||
# - items_count: int
|
||||
pre_sales_order_created = Signal()
|
||||
@@ -1,5 +1,7 @@
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TransactionTestCase
|
||||
from django.db import transaction
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from business import models as business_models
|
||||
@@ -120,3 +122,78 @@ class PreSalesOrderServiceTestCase(TestCase):
|
||||
|
||||
pre_order_services.delete_pre_sales_order(pre_sales_order=pre_sales_order)
|
||||
self.assertFalse(business_models.PreSalesOrder.objects.filter(id=order_id).exists())
|
||||
|
||||
|
||||
class PreSalesOrderSignalTestCase(TransactionTestCase):
|
||||
def setUp(self):
|
||||
(
|
||||
self.merchant,
|
||||
self.customer,
|
||||
self.warehouse_strict,
|
||||
self.warehouse_relaxed,
|
||||
self.warehouse_strict_out,
|
||||
self.product,
|
||||
self.operator,
|
||||
) = create_sales_fixtures()
|
||||
|
||||
self.user = get_user_model().objects.create_user(username='signal-user', password='pass')
|
||||
self.operator.sys_user = self.user
|
||||
self.operator.save(update_fields=['sys_user'])
|
||||
|
||||
def test_pre_sales_order_created_signal_fires_on_commit(self):
|
||||
from business.signals import pre_sales_order_created
|
||||
|
||||
received = []
|
||||
uid = 'tests.pre_sales_order_created.on_commit'
|
||||
|
||||
def _receiver(sender, **kwargs):
|
||||
received.append(kwargs)
|
||||
|
||||
pre_sales_order_created.connect(_receiver, dispatch_uid=uid, weak=False)
|
||||
try:
|
||||
pre_order_services.create_pre_sales_order(
|
||||
merchant=self.merchant,
|
||||
customer_id=self.customer.id,
|
||||
warehouse_id=self.warehouse_relaxed.id,
|
||||
operator=self.operator,
|
||||
created_by=self.user,
|
||||
items=[{'product_id': self.product.id, 'quantity': '1', 'unit': '米'}],
|
||||
)
|
||||
|
||||
self.assertEqual(len(received), 1)
|
||||
payload = received[0]
|
||||
self.assertIsNotNone(payload.get('instance'))
|
||||
self.assertEqual(payload.get('created_by').id, self.user.id)
|
||||
self.assertEqual(payload.get('operator').id, self.operator.id)
|
||||
self.assertEqual(payload.get('items_count'), 1)
|
||||
finally:
|
||||
pre_sales_order_created.disconnect(dispatch_uid=uid)
|
||||
|
||||
def test_pre_sales_order_created_signal_not_fired_on_rollback(self):
|
||||
from business.signals import pre_sales_order_created
|
||||
|
||||
received = []
|
||||
uid = 'tests.pre_sales_order_created.on_rollback'
|
||||
|
||||
def _receiver(sender, **kwargs):
|
||||
received.append(kwargs)
|
||||
|
||||
pre_sales_order_created.connect(_receiver, dispatch_uid=uid, weak=False)
|
||||
try:
|
||||
try:
|
||||
with transaction.atomic():
|
||||
pre_order_services.create_pre_sales_order(
|
||||
merchant=self.merchant,
|
||||
customer_id=self.customer.id,
|
||||
warehouse_id=self.warehouse_relaxed.id,
|
||||
operator=self.operator,
|
||||
created_by=self.user,
|
||||
items=[{'product_id': self.product.id, 'quantity': '1', 'unit': '米'}],
|
||||
)
|
||||
raise RuntimeError('force rollback')
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
self.assertEqual(received, [])
|
||||
finally:
|
||||
pre_sales_order_created.disconnect(dispatch_uid=uid)
|
||||
|
||||
Reference in New Issue
Block a user