forked from erp-dev/erp
227 lines
8.6 KiB
Python
227 lines
8.6 KiB
Python
"""
|
||
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')
|
||
)
|
||
|
||
template = getattr(
|
||
settings,
|
||
'PRINTING_JOB_STATE_ADVANCED_WECOM_MARKDOWN_TEMPLATE',
|
||
(
|
||
"### PrintingJob 状态推进\n"
|
||
"\n"
|
||
"- **PrintingOrder.id**: `{printing_order_id}`\n"
|
||
"- **PrintingJob.id**: `{printing_job_id}`\n"
|
||
"- **State**: **{state_name}**\n"
|
||
"- **AdvancedAt**: `{advanced_at}`\n"
|
||
),
|
||
)
|
||
message = template.format(
|
||
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),
|
||
)
|
||
|
||
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)
|