forked from erp-dev/erp
144 lines
4.1 KiB
Python
144 lines
4.1 KiB
Python
"""
|
||
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,
|
||
}
|