1
0
forked from erp-dev/erp
Files
erpnew/printing/services.py
2026-01-23 18:02:55 +08:00

230 lines
6.9 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 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 build_printing_job_followup_url(*, printing_order_id: int | None) -> str:
tmpl = getattr(
settings,
"PRINTING_JOB_STATE_ADVANCED_FOLLOWUP_URL_TEMPLATE",
"",
)
tmpl = (str(tmpl or "")).strip()
if not tmpl:
return ""
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.
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,
followup_url: str,
process_params_markdown: 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"
"{followup_line}"
"\n"
"**工序参数**\n"
"{process_params_markdown}\n"
),
)
followup_url = (followup_url or "").strip()
followup_line = f"- **跟进地址**[{followup_url}]({followup_url})\n" if followup_url else ""
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 "系统自动发送"),
followup_line=str(followup_line),
process_params_markdown=str(process_params_markdown),
)
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 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=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": str(state_name),
"advanced_at": str(advanced_at),
"followup_url": str(followup_url),
"process_params": params,
}
resp = send_wecom_webhook_message(
content=msg,
msgtype="markdown",
key=key,
timeout_seconds=timeout_seconds,
)
return {
"dry_run": False,
"message": msg,
"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,
"errmsg": resp.errmsg,
}