diff --git a/docs/2026-01-23_summary.md b/docs/2026-01-23_summary.md index 9d57ec7..7663e6b 100644 --- a/docs/2026-01-23_summary.md +++ b/docs/2026-01-23_summary.md @@ -36,6 +36,8 @@ - `PRINTING_JOB_STATE_ADVANCED_WECOM_MARKDOWN_TEMPLATE` - 开关:`PRINTING_JOB_STATE_ADVANCED_WECOM_NOTIFY_ENABLED`(默认测试环境关闭,非测试环境开启) - 模板已增加字段:`{sender}`(发送者) + - 模板已增加字段:`{followup_url}`(跟进地址,来自 `PRINTING_JOB_STATE_ADVANCED_FOLLOWUP_URL_TEMPLATE`) + - 模板已增加字段:`{process_params_markdown}`(工序参数,来自 `state_log.get_all_parameters_summary()`) #### 3) 重构:提取“发送最新状态到企业微信”逻辑为可复用 service(便于后续 API 化) diff --git a/flower/settings.py b/flower/settings.py index e5eb070..a617b06 100644 --- a/flower/settings.py +++ b/flower/settings.py @@ -270,6 +270,11 @@ else: # PrintingJob:状态推进时的企业微信通知(markdown) # - 默认:测试环境关闭(避免单测出网/刷屏),非测试环境开启 PRINTING_JOB_STATE_ADVANCED_WECOM_NOTIFY_ENABLED = (not TESTING) +# PrintingJob 跟进地址(用于企业微信通知里展示/点击) +# orderId 使用 PrintingOrder.id +PRINTING_JOB_STATE_ADVANCED_FOLLOWUP_URL_TEMPLATE = ( + "https://app.yuwen.cloud/workstation/production/batch-advance?orderId={order_id}" +) PRINTING_JOB_STATE_ADVANCED_WECOM_MARKDOWN_TEMPLATE = ( "### 印染任务状态推进\n" "\n" @@ -278,6 +283,10 @@ PRINTING_JOB_STATE_ADVANCED_WECOM_MARKDOWN_TEMPLATE = ( "- **推进状态**:**{state_name}**\n" "- **推进时间**:`{advanced_at}`\n" "- **发送者**:{sender}\n" + "- **跟进地址**:[{followup_url}]({followup_url})\n" + "\n" + "**工序参数**:\n" + "{process_params_markdown}\n" ) diff --git a/printing/handlers.py b/printing/handlers.py index fbcf408..8f102bc 100644 --- a/printing/handlers.py +++ b/printing/handlers.py @@ -191,18 +191,6 @@ def on_printing_job_state_advanced(sender, **kwargs): 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" - ), - ) sender_label = None completed_by = kwargs.get('completed_by') if completed_by: @@ -212,13 +200,33 @@ def on_printing_job_state_advanced(sender, **kwargs): else: sender_label = '系统自动发送' - 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), - sender=str(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: diff --git a/printing/services.py b/printing/services.py index b94f4be..06ad9b4 100644 --- a/printing/services.py +++ b/printing/services.py @@ -24,6 +24,49 @@ class PrintingJobLatestState: advanced_at: str +def build_printing_job_followup_url(*, printing_order_id: int | None) -> str: + tmpl = getattr( + settings, + "PRINTING_JOB_STATE_ADVANCED_FOLLOWUP_URL_TEMPLATE", + "https://app.yuwen.cloud/workstation/production/batch-advance?orderId={order_id}", + ) + oid = printing_order_id if printing_order_id is not None else "" + try: + return str(tmpl).format(order_id=str(oid)) + except Exception: + # template mis-config should not crash critical flows + return f"https://app.yuwen.cloud/workstation/production/batch-advance?orderId={oid}" + + +def _truncate_text(s: str, max_len: int) -> str: + s = s or "" + if len(s) <= max_len: + return s + return s[: max(0, int(max_len) - 6)] + "...(截断)" + + +def render_process_params_markdown(*, params: dict, max_items: int = 60, max_value_len: int = 200) -> str: + """ + Render process params as markdown lines. + Example: + - **温度**:25 + """ + if not params: + return "- (无)" + + items = [] + for k in sorted(params.keys(), key=lambda x: str(x)): + v = params.get(k) + kk = _truncate_text(str(k), 80) + vv = _truncate_text(str(v), int(max_value_len)) + items.append(f"- **{kk}**:{vv}") + + if len(items) > int(max_items): + remain = len(items) - int(max_items) + items = items[: int(max_items)] + [f"- ……(其余 {remain} 项已省略)"] + return "\n".join(items) + + def _user_label(user) -> str: """ Format a human-readable sender label from Django User. @@ -71,6 +114,8 @@ def render_printing_job_state_markdown( state_name: str, advanced_at: str, sender_label: str, + followup_url: str, + process_params_markdown: str, ) -> str: template = getattr( settings, @@ -83,6 +128,10 @@ def render_printing_job_state_markdown( "- **推进状态**:**{state_name}**\n" "- **推进时间**:`{advanced_at}`\n" "- **发送者**:{sender}\n" + "- **跟进地址**:[{followup_url}]({followup_url})\n" + "\n" + "**工序参数**:\n" + "{process_params_markdown}\n" ), ) return template.format( @@ -91,6 +140,8 @@ def render_printing_job_state_markdown( state_name=str(state_name), advanced_at=str(advanced_at), sender=str(sender_label or "系统自动发送"), + followup_url=str(followup_url), + process_params_markdown=str(process_params_markdown), ) @@ -113,17 +164,45 @@ def send_printing_job_latest_state_wecom( if not job: raise ValueError(f"PrintingJob 不存在:id={printing_job_id}") - latest = get_printing_job_latest_state(job=job) + # latest state log for parameters + state_log = ( + job.business_object.state_logs.filter(is_cancelled=False) + .select_related("state") + .order_by("-completed_at", "-id") + .first() + if job.business_object + else None + ) + if state_log is None: + state_name = "未开始" + advanced_at = "-" + params = {} + else: + state_name = getattr(state_log.state, "name", None) or str(state_log.state) + advanced_at = timezone.localtime(state_log.completed_at).strftime("%Y-%m-%d %H:%M:%S") + params = state_log.get_all_parameters_summary(include_cancelled=False) + + followup_url = build_printing_job_followup_url(printing_order_id=job.printing_order_id) + process_params_markdown = render_process_params_markdown(params=params) msg = render_printing_job_state_markdown( printing_order_id=str(job.printing_order_id), printing_job_id=str(job.id), - state_name=latest.state_name, - advanced_at=latest.advanced_at, + state_name=str(state_name), + advanced_at=str(advanced_at), sender_label=sender_label, + followup_url=followup_url, + process_params_markdown=process_params_markdown, ) if dry_run: - return {"dry_run": True, "message": msg, "state_name": latest.state_name, "advanced_at": latest.advanced_at} + return { + "dry_run": True, + "message": msg, + "state_name": str(state_name), + "advanced_at": str(advanced_at), + "followup_url": str(followup_url), + "process_params": params, + } resp = send_wecom_webhook_message( content=msg, @@ -134,8 +213,10 @@ def send_printing_job_latest_state_wecom( return { "dry_run": False, "message": msg, - "state_name": latest.state_name, - "advanced_at": latest.advanced_at, + "state_name": str(state_name), + "advanced_at": str(advanced_at), + "followup_url": str(followup_url), + "process_params": params, "wecom": resp.raw, "ok": resp.ok, "errcode": resp.errcode,