""" Printing domain services. This module is intended for reusable business logic that may be called by: - management commands - API views - signal handlers """ from __future__ import annotations from dataclasses import dataclass from django.conf import settings from django.utils import timezone from api_v1.utils.wecom_webhook import send_wecom_webhook_message from printing.models import PrintingJob @dataclass(frozen=True) class PrintingJobLatestState: state_name: str advanced_at: str def _user_label(user) -> str: """ Format a human-readable sender label from Django User. Prefer employee.name if available. """ if not user: return "系统自动发送" emp = getattr(user, "employee", None) name = getattr(emp, "name", None) if emp is not None else None if name: return str(name) username = getattr(user, "username", None) if username: return str(username) return "系统自动发送" def get_printing_job_latest_state(*, job: PrintingJob) -> PrintingJobLatestState: """ Latest state is defined as the latest non-cancelled StateFlowRecord on job.business_object. """ if not job.business_object: raise ValueError(f"PrintingJob 未绑定 business_object,无法获取状态:id={job.id}") state_log = ( job.business_object.state_logs.filter(is_cancelled=False) .select_related("state") .order_by("-completed_at", "-id") .first() ) if state_log is None: return PrintingJobLatestState(state_name="未开始", advanced_at="-") state_name = getattr(state_log.state, "name", None) or str(state_log.state) advanced_at_dt = state_log.completed_at advanced_at = timezone.localtime(advanced_at_dt).strftime("%Y-%m-%d %H:%M:%S") return PrintingJobLatestState(state_name=str(state_name), advanced_at=str(advanced_at)) def render_printing_job_state_markdown( *, printing_order_id: str, printing_job_id: str, state_name: str, advanced_at: str, sender_label: str, ) -> str: template = getattr( settings, "PRINTING_JOB_STATE_ADVANCED_WECOM_MARKDOWN_TEMPLATE", ( "### 印染任务状态推进\n" "\n" "- **印染订单ID**:`{printing_order_id}`\n" "- **印染任务ID**:`{printing_job_id}`\n" "- **推进状态**:**{state_name}**\n" "- **推进时间**:`{advanced_at}`\n" "- **发送者**:{sender}\n" ), ) return template.format( printing_order_id=str(printing_order_id), printing_job_id=str(printing_job_id), state_name=str(state_name), advanced_at=str(advanced_at), sender=str(sender_label or "系统自动发送"), ) def send_printing_job_latest_state_wecom( *, printing_job_id: int, sender_label: str, key: str | None = None, timeout_seconds: float = 10.0, dry_run: bool = False, ) -> dict: """ Reusable entrypoint for command / future API. """ job = ( PrintingJob.objects.select_related("printing_order", "business_object") .filter(id=int(printing_job_id)) .first() ) if not job: raise ValueError(f"PrintingJob 不存在:id={printing_job_id}") latest = get_printing_job_latest_state(job=job) 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, sender_label=sender_label, ) if dry_run: return {"dry_run": True, "message": msg, "state_name": latest.state_name, "advanced_at": latest.advanced_at} resp = send_wecom_webhook_message( content=msg, msgtype="markdown", key=key, timeout_seconds=timeout_seconds, ) return { "dry_run": False, "message": msg, "state_name": latest.state_name, "advanced_at": latest.advanced_at, "wecom": resp.raw, "ok": resp.ok, "errcode": resp.errcode, "errmsg": resp.errmsg, }