Files
zkxw/backend/app/services/market_field_scope.py
T

210 lines
5.6 KiB
Python

"""移动市场专员/地区经理的现场字段范围。"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
from fastapi import HTTPException
from sqlalchemy import or_
from sqlalchemy.orm import Session
from app.core.data_scope import apply_data_scope
from app.models.models import (
BizCollectionProtocol,
BizCollectionRegionStaff,
BizPresignOrder,
BizProtocolCollectionInfo,
)
from app.models.research_donation import (
BizResearchDonation,
BizResearchDonationCollection,
)
ScopeKind = Literal["specialist", "manager"]
@dataclass(frozen=True)
class MarketFieldContext:
"""由固定移动路由创建的现场范围上下文。"""
tenant_no: str
user_id: str
scope_kind: ScopeKind
def __post_init__(self) -> None:
tenant_no = (self.tenant_no or "").strip()
user_id = (self.user_id or "").strip()
if not tenant_no or not user_id:
raise ValueError("现场范围要求明确的租户和用户")
if self.scope_kind not in ("specialist", "manager"):
raise ValueError("不支持的现场范围类型")
object.__setattr__(self, "tenant_no", tenant_no)
object.__setattr__(self, "user_id", user_id)
def leader_region_ids_query(db: Session, ctx: MarketFieldContext):
"""返回当前经理在当前租户有效负责的地区 ID 查询。"""
return db.query(BizCollectionRegionStaff.region_id).filter(
BizCollectionRegionStaff.tenant_no == ctx.tenant_no,
BizCollectionRegionStaff.del_flag == 0,
BizCollectionRegionStaff.user_id == ctx.user_id,
BizCollectionRegionStaff.is_leader == 1,
)
def _collection_scope_condition(model, db: Session, ctx: MarketFieldContext):
if ctx.scope_kind == "specialist":
return model.sales_user_id == ctx.user_id
return or_(
model.region_id.in_(leader_region_ids_query(db, ctx)),
model.region_manager_user_id == ctx.user_id,
)
def apply_market_data_scope(
query,
model,
db: Session,
ctx: MarketFieldContext,
user_info: dict,
):
"""组合租户、软删与通用多角色数据权限。"""
user_id = (
str(user_info.get("user_id") or "").strip()
if isinstance(user_info, dict)
else ""
)
if not user_id or user_id != ctx.user_id:
raise PermissionError("用户身份与现场范围上下文不一致")
query = query.filter(
model.tenant_no == ctx.tenant_no,
model.del_flag == 0,
)
return apply_data_scope(
query,
model,
user_info,
db,
tenant_no=ctx.tenant_no,
)
def apply_presign_scope(
query,
db: Session,
ctx: MarketFieldContext,
*,
user_info: dict,
):
"""组合租户、软删、通用数据权限与预签现场范围。"""
query = apply_market_data_scope(
query, BizPresignOrder, db, ctx, user_info
)
return query.filter(_collection_scope_condition(BizPresignOrder, db, ctx))
def apply_cell_protocol_scope(
query,
db: Session,
ctx: MarketFieldContext,
*,
user_info: dict,
):
"""按细胞协议的有效采集信息施加现场范围。"""
query = apply_market_data_scope(
query, BizCollectionProtocol, db, ctx, user_info
)
collection_exists = (
db.query(BizProtocolCollectionInfo.id)
.filter(
BizProtocolCollectionInfo.collection_protocol_id
== BizCollectionProtocol.id,
BizProtocolCollectionInfo.tenant_no == ctx.tenant_no,
BizProtocolCollectionInfo.del_flag == 0,
_collection_scope_condition(BizProtocolCollectionInfo, db, ctx),
)
.exists()
.correlate(BizCollectionProtocol)
)
return query.filter(collection_exists)
def apply_donation_scope(
query,
db: Session,
ctx: MarketFieldContext,
*,
user_info: dict,
):
"""按科研捐赠的有效采集信息施加现场范围。"""
query = apply_market_data_scope(
query, BizResearchDonation, db, ctx, user_info
)
collection_exists = (
db.query(BizResearchDonationCollection.id)
.filter(
BizResearchDonationCollection.research_donation_id
== BizResearchDonation.id,
BizResearchDonationCollection.tenant_no == ctx.tenant_no,
BizResearchDonationCollection.del_flag == 0,
_collection_scope_condition(
BizResearchDonationCollection, db, ctx
),
)
.exists()
.correlate(BizResearchDonation)
)
return query.filter(collection_exists)
def _assert_access(query, record_id: str):
row = query.filter_by(id=record_id).first()
if row is None:
raise HTTPException(status_code=404, detail="数据不存在或无权访问")
return row
def assert_presign_access(
db: Session,
record_id: str,
ctx: MarketFieldContext,
*,
user_info: dict,
):
return _assert_access(
apply_presign_scope(
db.query(BizPresignOrder), db, ctx, user_info=user_info
),
record_id,
)
def assert_cell_protocol_access(
db: Session,
record_id: str,
ctx: MarketFieldContext,
*,
user_info: dict,
):
return _assert_access(
apply_cell_protocol_scope(
db.query(BizCollectionProtocol), db, ctx, user_info=user_info
),
record_id,
)
def assert_donation_access(
db: Session,
record_id: str,
ctx: MarketFieldContext,
*,
user_info: dict,
):
return _assert_access(
apply_donation_scope(
db.query(BizResearchDonation), db, ctx, user_info=user_info
),
record_id,
)