from __future__ import annotations from flask import g, jsonify, request from app import db from app.api.system import bp from app.core.governance.responsibilities import ( ResponsibilityConflict, ResponsibilityService, ResponsibilityUserUnavailable, ResponsibilityValidationError, SqlAlchemyResponsibilityRepository, ) from app.core.governance.unified_responsibilities import ( UnifiedResponsibilityService, ) from app.core.governance.unified_responsibility_repository import ( SqlAlchemyUnifiedResponsibilityRepository, ) from app.core.system.permissions import ( RESPONSIBILITIES_MANAGE, RESPONSIBILITIES_READ, require_permissions, ) from app.models.result import failed, success def _service() -> ResponsibilityService: return ResponsibilityService(SqlAlchemyResponsibilityRepository(db.session)) def _unified_service() -> UnifiedResponsibilityService: return UnifiedResponsibilityService( SqlAlchemyUnifiedResponsibilityRepository(db.session), commit=db.session.commit, rollback=db.session.rollback, ) def _etag(response, revision: int): response.headers["ETag"] = f'"{int(revision)}"' return response def _expected_revision() -> int: raw = str(request.headers.get("If-Match") or "").strip() if raw.startswith("W/"): raw = raw[2:].strip() raw = raw.strip('"') if not raw or not raw.isdigit(): raise ResponsibilityValidationError("missing valid If-Match revision") return int(raw) def _unified_error(exc: Exception): db.session.rollback() message = str(exc) if "If-Match" in message: status = 428 elif isinstance(exc, LookupError): status = 404 elif isinstance(exc, RuntimeError): status = 409 else: status = 400 return jsonify(failed(message, code=status)), status @bp.route( "/responsibilities//", methods=["GET"], ) @require_permissions(RESPONSIBILITIES_READ) def get_responsibility_matrix(resource_type: str, resource_uid: str): try: result = _service().get(resource_type, resource_uid) return _etag(jsonify(success(result)), result["revision"]) except ResponsibilityValidationError as exc: return jsonify(failed(str(exc), code=400)), 400 @bp.route( "/responsibilities//", methods=["PUT"], ) @require_permissions(RESPONSIBILITIES_MANAGE) def replace_responsibility_matrix(resource_type: str, resource_uid: str): try: revision = _expected_revision() body = request.get_json(silent=True) or {} result = _service().replace( resource_type=resource_type, resource_uid=resource_uid, assignments=body.get("assignments"), expected_revision=revision, actor_uid=g.current_user["id"], ) db.session.commit() return _etag(jsonify(success(result, "责任矩阵已更新")), result["revision"]) except ResponsibilityConflict as exc: db.session.rollback() return jsonify(failed(str(exc), code=409)), 409 except ResponsibilityUserUnavailable as exc: db.session.rollback() return jsonify(failed(str(exc), code=422)), 422 except ResponsibilityValidationError as exc: db.session.rollback() status = 428 if "If-Match" in str(exc) else 400 return jsonify(failed(str(exc), code=status)), status @bp.route( "/responsibilities///resolved", methods=["GET"], ) @require_permissions(RESPONSIBILITIES_READ) def resolve_responsibility(resource_type: str, resource_uid: str): try: return jsonify( success( _unified_service().resolve( resource_type, resource_uid, at=request.args.get("at"), ) ) ) except (ValueError, LookupError, RuntimeError) as exc: return _unified_error(exc) @bp.route( "/responsibilities/hierarchy//", methods=["PUT"], ) @require_permissions(RESPONSIBILITIES_MANAGE) def replace_responsibility_parent(resource_type: str, resource_uid: str): try: body = request.get_json(silent=True) or {} result = _unified_service().set_parent( { **body, "resource_type": resource_type, "resource_uid": resource_uid, }, expected_revision=_expected_revision(), actor_uid=g.current_user["id"], ) return _etag( jsonify(success(result, "责任继承关系已更新")), result["revision"] ) except (ValueError, LookupError, RuntimeError, ResponsibilityValidationError) as exc: return _unified_error(exc) @bp.route("/responsibilities/delegations", methods=["GET"]) @require_permissions(RESPONSIBILITIES_READ) def list_responsibility_delegations(): try: return jsonify( success( _unified_service().list_delegations( status=request.args.get("status") ) ) ) except (ValueError, RuntimeError) as exc: return _unified_error(exc) @bp.route("/responsibilities/delegations", methods=["POST"]) @require_permissions(RESPONSIBILITIES_MANAGE) def create_responsibility_delegation(): try: result = _unified_service().create_delegation( request.get_json(silent=True) or {}, actor_uid=g.current_user["id"], ) return jsonify(success(result, "责任委派已创建", code=201)), 201 except (ValueError, RuntimeError) as exc: return _unified_error(exc) @bp.route( "/responsibilities/delegations//revoke", methods=["POST"], ) @require_permissions(RESPONSIBILITIES_MANAGE) def revoke_responsibility_delegation(delegation_uid: str): try: result = _unified_service().revoke_delegation( delegation_uid, expected_version=_expected_revision(), actor_uid=g.current_user["id"], ) return _etag( jsonify(success(result, "责任委派已收回")), result["current_version"] ) except (ValueError, LookupError, RuntimeError, ResponsibilityValidationError) as exc: return _unified_error(exc) @bp.route("/responsibilities/delegations/expire", methods=["POST"]) @require_permissions(RESPONSIBILITIES_MANAGE) def expire_responsibility_delegations(): try: body = request.get_json(silent=True) or {} return jsonify( success( _unified_service().expire_delegations( at=body.get("at"), actor_uid=g.current_user["id"] ), "到期委派已收回", ) ) except (ValueError, RuntimeError) as exc: return _unified_error(exc) @bp.route("/responsibilities/transfers", methods=["POST"]) @require_permissions(RESPONSIBILITIES_MANAGE) def transfer_departing_responsibility(): try: result = _unified_service().transfer_departing_user( request.get_json(silent=True) or {}, actor_uid=g.current_user["id"], ) return jsonify(success(result, "离岗责任已转交", code=201)), 201 except (ValueError, RuntimeError) as exc: return _unified_error(exc) @bp.route("/responsibilities/policies", methods=["GET"]) @require_permissions(RESPONSIBILITIES_READ) def list_responsibility_policies(): try: return jsonify(success(_unified_service().list_policies())) except RuntimeError as exc: return _unified_error(exc) @bp.route("/responsibilities/policies", methods=["POST"]) @require_permissions(RESPONSIBILITIES_MANAGE) def create_responsibility_policy(): try: result = _unified_service().create_policy( 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, RuntimeError) as exc: return _unified_error(exc) @bp.route( "/responsibilities/policies//revisions", methods=["POST"], ) @require_permissions(RESPONSIBILITIES_MANAGE) def revise_responsibility_policy(policy_uid: str): try: body = request.get_json(silent=True) or {} result = _unified_service().revise_policy( policy_uid, body.get("definition"), expected_version=_expected_revision(), actor_uid=g.current_user["id"], ) return _etag( jsonify(success(result, "责任策略新版本已创建")), result["current_version"], ) except (ValueError, LookupError, RuntimeError, ResponsibilityValidationError) as exc: return _unified_error(exc) @bp.route( "/responsibilities/policies//publish", methods=["POST"], ) @require_permissions(RESPONSIBILITIES_MANAGE) def publish_responsibility_policy(policy_uid: str): try: result = _unified_service().publish_policy( policy_uid, expected_version=_expected_revision(), actor_uid=g.current_user["id"], ) return _etag( jsonify(success(result, "责任策略已发布")), result["current_version"], ) except (ValueError, LookupError, RuntimeError, ResponsibilityValidationError) as exc: return _unified_error(exc) @bp.route( "/responsibilities///joint-review/evaluate", methods=["POST"], ) @require_permissions(RESPONSIBILITIES_MANAGE) def evaluate_responsibility_joint_review(resource_type: str, resource_uid: str): try: body = request.get_json(silent=True) or {} return jsonify( success( _unified_service().evaluate_joint_review( resource_type, resource_uid, body.get("decisions") ) ) ) except (ValueError, LookupError, RuntimeError) as exc: return _unified_error(exc) @bp.route("/responsibilities/operations", methods=["GET"]) @require_permissions(RESPONSIBILITIES_READ) def responsibility_operations(): try: return jsonify( success( _unified_service().operations(request.args.get("owner_uid")) ) ) except (ValueError, RuntimeError) as exc: return _unified_error(exc)