feat(services): 核心业务逻辑 — 接收/返工/裂变/记录/标签打印

task_service:
- receive_task() — PENDING→WIP, 记录 received_at
- reject_task() — →REJECTED + 自动返工闭环
- transfer_task() — →COMPLETED + 多路裂变 + virtual_warehouse入库
- add_task_record() — 图文记录追加
- 全链路 selectinload(Task.records)
- 全部 now 改用 get_beijing_time()

product_service:
- create_product() — PG Sequence 自动生成16位HEX
- update_overall_status() — 宏观状态校验+更新
- 扫码返回完整 task_tree (含 records)
- order_no 自由文本→自动创建 ProductionOrder
- update_product() 支持 order_no 编辑

label_service:
- 480×360 工业级排版 (QR+名/规/单+底部通栏码)
- simhei.ttf 34px/28px + stroke_width 描边
- TSPL Socket 发送到打标机
This commit is contained in:
2026-08-05 14:00:55 +08:00
parent 79583cf8f4
commit 1fed716829
4 changed files with 415 additions and 20 deletions

View File

@ -1,13 +1,13 @@
"""任务服务 — 核心业务逻辑:接收、驳回返工、裂变转交、无限嵌套子任务"""
from __future__ import annotations
import uuid
from datetime import datetime, timezone
from fastapi import HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.task import Task, TASK_STATUS_PENDING, TASK_STATUS_WIP, TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED
from app.models.task import Task, TaskRecord, TASK_STATUS_PENDING, TASK_STATUS_WIP, TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED
from app.core.time_utils import get_beijing_time
from app.models.product import Product
from app.models.task_log import TaskLog
from app.schemas.task import (
@ -17,6 +17,8 @@ from app.schemas.task import (
TaskRejectRequest,
TaskTransferRequest,
SubtaskCreate,
TaskRecordCreate,
TaskRecordResponse,
TaskResponse,
TaskCompleteResponse,
TaskTransferResponse,
@ -40,6 +42,7 @@ async def _get_task_or_404(db: AsyncSession, task_id: uuid.UUID) -> Task:
selectinload(Task.child_tasks),
selectinload(Task.parent_task),
selectinload(Task.product),
selectinload(Task.records),
)
.where(Task.id == task_id)
)
@ -56,7 +59,10 @@ async def _get_task_with_children_recursive(db: AsyncSession, task_id: uuid.UUID
"""递归加载任务及其所有子孙任务"""
result = await db.execute(
select(Task)
.options(selectinload(Task.child_tasks))
.options(
selectinload(Task.child_tasks),
selectinload(Task.records),
)
.where(Task.id == task_id)
)
task = result.scalar_one_or_none()
@ -71,7 +77,10 @@ async def _get_task_with_children_recursive(db: AsyncSession, task_id: uuid.UUID
for child in t.child_tasks:
child_result = await db.execute(
select(Task)
.options(selectinload(Task.child_tasks))
.options(
selectinload(Task.child_tasks),
selectinload(Task.records),
)
.where(Task.id == child.id)
)
refreshed_child = child_result.scalar_one()
@ -87,6 +96,8 @@ def _to_response(task: Task) -> TaskResponse:
return TaskResponse(
id=task.id,
product_id=task.product_id,
product_sn=task.product.serial_number if task.product else "",
product_material=task.product.material_name or task.product.material_id or "" if task.product else "",
parent_task_id=task.parent_task_id,
task_name=task.task_name,
assignee_id=task.assignee_id,
@ -98,6 +109,7 @@ def _to_response(task: Task) -> TaskResponse:
completed_at=task.completed_at,
created_at=task.created_at,
child_tasks=[_to_response(c) for c in task.child_tasks],
records=[TaskRecordResponse.model_validate(r) for r in (task.records or [])],
)
@ -187,12 +199,19 @@ async def update_task(db: AsyncSession, task_id: uuid.UUID, data: TaskUpdate) ->
async def get_all_tasks(
db: AsyncSession, product_id: uuid.UUID | None = None, skip: int = 0, limit: int = 50
db: AsyncSession, product_id: uuid.UUID | None = None,
assignee_id: str | None = None, skip: int = 0, limit: int = 50
) -> TaskListResponse:
"""获取任务列表,可按产品筛选"""
stmt = select(Task).options(selectinload(Task.child_tasks))
"""获取任务列表,可按产品/负责人筛选"""
stmt = select(Task).options(
selectinload(Task.child_tasks),
selectinload(Task.records),
selectinload(Task.product),
)
if product_id:
stmt = stmt.where(Task.product_id == product_id)
if assignee_id:
stmt = stmt.where(Task.assignee_id == assignee_id)
stmt = stmt.offset(skip).limit(limit).order_by(Task.created_at.desc())
result = await db.execute(stmt)
@ -225,7 +244,7 @@ async def receive_task(
detail=f"只有待接收(PENDING)状态的任务可接收,当前状态: {task.status}",
)
now = datetime.now(timezone.utc)
now = get_beijing_time()
task.status = TASK_STATUS_WIP
task.received_at = now
@ -269,7 +288,7 @@ async def reject_task(
detail=f"任务状态为 {task.status},无法驳回",
)
now = datetime.now(timezone.utc)
now = get_beijing_time()
# --- 1. 标记当前任务为已驳回 ---
task.status = TASK_STATUS_REJECTED
@ -364,7 +383,7 @@ async def transfer_task(
detail=f"请等待相关子任务完成:{', '.join(incomplete_names)}",
)
now = datetime.now(timezone.utc)
now = get_beijing_time()
# --- 动作 1:闭环当前节点 ---
task.status = TASK_STATUS_COMPLETED
@ -501,7 +520,7 @@ async def complete_task(
detail=f"请等待相关子任务完成:{', '.join(incomplete_names)}",
)
now = datetime.now(timezone.utc)
now = get_beijing_time()
# --- 3. 标记当前任务为已完成 ---
task.status = TASK_STATUS_COMPLETED
@ -591,3 +610,28 @@ async def create_subtask(
await db.commit()
return _to_response(subtask)
# ============================================================
# 任务进度记录 — 随时备注/传图
# ============================================================
async def add_task_record(
db: AsyncSession, task_id: uuid.UUID, data: TaskRecordCreate
) -> TaskResponse:
"""追加进度记录(备注+图片),不改变任务状态"""
import json
task = await _get_task_with_children_recursive(db, task_id)
record = TaskRecord(
task_id=task_id,
remark=data.remark or None,
images=json.dumps(data.images) if data.images else None,
)
db.add(record)
await db.commit()
await db.refresh(record)
# 重新加载 task 带上新 record
return await get_task(db, task_id)