1
0
forked from erp-dev/erp
Files
erpnew/printing/handlers.py

330 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Printing 模块信号处理函数
监听 stateflow 信号,在流程完成时执行业务逻辑。
"""
import logging
from decimal import Decimal, InvalidOperation
from django.conf import settings
from django.db import transaction
from django.utils import timezone
logger = logging.getLogger(__name__)
def on_printing_job_process_completed(sender, **kwargs):
"""
PrintingJob 流程完成时的处理
当 PrintingJob 的工艺流程全部完成时,自动创建 SalesItem销售品
数量和单位从指定的流程节点参数中获取(通过 settings 配置)。
配置项:
AUTO_CREATE_SALESITEM_FROM_PRINT_ORDER: 是否启用自动创建销售品(默认 True
PRINTING_SALES_ITEM_SOURCE_STATE_ID: 指定从哪个 State 获取参数
PRINTING_SALES_ITEM_QUANTITY_KEY: 数量参数的 key
PRINTING_SALES_ITEM_UNIT_KEY: 单位参数的 key
Args:
sender: PrintingJob 类
content_object: PrintingJob 实例
business_object: BusinessObject 实例
last_completed_by: 最后步骤操作人
其他参数见 stateflow.signals.process_completed
"""
# 检查是否启用自动创建销售品
if not getattr(settings, 'AUTO_CREATE_SALESITEM_FROM_PRINT_ORDER', True):
logger.debug('[printing.handlers] AUTO_CREATE_SALESITEM_FROM_PRINT_ORDER=False跳过销售品创建')
return
from shipment.models import SalesItem, UnitChoices
logger.info(
f'[printing.handlers] 收到 process_completed 信号: '
f'sender={sender}, kwargs keys={list(kwargs.keys())}'
)
content_object = kwargs.get('content_object')
business_object = kwargs.get('business_object')
last_completed_by = kwargs.get('last_completed_by')
if content_object is None or business_object is None:
logger.warning('process_completed 信号缺少 content_object 或 business_object')
return
printing_job = content_object
logger.info(f'[printing.handlers] 处理 PrintingJob #{printing_job.id}')
# 从 settings 获取配置
source_state_id = getattr(settings, 'PRINTING_SALES_ITEM_SOURCE_STATE_ID', None)
quantity_key = getattr(settings, 'PRINTING_SALES_ITEM_QUANTITY_KEY', '米数')
if source_state_id is None:
logger.debug(
f'PrintingJob #{printing_job.id} 流程完成,'
f'但 PRINTING_SALES_ITEM_SOURCE_STATE_ID 未配置,跳过销售品创建'
)
return
# 从 business_object 的状态流转记录中找到指定 state_id 的记录
state_log = business_object.state_logs.filter(
state_id=source_state_id,
is_cancelled=False
).first()
if state_log is None:
logger.warning(
f'PrintingJob #{printing_job.id} 流程完成,'
f'但未找到 state_id={source_state_id} 的已完成记录,跳过销售品创建'
)
return
# 获取参数汇总
params = state_log.get_all_parameters_summary()
# 获取数量
quantity_raw = params.get(quantity_key)
if quantity_raw is None:
logger.warning(
f'PrintingJob #{printing_job.id} 流程完成,'
f'但 state_id={source_state_id} 的参数中缺少 {quantity_key},跳过销售品创建'
)
return
# 转换数量为 Decimal
try:
quantity = Decimal(str(quantity_raw))
except (InvalidOperation, ValueError, TypeError) as e:
logger.warning(
f'PrintingJob #{printing_job.id} 流程完成,'
f'{quantity_key}={quantity_raw} 无法转换为数字: {e},跳过销售品创建'
)
return
# 单位固定为"米"
sales_unit = UnitChoices.METER
# 获取产品名称
product_name = printing_job.product.name if printing_job.product else '未知产品'
# 获取客户ID从主订单获取
customer_id = None
if printing_job.printing_order and printing_job.printing_order.customer:
customer_id = printing_job.printing_order.customer_id
try:
with transaction.atomic():
# SalesItem.merchant 不允许为空,优先取 printing_job.merchant
sales_merchant = getattr(printing_job, 'merchant', None)
if not sales_merchant and getattr(printing_job, 'printing_order', None):
sales_merchant = getattr(printing_job.printing_order, 'merchant', None)
if not sales_merchant and last_completed_by and hasattr(last_completed_by, 'employee'):
sales_merchant = getattr(last_completed_by.employee, 'merchant', None)
if not sales_merchant:
logger.warning(
f'PrintingJob #{printing_job.id} 流程完成,'
f'但无法确定 merchant跳过销售品创建'
)
return
sales_item = SalesItem.objects.create(
shipment=None, # 暂不关联出货单,待后续分配
merchant=sales_merchant,
name=product_name,
quantity=quantity,
unit=sales_unit,
created_by=last_completed_by,
printing_job_id=printing_job.id,
customer_id=customer_id,
)
logger.info(
f'PrintingJob #{printing_job.id} 流程完成,'
f'已创建销售品 SalesItem #{sales_item.id}: '
f'{product_name} x {quantity} {sales_item.get_unit_display()}'
)
except Exception as e:
logger.error(
f'PrintingJob #{printing_job.id} 流程完成后创建销售品失败: {e}',
exc_info=True
)
def on_printing_job_state_advanced(sender, **kwargs):
"""
PrintingJob 每次状态推进时的处理(监听 stateflow.signals.state_advanced
需求:
- 仅监听 PrintingJob
- 在事务提交后发送企业微信机器人通知markdown
- 消息包含:
- PrintingOrder.id
- PrintingJob.id
- 被推进状态名 state.name
- 推进时间state_log.completed_at
"""
# 测试环境默认关闭,避免单测出网/刷屏
if not getattr(settings, 'PRINTING_JOB_STATE_ADVANCED_WECOM_NOTIFY_ENABLED', True):
return
if getattr(settings, 'TESTING', False):
return
content_object = kwargs.get('content_object')
state = kwargs.get('state')
state_log = kwargs.get('state_log')
if content_object is None or state is None or state_log is None:
logger.warning('[printing.handlers] state_advanced 信号缺少关键参数,跳过通知')
return
printing_job = content_object
printing_job_id = getattr(printing_job, 'id', None)
printing_order = getattr(printing_job, 'printing_order', None)
printing_order_id = getattr(printing_order, 'id', None)
state_name = getattr(state, 'name', None) or str(state)
advanced_at_dt = getattr(state_log, 'completed_at', None) or getattr(state_log, 'created_at', None)
advanced_at = (
timezone.localtime(advanced_at_dt).strftime('%Y-%m-%d %H:%M:%S')
if advanced_at_dt is not None
else timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S')
)
sender_label = None
completed_by = kwargs.get('completed_by')
if completed_by:
emp = getattr(completed_by, 'employee', None)
sender_label = getattr(emp, 'name', None) if emp is not None else None
sender_label = sender_label or getattr(completed_by, 'username', None) or '系统自动发送'
else:
sender_label = '系统自动发送'
# 工序参数:取该 state_log 的最新汇总
try:
params = state_log.get_all_parameters_summary(include_cancelled=False) if state_log else {}
except Exception:
logger.exception('[printing.handlers] 读取工序参数失败,降级为空')
params = {}
try:
from printing.services import (
build_printing_job_followup_url,
render_printing_job_state_markdown,
render_process_params_markdown,
)
followup_url = build_printing_job_followup_url(printing_order_id=printing_order_id)
process_params_markdown = render_process_params_markdown(params=params)
message = render_printing_job_state_markdown(
printing_order_id=str(printing_order_id) if printing_order_id is not None else '-',
printing_job_id=str(printing_job_id) if printing_job_id is not None else '-',
state_name=str(state_name),
advanced_at=str(advanced_at),
sender_label=str(sender_label),
followup_url=str(followup_url),
process_params_markdown=str(process_params_markdown),
)
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)
def on_printing_order_created(sender, **kwargs):
"""
PrintingOrder 创建时的处理(领域 signalprinting.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)