支持具备 changeApplicant 权限时代填申请人及发票/余额联动;系统/CRM/BX/PL 主数据与下拉选项改为仅租户隔离,避免「仅本人」导致空选项。 Co-authored-by: Cursor <cursoragent@cursor.com>
136 lines
4.5 KiB
Python
136 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import List, Optional
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.bx_auth import BxUserContext
|
|
from app.core.tenant_context import get_tenant_no
|
|
from app.models.bx_models import BxExpenseItem
|
|
from app.services.bx.base import BxCrudService
|
|
|
|
|
|
class ExpenseItemService:
|
|
@staticmethod
|
|
def _tenant_no(db: Session, user: BxUserContext) -> str:
|
|
return get_tenant_no(db, user.to_tenant_user_dict())
|
|
|
|
@staticmethod
|
|
def get_by_id(
|
|
db: Session,
|
|
item_id: str,
|
|
tenant_no: str,
|
|
*,
|
|
user: Optional[BxUserContext] = None,
|
|
skip_scope: bool = True,
|
|
) -> Optional[BxExpenseItem]:
|
|
# 费用项目为主数据:默认跳过 create_by 数据范围
|
|
return BxCrudService.get_by_id(
|
|
BxExpenseItem, db, item_id, tenant_no, user=user, skip_scope=skip_scope
|
|
)
|
|
|
|
@staticmethod
|
|
def create(
|
|
db: Session,
|
|
obj_in: dict,
|
|
*,
|
|
user: BxUserContext,
|
|
) -> BxExpenseItem:
|
|
tenant_no = ExpenseItemService._tenant_no(db, user)
|
|
data = dict(obj_in)
|
|
parent_id = data.get("parent_id") or "0"
|
|
data["parent_id"] = parent_id
|
|
if parent_id and parent_id != "0":
|
|
parent = ExpenseItemService.get_by_id(db, parent_id, tenant_no, user=user)
|
|
if not parent:
|
|
raise ValueError("父级费用项目不存在")
|
|
data.setdefault("level", (parent.level or 1) + 1)
|
|
else:
|
|
data["parent_id"] = "0"
|
|
data.setdefault("level", 1)
|
|
return BxCrudService.create(
|
|
BxExpenseItem,
|
|
db,
|
|
data,
|
|
tenant_no=tenant_no,
|
|
user_id=user.id,
|
|
dept_id=user.dept_id,
|
|
)
|
|
|
|
@staticmethod
|
|
def update(
|
|
db: Session,
|
|
item_id: str,
|
|
obj_in: dict,
|
|
*,
|
|
user: BxUserContext,
|
|
) -> Optional[BxExpenseItem]:
|
|
tenant_no = ExpenseItemService._tenant_no(db, user)
|
|
data = dict(obj_in)
|
|
if "parent_id" in data and data["parent_id"] is not None:
|
|
parent_id = data["parent_id"] or "0"
|
|
data["parent_id"] = parent_id
|
|
if parent_id != "0":
|
|
if parent_id == item_id:
|
|
raise ValueError("不能将项目设置为自己的父级")
|
|
parent = ExpenseItemService.get_by_id(db, parent_id, tenant_no, user=user)
|
|
if not parent:
|
|
raise ValueError("父级费用项目不存在")
|
|
return BxCrudService.update(
|
|
BxExpenseItem,
|
|
db,
|
|
item_id,
|
|
tenant_no,
|
|
data,
|
|
user_id=user.id,
|
|
user=user,
|
|
skip_scope=True,
|
|
)
|
|
|
|
@staticmethod
|
|
def soft_delete(db: Session, item_id: str, *, user: BxUserContext) -> bool:
|
|
tenant_no = ExpenseItemService._tenant_no(db, user)
|
|
return BxCrudService.soft_delete(
|
|
BxExpenseItem,
|
|
db,
|
|
item_id,
|
|
tenant_no,
|
|
user_id=user.id,
|
|
user=user,
|
|
skip_scope=True,
|
|
)
|
|
|
|
@staticmethod
|
|
def get_item_tree(db: Session, tenant_no: str, *, user: BxUserContext) -> List[BxExpenseItem]:
|
|
# 费用项目是租户级主数据,供报销/备用金选择;不做 create_by 数据范围过滤,
|
|
# 否则「仅本人」角色看不到管理员维护的费用项目。
|
|
_ = user
|
|
query = db.query(BxExpenseItem).filter(
|
|
BxExpenseItem.del_flag == 0, BxExpenseItem.tenant_no == tenant_no
|
|
)
|
|
items = query.order_by(
|
|
BxExpenseItem.sort_order.asc(), BxExpenseItem.create_time.desc()
|
|
).all()
|
|
return ExpenseItemService._build_tree(items)
|
|
|
|
@staticmethod
|
|
def _build_tree(items: List[BxExpenseItem]) -> List[BxExpenseItem]:
|
|
item_map = {item.id: item for item in items}
|
|
roots: List[BxExpenseItem] = []
|
|
for item in items:
|
|
parent_id = item.parent_id or "0"
|
|
if not parent_id or parent_id == "0":
|
|
roots.append(item)
|
|
else:
|
|
parent = item_map.get(parent_id)
|
|
if parent:
|
|
if not hasattr(parent, "children") or parent.children is None:
|
|
parent.children = []
|
|
parent.children.append(item)
|
|
for item in items:
|
|
if hasattr(item, "children") and item.children:
|
|
item.child_count = len(item.children)
|
|
else:
|
|
item.child_count = 0
|
|
return roots
|