"""Unified governance work-center API.""" from __future__ import annotations from flask import g, jsonify, request from app import db from app.api.system import bp from app.core.events.email_delivery import smtp_sender from app.core.governance.work_center import UnifiedWorkCenterService from app.core.governance.work_center_repository import SqlAlchemyWorkCenterRepository from app.core.system.permissions import ( WORK_CENTER_MANAGE, WORK_CENTER_OPERATE, WORK_CENTER_READ, permissions_for_roles, require_permissions, ) from app.models.result import failed, success def _service(): return UnifiedWorkCenterService( SqlAlchemyWorkCenterRepository(db.session), commit=db.session.commit, rollback=db.session.rollback, ) def _expected_version() -> int: raw = str(request.headers.get("If-Match") or "").strip() if raw.startswith("W/"): raw = raw[2:].strip() raw = raw.strip('"') if not raw.isdigit(): raise ValueError("missing valid If-Match version") return int(raw) def _etag(response, version): response.headers["ETag"] = f'"{int(version)}"' return response def _error(exc): db.session.rollback() if "If-Match" in str(exc): status = 428 elif isinstance(exc, LookupError): status = 404 elif isinstance(exc, PermissionError): status = 403 elif isinstance(exc, RuntimeError): status = 409 else: status = 400 return jsonify(failed(str(exc), code=status)), status @bp.route("/work-center/workflows", methods=["GET"]) @require_permissions(WORK_CENTER_READ) def list_work_center_workflows(): return jsonify(success(_service().list_workflows())) @bp.route("/work-center/workflows", methods=["POST"]) @require_permissions(WORK_CENTER_MANAGE) def create_work_center_workflow(): try: result = _service().create_workflow( request.get_json(silent=True) or {}, actor_uid=g.current_user["id"] ) return _etag(jsonify(success(result, "流程草稿已创建", code=201)), 1), 201 except (ValueError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/workflows//revisions", methods=["POST"]) @require_permissions(WORK_CENTER_MANAGE) def revise_work_center_workflow(workflow_uid): try: result = _service().revise_workflow( workflow_uid, request.get_json(silent=True) or {}, expected_version=_expected_version(), actor_uid=g.current_user["id"], ) return _etag(jsonify(success(result, "流程版本已创建")), result["current_version"]) except (ValueError, LookupError, PermissionError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/workflows//publish", methods=["POST"]) @require_permissions(WORK_CENTER_MANAGE) def publish_work_center_workflow(workflow_uid): try: result = _service().publish_workflow( workflow_uid, expected_version=_expected_version(), actor_uid=g.current_user["id"], ) return _etag(jsonify(success(result, "流程版本已发布")), result["current_version"]) except (ValueError, LookupError, PermissionError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/tasks", methods=["GET"]) @require_permissions(WORK_CENTER_READ) def list_work_center_tasks(): permissions = permissions_for_roles(g.current_user.get("roles", [])) filters = { key: request.args.get(key) for key in ("status", "task_type", "subject_type", "assignee_uid") if request.args.get(key) } filters.update( { "requester_uid": g.current_user["id"], "can_manage": WORK_CENTER_MANAGE in permissions, } ) return jsonify(success(_service().list_tasks(**filters))) @bp.route("/work-center/tasks", methods=["POST"]) @require_permissions(WORK_CENTER_OPERATE) def create_work_center_task(): try: result = _service().create_task( request.get_json(silent=True) or {}, actor_uid=g.current_user["id"] ) return _etag(jsonify(success(result, "统一任务已创建", code=201)), result["current_version"]), 201 except (ValueError, LookupError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/tasks/", methods=["GET"]) @require_permissions(WORK_CENTER_READ) def get_work_center_task(task_uid): try: permissions = permissions_for_roles(g.current_user.get("roles", [])) result = _service().task_detail( task_uid, requester_uid=g.current_user["id"], can_manage=WORK_CENTER_MANAGE in permissions, ) return _etag(jsonify(success(result)), result["current_version"]) except (ValueError, LookupError) as exc: return _error(exc) def _task_action(task_uid, action): service = _service() body = request.get_json(silent=True) or {} arguments = { "expected_version": _expected_version(), "actor_uid": g.current_user["id"], } if action == "review": return service.review_task(task_uid, body, **arguments) if action == "transfer": return service.transfer_review(task_uid, body, **arguments) if action == "close": return service.close_task(task_uid, body, **arguments) return service.reopen_task(task_uid, body, **arguments) @bp.route("/work-center/tasks//", methods=["POST"]) @require_permissions(WORK_CENTER_OPERATE) def operate_work_center_task(task_uid, action): if action not in {"review", "transfer", "close", "reopen"}: return jsonify(failed("unsupported task action", code=404)), 404 try: result = _task_action(task_uid, action) return _etag(jsonify(success(result, "任务状态已更新")), result["current_version"]) except (ValueError, LookupError, PermissionError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/tasks//comments", methods=["POST"]) @require_permissions(WORK_CENTER_OPERATE) def add_work_center_comment(task_uid): try: result = _service().add_comment( task_uid, request.get_json(silent=True) or {}, actor_uid=g.current_user["id"], ) return jsonify(success(result, "评论已添加", code=201)), 201 except (ValueError, LookupError, PermissionError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/tasks//attachments", methods=["POST"]) @require_permissions(WORK_CENTER_OPERATE) def add_work_center_attachment(task_uid): try: result = _service().add_attachment( task_uid, request.get_json(silent=True) or {}, actor_uid=g.current_user["id"], ) return jsonify(success(result, "附件证据已登记", code=201)), 201 except (ValueError, LookupError, PermissionError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/timeouts/process", methods=["POST"]) @require_permissions(WORK_CENTER_MANAGE) def process_work_center_timeouts(): try: body = request.get_json(silent=True) or {} return jsonify( success( _service().process_timeouts( at=body.get("at"), actor_uid=g.current_user["id"] ), "逾期任务已处理", ) ) except (ValueError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/notifications", methods=["GET"]) @require_permissions(WORK_CENTER_READ) def list_work_center_notifications(): return jsonify( success( _service().list_notifications( g.current_user["id"], unread_only=request.args.get("unread_only") == "true", ) ) ) @bp.route("/work-center/notifications//read", methods=["POST"]) @require_permissions(WORK_CENTER_OPERATE) def read_work_center_notification(notification_uid): try: return jsonify( success( _service().mark_notification_read( notification_uid, actor_uid=g.current_user["id"] ), "消息已读", ) ) except (ValueError, LookupError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/notifications/deliver", methods=["POST"]) @require_permissions(WORK_CENTER_MANAGE) def deliver_work_center_notifications(): try: body = request.get_json(silent=True) or {} result = _service().deliver_notifications( "email", smtp_sender, at=body.get("at"), limit=body.get("limit", 50) ) return jsonify(success(result, "邮件投递批次已处理")) except (ValueError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/templates", methods=["GET"]) @require_permissions(WORK_CENTER_READ) def list_work_center_templates(): return jsonify(success(_service().list_notification_templates())) @bp.route("/work-center/templates", methods=["POST"]) @require_permissions(WORK_CENTER_MANAGE) def create_work_center_template(): try: result = _service().create_notification_template( request.get_json(silent=True) or {}, actor_uid=g.current_user["id"] ) return _etag(jsonify(success(result, "通知模板已创建", code=201)), 1), 201 except (ValueError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/templates/", methods=["PATCH"]) @require_permissions(WORK_CENTER_MANAGE) def revise_work_center_template(template_uid): try: result = _service().revise_notification_template( template_uid, request.get_json(silent=True) or {}, expected_version=_expected_version(), actor_uid=g.current_user["id"], ) return _etag(jsonify(success(result, "通知模板已更新")), result["current_version"]) except (ValueError, LookupError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/preferences", methods=["GET"]) @require_permissions(WORK_CENTER_READ) def get_work_center_preferences(): result = _service().get_notification_preferences(g.current_user["id"]) return _etag(jsonify(success(result)), result["revision"]) @bp.route("/work-center/preferences", methods=["PUT"]) @require_permissions(WORK_CENTER_OPERATE) def replace_work_center_preferences(): try: result = _service().replace_notification_preferences( request.get_json(silent=True) or {}, expected_revision=_expected_version(), actor_uid=g.current_user["id"], ) return _etag(jsonify(success(result, "通知偏好已更新")), result["revision"]) except (ValueError, RuntimeError) as exc: return _error(exc) @bp.route("/work-center/dashboard", methods=["GET"]) @require_permissions(WORK_CENTER_READ) def get_work_center_dashboard(): try: return jsonify(success(_service().dashboard(at=request.args.get("at")))) except ValueError as exc: return _error(exc)