forked from erp-dev/erp
feat: notification sending for printing order created
This commit is contained in:
@@ -51,6 +51,22 @@ class PrintingOrderService:
|
||||
data['merchant'] = user.employee.merchant
|
||||
|
||||
order = printing_models.PrintingOrder.objects.create(**data)
|
||||
|
||||
# domain event: printing order created
|
||||
# handler will ensure transaction.on_commit for external side effects (e.g. WeCom notify)
|
||||
try:
|
||||
from printing.signals import printing_order_created
|
||||
printing_order_created.send(
|
||||
sender=printing_models.PrintingOrder,
|
||||
instance=order,
|
||||
created_by=user,
|
||||
)
|
||||
except Exception:
|
||||
# signal should never break core create flow
|
||||
import logging
|
||||
logging.getLogger(__name__).exception(
|
||||
"[api_v1.views.printing.services] 触发 printing_order_created signal 失败(已忽略)"
|
||||
)
|
||||
return order
|
||||
|
||||
@staticmethod
|
||||
|
||||
25
docs/2026-01-27_summary.md
Normal file
25
docs/2026-01-27_summary.md
Normal file
@@ -0,0 +1,25 @@
|
||||
### 2026-01-27 工作记录
|
||||
|
||||
#### 1) PrintingOrder 创建通知:自定义 signal + handler(企业微信 markdown)
|
||||
|
||||
目标:
|
||||
- 当 `PrintingOrder` 被创建时,像“状态推进 advance”一样通过 **signal + handler** 发送企业微信通知
|
||||
- 避免使用 `post_save` 造成的语义污染/误触发
|
||||
- 外部副作用(企业微信请求)必须在事务提交后执行(`transaction.on_commit`)
|
||||
|
||||
实现:
|
||||
- 新增领域 signal:`printing/signals.py::printing_order_created`
|
||||
- 绑定与处理:
|
||||
- `printing/apps.py` 中 connect `printing_order_created` → `printing/handlers.py::on_printing_order_created`
|
||||
- handler 内:
|
||||
- 默认测试环境关闭(避免单测出网/刷屏)
|
||||
- `transaction.on_commit` 后发送企业微信 `markdown`
|
||||
- 触发点:
|
||||
- `api_v1/views/printing/services.py::PrintingOrderService.create_printing_order` 在创建成功后触发 `printing_order_created`
|
||||
|
||||
配置:
|
||||
- `flower/settings.py`
|
||||
- `PRINTING_ORDER_CREATED_WECOM_NOTIFY_ENABLED`(支持 `.env` 覆盖,默认非测试环境开启)
|
||||
- `PRINTING_ORDER_CREATED_WECOM_MARKDOWN_TEMPLATE`(中文字段)
|
||||
- `env.example` 已补充:`PRINTING_ORDER_CREATED_WECOM_NOTIFY_ENABLED=True`
|
||||
|
||||
@@ -75,6 +75,9 @@ WECOM_WEBHOOK_KEY=
|
||||
# PrintingJob 状态推进通知的“跟进地址”模板;为空则消息里省略“跟进地址”字段
|
||||
PRINTING_JOB_STATE_ADVANCED_FOLLOWUP_URL_TEMPLATE=https://app.yuwen.cloud/workstation/production/batch-advance?orderId={order_id}
|
||||
|
||||
# PrintingOrder 创建通知(企业微信):测试环境默认关闭;生产/开发默认开启
|
||||
PRINTING_ORDER_CREATED_WECOM_NOTIFY_ENABLED=True
|
||||
|
||||
############################
|
||||
# Printing 自动创建销售品(可选)
|
||||
############################
|
||||
|
||||
@@ -290,6 +290,24 @@ PRINTING_JOB_STATE_ADVANCED_WECOM_MARKDOWN_TEMPLATE = (
|
||||
"{process_params_markdown}\n"
|
||||
)
|
||||
|
||||
# PrintingOrder:创建时的企业微信通知(markdown)
|
||||
# - 默认:测试环境关闭(避免单测出网/刷屏),非测试环境开启
|
||||
PRINTING_ORDER_CREATED_WECOM_NOTIFY_ENABLED = env.bool(
|
||||
"PRINTING_ORDER_CREATED_WECOM_NOTIFY_ENABLED",
|
||||
default=(not TESTING),
|
||||
)
|
||||
PRINTING_ORDER_CREATED_WECOM_MARKDOWN_TEMPLATE = (
|
||||
"### 印染订单创建\n"
|
||||
"\n"
|
||||
"- **印染订单ID**:`{printing_order_id}`\n"
|
||||
"- **订单编号**:`{printing_order_human_id}`\n"
|
||||
"- **创建时间**:`{created_at}`\n"
|
||||
"- **发送者**:{sender}\n"
|
||||
"- **客户**:{customer_name}\n"
|
||||
"- **面料**:{fabric}\n"
|
||||
"- **出货日期**:`{outgoing_date}`\n"
|
||||
)
|
||||
|
||||
|
||||
# Logging configuration
|
||||
# 目标:
|
||||
|
||||
@@ -12,8 +12,9 @@ class PrintingConfig(AppConfig):
|
||||
def ready(self):
|
||||
"""注册信号处理函数"""
|
||||
from stateflow.signals import process_completed, state_advanced
|
||||
from .models import PrintingJob
|
||||
from .models import PrintingJob, PrintingOrder
|
||||
from . import handlers
|
||||
from .signals import printing_order_created
|
||||
|
||||
# 监听 PrintingJob 流程完成信号
|
||||
process_completed.connect(
|
||||
@@ -27,6 +28,13 @@ class PrintingConfig(AppConfig):
|
||||
sender=PrintingJob
|
||||
)
|
||||
|
||||
# 监听 PrintingOrder 创建事件(领域 signal)
|
||||
printing_order_created.connect(
|
||||
handlers.on_printing_order_created,
|
||||
sender=PrintingOrder,
|
||||
dispatch_uid="printing.on_printing_order_created",
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f'[printing.apps] 已注册 process_completed 信号处理器, '
|
||||
f'sender={PrintingJob}, handler={handlers.on_printing_job_process_completed}'
|
||||
@@ -35,3 +43,7 @@ class PrintingConfig(AppConfig):
|
||||
f'[printing.apps] 已注册 state_advanced 信号处理器, '
|
||||
f'sender={PrintingJob}, handler={handlers.on_printing_job_state_advanced}'
|
||||
)
|
||||
logger.info(
|
||||
f'[printing.apps] 已注册 printing_order_created 信号处理器, '
|
||||
f'sender={PrintingOrder}, handler={handlers.on_printing_order_created}'
|
||||
)
|
||||
@@ -242,3 +242,88 @@ def on_printing_job_state_advanced(sender, **kwargs):
|
||||
|
||||
# 在事务提交后再发送,避免事务回滚但通知已发出
|
||||
transaction.on_commit(_send_wecom)
|
||||
|
||||
|
||||
def on_printing_order_created(sender, **kwargs):
|
||||
"""
|
||||
PrintingOrder 创建时的处理(领域 signal:printing.signals.printing_order_created)。
|
||||
|
||||
需求:
|
||||
- 在事务提交后发送企业微信机器人通知(markdown)
|
||||
- 消息字段(中文):
|
||||
- 印染订单ID / human_id
|
||||
- 创建时间
|
||||
- 发送者(优先 created_by.employee.name / created_by.username)
|
||||
- 客户、面料、出货日期(可选字段,缺省展示为 '-')
|
||||
"""
|
||||
# 测试环境默认关闭,避免单测出网/刷屏
|
||||
if not getattr(settings, "PRINTING_ORDER_CREATED_WECOM_NOTIFY_ENABLED", True):
|
||||
return
|
||||
if getattr(settings, "TESTING", False):
|
||||
return
|
||||
|
||||
order = kwargs.get("instance")
|
||||
created_by = kwargs.get("created_by")
|
||||
if order is None:
|
||||
logger.warning("[printing.handlers] printing_order_created 信号缺少 instance,跳过通知")
|
||||
return
|
||||
|
||||
try:
|
||||
from printing.services import render_printing_order_created_markdown
|
||||
|
||||
created_at_dt = getattr(order, "created_at", None)
|
||||
created_at = (
|
||||
timezone.localtime(created_at_dt).strftime("%Y-%m-%d %H:%M:%S")
|
||||
if created_at_dt is not None
|
||||
else timezone.localtime(timezone.now()).strftime("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
|
||||
emp = getattr(created_by, "employee", None) if created_by else None
|
||||
sender_label = getattr(emp, "name", None) if emp is not None else None
|
||||
sender_label = sender_label or getattr(created_by, "username", None) if created_by else None
|
||||
sender_label = sender_label or "系统自动发送"
|
||||
|
||||
customer_name = None
|
||||
try:
|
||||
customer = getattr(order, "customer", None)
|
||||
customer_name = getattr(customer, "name", None) if customer else None
|
||||
except Exception:
|
||||
customer_name = None
|
||||
|
||||
outgoing_dt = getattr(order, "outgoing_date", None)
|
||||
outgoing_date = (
|
||||
timezone.localtime(outgoing_dt).strftime("%Y-%m-%d %H:%M:%S")
|
||||
if outgoing_dt is not None
|
||||
else "-"
|
||||
)
|
||||
|
||||
message = render_printing_order_created_markdown(
|
||||
printing_order_id=str(getattr(order, "id", "-") or "-"),
|
||||
printing_order_human_id=str(getattr(order, "human_id", "-") or "-"),
|
||||
created_at=str(created_at),
|
||||
sender_label=str(sender_label),
|
||||
customer_name=str(customer_name or "-"),
|
||||
fabric=str(getattr(order, "fabric", "-") or "-"),
|
||||
outgoing_date=str(outgoing_date),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("[printing.handlers] 渲染企业微信消息失败,跳过通知")
|
||||
return
|
||||
|
||||
def _send_wecom():
|
||||
try:
|
||||
from api_v1.utils.wecom_webhook import send_wecom_webhook_message
|
||||
|
||||
resp = send_wecom_webhook_message(content=message, msgtype="markdown")
|
||||
if not resp.ok:
|
||||
logger.warning(
|
||||
"[printing.handlers] WeCom webhook 返回失败:errcode=%s, errmsg=%s, raw=%s",
|
||||
resp.errcode,
|
||||
resp.errmsg,
|
||||
resp.raw,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("[printing.handlers] 发送 WeCom webhook 失败(已忽略,不影响主流程)")
|
||||
|
||||
# 在事务提交后再发送,避免事务回滚但通知已发出
|
||||
transaction.on_commit(_send_wecom)
|
||||
|
||||
@@ -254,3 +254,39 @@ def send_printing_job_latest_state_wecom(
|
||||
"errcode": resp.errcode,
|
||||
"errmsg": resp.errmsg,
|
||||
}
|
||||
|
||||
|
||||
def render_printing_order_created_markdown(
|
||||
*,
|
||||
printing_order_id: str,
|
||||
printing_order_human_id: str,
|
||||
created_at: str,
|
||||
sender_label: str,
|
||||
customer_name: str,
|
||||
fabric: str,
|
||||
outgoing_date: str,
|
||||
) -> str:
|
||||
template = getattr(
|
||||
settings,
|
||||
"PRINTING_ORDER_CREATED_WECOM_MARKDOWN_TEMPLATE",
|
||||
(
|
||||
"### 印染订单创建\n"
|
||||
"\n"
|
||||
"- **印染订单ID**:`{printing_order_id}`\n"
|
||||
"- **订单编号**:`{printing_order_human_id}`\n"
|
||||
"- **创建时间**:`{created_at}`\n"
|
||||
"- **发送者**:{sender}\n"
|
||||
"- **客户**:{customer_name}\n"
|
||||
"- **面料**:{fabric}\n"
|
||||
"- **出货日期**:`{outgoing_date}`\n"
|
||||
),
|
||||
)
|
||||
return template.format(
|
||||
printing_order_id=str(printing_order_id),
|
||||
printing_order_human_id=str(printing_order_human_id),
|
||||
created_at=str(created_at),
|
||||
sender=str(sender_label or "系统自动发送"),
|
||||
customer_name=str(customer_name or "-"),
|
||||
fabric=str(fabric or "-"),
|
||||
outgoing_date=str(outgoing_date or "-"),
|
||||
)
|
||||
|
||||
17
printing/signals.py
Normal file
17
printing/signals.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Printing 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 / sender_label)
|
||||
- keep integration (e.g. WeCom notify) in handlers
|
||||
"""
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
# Fired when a PrintingOrder is created via business service.
|
||||
# Payload:
|
||||
# - instance: PrintingOrder
|
||||
# - created_by: Django User (may be None)
|
||||
printing_order_created = Signal()
|
||||
|
||||
Reference in New Issue
Block a user