| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- """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/<workflow_uid>/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/<workflow_uid>/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/<task_uid>", 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/<task_uid>/<action>", 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/<task_uid>/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/<task_uid>/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/<notification_uid>/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/<template_uid>", 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)
|