921 lines
28 KiB
Python
921 lines
28 KiB
Python
"""OA 审批 CRUD(工作流角色、分组、应用、草稿等)"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional, Tuple
|
|
|
|
from sqlalchemy import and_
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.db_collation import eq_unicode
|
|
from app.core.utils import generate_uuid
|
|
from app.models.models import OaApp, OaAppGroup, OaAppVersion, OaRole, OaRoleUser, SysUser
|
|
|
|
DEFAULT_ADVANCED_SETTINGS: Dict[str, Any] = {
|
|
"supervisorFallbackToDeptLeader": True,
|
|
"allowWithdrawRunning": True,
|
|
"approverDedup": False,
|
|
"commentRequired": False,
|
|
"allowAddSign": True,
|
|
"allowReturn": True,
|
|
"autoPassWhenApproverIsInitiator": False,
|
|
}
|
|
|
|
|
|
def _dumps(data: Any) -> Optional[str]:
|
|
if data is None:
|
|
return None
|
|
if isinstance(data, str):
|
|
return data
|
|
return json.dumps(data, ensure_ascii=False)
|
|
|
|
|
|
def _loads(text: Optional[str], default: Any = None) -> Any:
|
|
if text is None or text == "":
|
|
return default
|
|
if not isinstance(text, str):
|
|
return text
|
|
try:
|
|
return json.loads(text)
|
|
except (TypeError, ValueError):
|
|
return default
|
|
|
|
PRESET_ROLES = (
|
|
("GM", "总经理"),
|
|
("CHAIRMAN", "董事长"),
|
|
)
|
|
|
|
|
|
def _role_to_dict(r: OaRole, *, member_count: int = 0) -> Dict[str, Any]:
|
|
return {
|
|
"id": r.id,
|
|
"roleName": r.role_name,
|
|
"roleCode": r.role_code,
|
|
"roleKind": r.role_kind,
|
|
"status": r.status,
|
|
"tenantNo": r.tenant_no,
|
|
"orderNum": r.order_num if r.order_num is not None else 0,
|
|
"remark": r.remark or "",
|
|
"memberCount": member_count,
|
|
"createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
|
|
"updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
|
|
}
|
|
|
|
|
|
def ensure_presets(db: Session, tenant_no: str, user_id: Optional[str] = None) -> List[OaRole]:
|
|
"""幂等写入预置角色 GM / CHAIRMAN。"""
|
|
now = datetime.now()
|
|
created: List[OaRole] = []
|
|
for code, name in PRESET_ROLES:
|
|
exists = (
|
|
db.query(OaRole)
|
|
.filter(
|
|
OaRole.tenant_no == tenant_no,
|
|
OaRole.role_code == code,
|
|
OaRole.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if exists:
|
|
continue
|
|
obj = OaRole(
|
|
id=generate_uuid(),
|
|
role_name=name,
|
|
role_code=code,
|
|
role_kind="preset",
|
|
status="1",
|
|
tenant_no=tenant_no,
|
|
order_num=0 if code == "GM" else 1,
|
|
remark="系统预置工作流角色",
|
|
create_by=user_id,
|
|
create_dept_id="",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
db.add(obj)
|
|
created.append(obj)
|
|
if created:
|
|
db.commit()
|
|
for obj in created:
|
|
db.refresh(obj)
|
|
return created
|
|
|
|
|
|
def _member_count_map(db: Session, role_ids: List[str], tenant_no: str) -> Dict[str, int]:
|
|
if not role_ids:
|
|
return {}
|
|
rows = (
|
|
db.query(OaRoleUser.oa_role_id, OaRoleUser.id)
|
|
.filter(
|
|
OaRoleUser.oa_role_id.in_(role_ids),
|
|
OaRoleUser.tenant_no == tenant_no,
|
|
OaRoleUser.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
counts: Dict[str, int] = {rid: 0 for rid in role_ids}
|
|
for rid, _ in rows:
|
|
counts[rid] = counts.get(rid, 0) + 1
|
|
return counts
|
|
|
|
|
|
def list_roles(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
keyword: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
) -> List[Dict[str, Any]]:
|
|
ensure_presets(db, tenant_no)
|
|
q = db.query(OaRole).filter(OaRole.tenant_no == tenant_no, OaRole.del_flag == 0)
|
|
kw = (keyword or "").strip()
|
|
if kw:
|
|
like = f"%{kw}%"
|
|
q = q.filter((OaRole.role_name.like(like)) | (OaRole.role_code.like(like)))
|
|
if status:
|
|
q = q.filter(OaRole.status == status)
|
|
rows = q.order_by(OaRole.role_kind.desc(), OaRole.order_num.asc(), OaRole.create_time.desc()).all()
|
|
counts = _member_count_map(db, [r.id for r in rows], tenant_no)
|
|
return [_role_to_dict(r, member_count=counts.get(r.id, 0)) for r in rows]
|
|
|
|
|
|
def get_role(db: Session, role_id: str, tenant_no: str) -> Optional[OaRole]:
|
|
return (
|
|
db.query(OaRole)
|
|
.filter(OaRole.id == role_id, OaRole.tenant_no == tenant_no, OaRole.del_flag == 0)
|
|
.first()
|
|
)
|
|
|
|
|
|
def create_role(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
role_name: str,
|
|
role_code: str,
|
|
status: str = "1",
|
|
order_num: Optional[int] = None,
|
|
remark: Optional[str] = None,
|
|
user_id: Optional[str] = None,
|
|
) -> OaRole:
|
|
name = (role_name or "").strip()
|
|
code = (role_code or "").strip().upper()
|
|
if not name:
|
|
raise ValueError("角色名称不能为空")
|
|
if not code:
|
|
raise ValueError("角色编码不能为空")
|
|
if code in {c for c, _ in PRESET_ROLES}:
|
|
raise ValueError("该编码为系统预置,请勿占用")
|
|
exists = (
|
|
db.query(OaRole)
|
|
.filter(OaRole.tenant_no == tenant_no, OaRole.role_code == code, OaRole.del_flag == 0)
|
|
.first()
|
|
)
|
|
if exists:
|
|
raise ValueError("角色编码已存在")
|
|
now = datetime.now()
|
|
obj = OaRole(
|
|
id=generate_uuid(),
|
|
role_name=name,
|
|
role_code=code,
|
|
role_kind="custom",
|
|
status=status or "1",
|
|
tenant_no=tenant_no,
|
|
order_num=order_num if order_num is not None else 10,
|
|
remark=(remark or "").strip() or None,
|
|
create_by=user_id,
|
|
create_dept_id="",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
db.add(obj)
|
|
db.commit()
|
|
db.refresh(obj)
|
|
return obj
|
|
|
|
|
|
def update_role(
|
|
db: Session,
|
|
role_id: str,
|
|
*,
|
|
tenant_no: str,
|
|
role_name: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
order_num: Optional[int] = None,
|
|
remark: Optional[str] = None,
|
|
user_id: Optional[str] = None,
|
|
) -> OaRole:
|
|
obj = get_role(db, role_id, tenant_no)
|
|
if not obj:
|
|
raise ValueError("角色不存在")
|
|
if role_name is not None:
|
|
name = (role_name or "").strip()
|
|
if not name:
|
|
raise ValueError("角色名称不能为空")
|
|
obj.role_name = name
|
|
if status is not None:
|
|
obj.status = status
|
|
if order_num is not None:
|
|
obj.order_num = order_num
|
|
if remark is not None:
|
|
obj.remark = (remark or "").strip() or None
|
|
obj.update_by = user_id
|
|
obj.update_time = datetime.now()
|
|
db.add(obj)
|
|
db.commit()
|
|
db.refresh(obj)
|
|
return obj
|
|
|
|
|
|
def delete_role(db: Session, role_id: str, *, tenant_no: str, user_id: Optional[str] = None) -> None:
|
|
obj = get_role(db, role_id, tenant_no)
|
|
if not obj:
|
|
raise ValueError("角色不存在")
|
|
if obj.role_kind == "preset":
|
|
raise ValueError("预置角色不可删除")
|
|
obj.del_flag = 1
|
|
obj.update_by = user_id
|
|
obj.update_time = datetime.now()
|
|
db.add(obj)
|
|
db.commit()
|
|
|
|
|
|
def list_role_users(db: Session, role_id: str, tenant_no: str) -> List[Dict[str, Any]]:
|
|
rows = (
|
|
db.query(OaRoleUser, SysUser)
|
|
.outerjoin(
|
|
SysUser,
|
|
and_(eq_unicode(SysUser.id, OaRoleUser.user_id), SysUser.del_flag == 0),
|
|
)
|
|
.filter(
|
|
OaRoleUser.oa_role_id == role_id,
|
|
OaRoleUser.tenant_no == tenant_no,
|
|
OaRoleUser.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
out: List[Dict[str, Any]] = []
|
|
for ru, u in rows:
|
|
out.append(
|
|
{
|
|
"id": ru.id,
|
|
"userId": ru.user_id,
|
|
"nickName": (u.nick_name if u else "") or "",
|
|
"userNo": (u.user_no if u else "") or "",
|
|
}
|
|
)
|
|
return out
|
|
|
|
|
|
def replace_role_users(
|
|
db: Session,
|
|
role_id: str,
|
|
*,
|
|
tenant_no: str,
|
|
user_ids: List[str],
|
|
operator_id: Optional[str] = None,
|
|
) -> None:
|
|
obj = get_role(db, role_id, tenant_no)
|
|
if not obj:
|
|
raise ValueError("角色不存在")
|
|
now = datetime.now()
|
|
existing = (
|
|
db.query(OaRoleUser)
|
|
.filter(
|
|
OaRoleUser.oa_role_id == role_id,
|
|
OaRoleUser.tenant_no == tenant_no,
|
|
OaRoleUser.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
for row in existing:
|
|
row.del_flag = 1
|
|
row.update_by = operator_id
|
|
row.update_time = now
|
|
db.add(row)
|
|
seen = set()
|
|
for uid in user_ids or []:
|
|
uid = (uid or "").strip()
|
|
if not uid or uid in seen:
|
|
continue
|
|
seen.add(uid)
|
|
db.add(
|
|
OaRoleUser(
|
|
id=generate_uuid(),
|
|
oa_role_id=role_id,
|
|
user_id=uid,
|
|
tenant_no=tenant_no,
|
|
order_num=0,
|
|
remark=None,
|
|
create_by=operator_id,
|
|
create_dept_id="",
|
|
create_time=now,
|
|
update_by=operator_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
)
|
|
db.commit()
|
|
|
|
|
|
def list_oa_role_user_ids(db: Session, role_ids: List[str], tenant_no: str) -> List[str]:
|
|
if not role_ids:
|
|
return []
|
|
enabled = {
|
|
r.id
|
|
for r in db.query(OaRole)
|
|
.filter(
|
|
OaRole.id.in_(role_ids),
|
|
OaRole.tenant_no == tenant_no,
|
|
OaRole.del_flag == 0,
|
|
OaRole.status == "1",
|
|
)
|
|
.all()
|
|
}
|
|
if not enabled:
|
|
return []
|
|
rows = (
|
|
db.query(OaRoleUser.user_id, OaRoleUser.oa_role_id)
|
|
.filter(
|
|
OaRoleUser.oa_role_id.in_(list(enabled)),
|
|
OaRoleUser.tenant_no == tenant_no,
|
|
OaRoleUser.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
out: List[str] = []
|
|
seen = set()
|
|
for uid, rid in rows:
|
|
if rid in enabled and uid and uid not in seen:
|
|
seen.add(uid)
|
|
out.append(uid)
|
|
return out
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 分组 oa_app_group
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _group_to_dict(g: OaAppGroup) -> Dict[str, Any]:
|
|
return {
|
|
"id": g.id,
|
|
"groupName": g.group_name,
|
|
"tenantNo": g.tenant_no,
|
|
"orderNum": g.order_num if g.order_num is not None else 0,
|
|
"remark": g.remark or "",
|
|
"createTime": g.create_time.strftime("%Y-%m-%d %H:%M:%S") if g.create_time else "",
|
|
"updateTime": g.update_time.strftime("%Y-%m-%d %H:%M:%S") if g.update_time else "",
|
|
}
|
|
|
|
|
|
def list_groups(db: Session, *, tenant_no: str) -> List[Dict[str, Any]]:
|
|
rows = (
|
|
db.query(OaAppGroup)
|
|
.filter(OaAppGroup.tenant_no == tenant_no, OaAppGroup.del_flag == 0)
|
|
.order_by(OaAppGroup.order_num.asc(), OaAppGroup.create_time.asc())
|
|
.all()
|
|
)
|
|
return [_group_to_dict(r) for r in rows]
|
|
|
|
|
|
def get_group(db: Session, group_id: str, tenant_no: str) -> Optional[OaAppGroup]:
|
|
return (
|
|
db.query(OaAppGroup)
|
|
.filter(OaAppGroup.id == group_id, OaAppGroup.tenant_no == tenant_no, OaAppGroup.del_flag == 0)
|
|
.first()
|
|
)
|
|
|
|
|
|
def create_group(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
group_name: str,
|
|
order_num: Optional[int] = None,
|
|
remark: Optional[str] = None,
|
|
user_id: Optional[str] = None,
|
|
) -> OaAppGroup:
|
|
name = (group_name or "").strip()
|
|
if not name:
|
|
raise ValueError("分组名称不能为空")
|
|
now = datetime.now()
|
|
obj = OaAppGroup(
|
|
id=generate_uuid(),
|
|
group_name=name,
|
|
tenant_no=tenant_no,
|
|
order_num=order_num if order_num is not None else 0,
|
|
remark=(remark or "").strip() or None,
|
|
create_by=user_id,
|
|
create_dept_id="",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
db.add(obj)
|
|
db.commit()
|
|
db.refresh(obj)
|
|
return obj
|
|
|
|
|
|
def update_group(
|
|
db: Session,
|
|
group_id: str,
|
|
*,
|
|
tenant_no: str,
|
|
group_name: Optional[str] = None,
|
|
order_num: Optional[int] = None,
|
|
remark: Optional[str] = None,
|
|
user_id: Optional[str] = None,
|
|
) -> OaAppGroup:
|
|
obj = get_group(db, group_id, tenant_no)
|
|
if not obj:
|
|
raise ValueError("分组不存在")
|
|
if group_name is not None:
|
|
name = (group_name or "").strip()
|
|
if not name:
|
|
raise ValueError("分组名称不能为空")
|
|
obj.group_name = name
|
|
if order_num is not None:
|
|
obj.order_num = order_num
|
|
if remark is not None:
|
|
obj.remark = (remark or "").strip() or None
|
|
obj.update_by = user_id
|
|
obj.update_time = datetime.now()
|
|
db.add(obj)
|
|
db.commit()
|
|
db.refresh(obj)
|
|
return obj
|
|
|
|
|
|
def delete_group(db: Session, group_id: str, *, tenant_no: str, user_id: Optional[str] = None) -> None:
|
|
obj = get_group(db, group_id, tenant_no)
|
|
if not obj:
|
|
raise ValueError("分组不存在")
|
|
used = (
|
|
db.query(OaApp.id)
|
|
.filter(OaApp.group_id == group_id, OaApp.tenant_no == tenant_no, OaApp.del_flag == 0)
|
|
.first()
|
|
)
|
|
if used:
|
|
raise ValueError("分组下仍有应用,无法删除")
|
|
obj.del_flag = 1
|
|
obj.update_by = user_id
|
|
obj.update_time = datetime.now()
|
|
db.add(obj)
|
|
db.commit()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 应用 oa_app / 草稿 oa_app_version
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _app_to_dict(a: OaApp, *, group_name: str = "", has_draft: bool = False, latest_published: Optional[int] = None) -> Dict[str, Any]:
|
|
return {
|
|
"id": a.id,
|
|
"groupId": a.group_id or "",
|
|
"groupName": group_name or "",
|
|
"appName": a.app_name,
|
|
"iconUrl": a.icon_url or "",
|
|
"description": a.description or "",
|
|
"initiatorScope": a.initiator_scope or "all",
|
|
"initiatorUserIds": _loads(a.initiator_user_ids, []),
|
|
"adminScope": a.admin_scope or "all_oa_admin",
|
|
"adminUserIds": _loads(a.admin_user_ids, []),
|
|
"status": a.status,
|
|
"tenantNo": a.tenant_no,
|
|
"orderNum": a.order_num if a.order_num is not None else 0,
|
|
"remark": a.remark or "",
|
|
"hasDraft": has_draft,
|
|
"latestPublishedVersion": latest_published,
|
|
"createTime": a.create_time.strftime("%Y-%m-%d %H:%M:%S") if a.create_time else "",
|
|
"updateTime": a.update_time.strftime("%Y-%m-%d %H:%M:%S") if a.update_time else "",
|
|
}
|
|
|
|
|
|
def get_app(db: Session, app_id: str, tenant_no: str) -> Optional[OaApp]:
|
|
return (
|
|
db.query(OaApp)
|
|
.filter(OaApp.id == app_id, OaApp.tenant_no == tenant_no, OaApp.del_flag == 0)
|
|
.first()
|
|
)
|
|
|
|
|
|
def list_apps(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
group_id: Optional[str] = None,
|
|
keyword: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
) -> List[Dict[str, Any]]:
|
|
q = db.query(OaApp).filter(OaApp.tenant_no == tenant_no, OaApp.del_flag == 0)
|
|
if group_id:
|
|
q = q.filter(OaApp.group_id == group_id)
|
|
if status:
|
|
q = q.filter(OaApp.status == status)
|
|
kw = (keyword or "").strip()
|
|
if kw:
|
|
like = f"%{kw}%"
|
|
q = q.filter(OaApp.app_name.like(like))
|
|
rows = q.order_by(OaApp.order_num.asc(), OaApp.create_time.desc()).all()
|
|
group_ids = list({r.group_id for r in rows if r.group_id})
|
|
group_names: Dict[str, str] = {}
|
|
if group_ids:
|
|
for g in (
|
|
db.query(OaAppGroup)
|
|
.filter(OaAppGroup.id.in_(group_ids), OaAppGroup.del_flag == 0)
|
|
.all()
|
|
):
|
|
group_names[g.id] = g.group_name
|
|
out: List[Dict[str, Any]] = []
|
|
for r in rows:
|
|
draft = (
|
|
db.query(OaAppVersion.id)
|
|
.filter(
|
|
OaAppVersion.app_id == r.id,
|
|
OaAppVersion.tenant_no == tenant_no,
|
|
OaAppVersion.status == "draft",
|
|
OaAppVersion.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
pub = (
|
|
db.query(OaAppVersion)
|
|
.filter(
|
|
OaAppVersion.app_id == r.id,
|
|
OaAppVersion.tenant_no == tenant_no,
|
|
OaAppVersion.is_latest_published == "1",
|
|
OaAppVersion.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
out.append(
|
|
_app_to_dict(
|
|
r,
|
|
group_name=group_names.get(r.group_id or "", ""),
|
|
has_draft=bool(draft),
|
|
latest_published=pub.version_no if pub else None,
|
|
)
|
|
)
|
|
return out
|
|
|
|
|
|
def _create_version(
|
|
db: Session,
|
|
*,
|
|
app_id: str,
|
|
tenant_no: str,
|
|
version_no: int,
|
|
status: str,
|
|
form_schema: Any,
|
|
flow_tree: Any,
|
|
advanced: Any,
|
|
user_id: Optional[str],
|
|
is_latest_published: str = "0",
|
|
) -> OaAppVersion:
|
|
now = datetime.now()
|
|
ver = OaAppVersion(
|
|
id=generate_uuid(),
|
|
app_id=app_id,
|
|
version_no=version_no,
|
|
status=status,
|
|
is_latest_published=is_latest_published,
|
|
form_schema_json=_dumps(form_schema if form_schema is not None else {}),
|
|
flow_tree_json=_dumps(flow_tree if flow_tree is not None else {}),
|
|
advanced_settings_json=_dumps(advanced if advanced is not None else DEFAULT_ADVANCED_SETTINGS),
|
|
tenant_no=tenant_no,
|
|
order_num=0,
|
|
remark=None,
|
|
create_by=user_id,
|
|
create_dept_id="",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
db.add(ver)
|
|
return ver
|
|
|
|
|
|
def create_app(
|
|
db: Session,
|
|
*,
|
|
tenant_no: str,
|
|
app_name: str,
|
|
group_id: Optional[str] = None,
|
|
icon_url: Optional[str] = None,
|
|
description: Optional[str] = None,
|
|
initiator_scope: str = "all",
|
|
initiator_user_ids: Optional[List[str]] = None,
|
|
admin_scope: str = "all_oa_admin",
|
|
admin_user_ids: Optional[List[str]] = None,
|
|
status: str = "1",
|
|
order_num: Optional[int] = None,
|
|
remark: Optional[str] = None,
|
|
user_id: Optional[str] = None,
|
|
) -> OaApp:
|
|
name = (app_name or "").strip()
|
|
if not name:
|
|
raise ValueError("应用名称不能为空")
|
|
if len(name) > 50:
|
|
raise ValueError("应用名称不能超过50字")
|
|
gid = (group_id or "").strip() or None
|
|
if gid and not get_group(db, gid, tenant_no):
|
|
raise ValueError("分组不存在")
|
|
now = datetime.now()
|
|
obj = OaApp(
|
|
id=generate_uuid(),
|
|
group_id=gid,
|
|
app_name=name,
|
|
icon_url=(icon_url or "").strip() or None,
|
|
description=(description or "").strip() or None,
|
|
initiator_scope=initiator_scope or "all",
|
|
initiator_user_ids=_dumps(initiator_user_ids or []),
|
|
admin_scope=admin_scope or "all_oa_admin",
|
|
admin_user_ids=_dumps(admin_user_ids or []),
|
|
status=status or "1",
|
|
tenant_no=tenant_no,
|
|
order_num=order_num if order_num is not None else 0,
|
|
remark=(remark or "").strip() or None,
|
|
create_by=user_id,
|
|
create_dept_id="",
|
|
create_time=now,
|
|
update_by=user_id,
|
|
update_time=now,
|
|
del_flag=0,
|
|
)
|
|
db.add(obj)
|
|
_create_version(
|
|
db,
|
|
app_id=obj.id,
|
|
tenant_no=tenant_no,
|
|
version_no=1,
|
|
status="draft",
|
|
form_schema={},
|
|
flow_tree={},
|
|
advanced=DEFAULT_ADVANCED_SETTINGS,
|
|
user_id=user_id,
|
|
)
|
|
db.commit()
|
|
db.refresh(obj)
|
|
return obj
|
|
|
|
|
|
def update_app(
|
|
db: Session,
|
|
app_id: str,
|
|
*,
|
|
tenant_no: str,
|
|
app_name: Optional[str] = None,
|
|
group_id: Optional[str] = None,
|
|
icon_url: Optional[str] = None,
|
|
description: Optional[str] = None,
|
|
initiator_scope: Optional[str] = None,
|
|
initiator_user_ids: Optional[List[str]] = None,
|
|
admin_scope: Optional[str] = None,
|
|
admin_user_ids: Optional[List[str]] = None,
|
|
status: Optional[str] = None,
|
|
order_num: Optional[int] = None,
|
|
remark: Optional[str] = None,
|
|
user_id: Optional[str] = None,
|
|
) -> OaApp:
|
|
obj = get_app(db, app_id, tenant_no)
|
|
if not obj:
|
|
raise ValueError("应用不存在")
|
|
if app_name is not None:
|
|
name = (app_name or "").strip()
|
|
if not name:
|
|
raise ValueError("应用名称不能为空")
|
|
if len(name) > 50:
|
|
raise ValueError("应用名称不能超过50字")
|
|
obj.app_name = name
|
|
if group_id is not None:
|
|
gid = (group_id or "").strip() or None
|
|
if gid and not get_group(db, gid, tenant_no):
|
|
raise ValueError("分组不存在")
|
|
obj.group_id = gid
|
|
if icon_url is not None:
|
|
obj.icon_url = (icon_url or "").strip() or None
|
|
if description is not None:
|
|
desc = (description or "").strip()
|
|
if len(desc) > 100:
|
|
raise ValueError("表单说明不能超过100字")
|
|
obj.description = desc or None
|
|
if initiator_scope is not None:
|
|
obj.initiator_scope = initiator_scope
|
|
if initiator_user_ids is not None:
|
|
obj.initiator_user_ids = _dumps(initiator_user_ids)
|
|
if admin_scope is not None:
|
|
obj.admin_scope = admin_scope
|
|
if admin_user_ids is not None:
|
|
obj.admin_user_ids = _dumps(admin_user_ids)
|
|
if status is not None:
|
|
obj.status = status
|
|
if order_num is not None:
|
|
obj.order_num = order_num
|
|
if remark is not None:
|
|
obj.remark = (remark or "").strip() or None
|
|
obj.update_by = user_id
|
|
obj.update_time = datetime.now()
|
|
db.add(obj)
|
|
db.commit()
|
|
db.refresh(obj)
|
|
return obj
|
|
|
|
|
|
def delete_app(db: Session, app_id: str, *, tenant_no: str, user_id: Optional[str] = None) -> None:
|
|
obj = get_app(db, app_id, tenant_no)
|
|
if not obj:
|
|
raise ValueError("应用不存在")
|
|
now = datetime.now()
|
|
obj.del_flag = 1
|
|
obj.update_by = user_id
|
|
obj.update_time = now
|
|
db.add(obj)
|
|
for ver in (
|
|
db.query(OaAppVersion)
|
|
.filter(OaAppVersion.app_id == app_id, OaAppVersion.tenant_no == tenant_no, OaAppVersion.del_flag == 0)
|
|
.all()
|
|
):
|
|
ver.del_flag = 1
|
|
ver.update_by = user_id
|
|
ver.update_time = now
|
|
db.add(ver)
|
|
db.commit()
|
|
|
|
|
|
def _find_draft(db: Session, app_id: str, tenant_no: str) -> Optional[OaAppVersion]:
|
|
return (
|
|
db.query(OaAppVersion)
|
|
.filter(
|
|
OaAppVersion.app_id == app_id,
|
|
OaAppVersion.tenant_no == tenant_no,
|
|
OaAppVersion.status == "draft",
|
|
OaAppVersion.del_flag == 0,
|
|
)
|
|
.order_by(OaAppVersion.version_no.desc())
|
|
.first()
|
|
)
|
|
|
|
|
|
def _find_latest_published(db: Session, app_id: str, tenant_no: str) -> Optional[OaAppVersion]:
|
|
return (
|
|
db.query(OaAppVersion)
|
|
.filter(
|
|
OaAppVersion.app_id == app_id,
|
|
OaAppVersion.tenant_no == tenant_no,
|
|
OaAppVersion.is_latest_published == "1",
|
|
OaAppVersion.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
|
|
def _version_draft_to_dict(app: OaApp, ver: OaAppVersion) -> Dict[str, Any]:
|
|
advanced = _loads(ver.advanced_settings_json, None)
|
|
if not isinstance(advanced, dict):
|
|
advanced = dict(DEFAULT_ADVANCED_SETTINGS)
|
|
else:
|
|
merged = dict(DEFAULT_ADVANCED_SETTINGS)
|
|
merged.update(advanced)
|
|
advanced = merged
|
|
return {
|
|
"appId": app.id,
|
|
"versionId": ver.id,
|
|
"versionNo": ver.version_no,
|
|
"status": ver.status,
|
|
"basic": {
|
|
"groupId": app.group_id or "",
|
|
"appName": app.app_name,
|
|
"iconUrl": app.icon_url or "",
|
|
"description": app.description or "",
|
|
"initiatorScope": app.initiator_scope or "all",
|
|
"initiatorUserIds": _loads(app.initiator_user_ids, []),
|
|
"adminScope": app.admin_scope or "all_oa_admin",
|
|
"adminUserIds": _loads(app.admin_user_ids, []),
|
|
"status": app.status,
|
|
"orderNum": app.order_num if app.order_num is not None else 0,
|
|
"remark": app.remark or "",
|
|
},
|
|
"formSchemaJson": _loads(ver.form_schema_json, {}),
|
|
"flowTreeJson": _loads(ver.flow_tree_json, {}),
|
|
"advancedSettingsJson": advanced,
|
|
"updateTime": ver.update_time.strftime("%Y-%m-%d %H:%M:%S") if ver.update_time else "",
|
|
}
|
|
|
|
|
|
def get_or_create_draft(
|
|
db: Session, app_id: str, *, tenant_no: str, user_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
app = get_app(db, app_id, tenant_no)
|
|
if not app:
|
|
raise ValueError("应用不存在")
|
|
draft = _find_draft(db, app_id, tenant_no)
|
|
if draft:
|
|
return _version_draft_to_dict(app, draft)
|
|
published = _find_latest_published(db, app_id, tenant_no)
|
|
if published:
|
|
next_no = (published.version_no or 0) + 1
|
|
draft = _create_version(
|
|
db,
|
|
app_id=app_id,
|
|
tenant_no=tenant_no,
|
|
version_no=next_no,
|
|
status="draft",
|
|
form_schema=_loads(published.form_schema_json, {}),
|
|
flow_tree=_loads(published.flow_tree_json, {}),
|
|
advanced=_loads(published.advanced_settings_json, DEFAULT_ADVANCED_SETTINGS),
|
|
user_id=user_id,
|
|
)
|
|
else:
|
|
max_row = (
|
|
db.query(OaAppVersion)
|
|
.filter(OaAppVersion.app_id == app_id, OaAppVersion.tenant_no == tenant_no, OaAppVersion.del_flag == 0)
|
|
.order_by(OaAppVersion.version_no.desc())
|
|
.first()
|
|
)
|
|
next_no = (max_row.version_no if max_row else 0) + 1
|
|
draft = _create_version(
|
|
db,
|
|
app_id=app_id,
|
|
tenant_no=tenant_no,
|
|
version_no=next_no or 1,
|
|
status="draft",
|
|
form_schema={},
|
|
flow_tree={},
|
|
advanced=DEFAULT_ADVANCED_SETTINGS,
|
|
user_id=user_id,
|
|
)
|
|
db.commit()
|
|
db.refresh(draft)
|
|
return _version_draft_to_dict(app, draft)
|
|
|
|
|
|
def save_draft(
|
|
db: Session,
|
|
app_id: str,
|
|
*,
|
|
tenant_no: str,
|
|
basic: Optional[Dict[str, Any]] = None,
|
|
form_schema_json: Any = None,
|
|
flow_tree_json: Any = None,
|
|
advanced_settings_json: Any = None,
|
|
user_id: Optional[str] = None,
|
|
) -> Dict[str, Any]:
|
|
app = get_app(db, app_id, tenant_no)
|
|
if not app:
|
|
raise ValueError("应用不存在")
|
|
# 确保草稿存在
|
|
get_or_create_draft(db, app_id, tenant_no=tenant_no, user_id=user_id)
|
|
draft = _find_draft(db, app_id, tenant_no)
|
|
if not draft:
|
|
raise ValueError("草稿不存在")
|
|
if basic:
|
|
field_map = {
|
|
"appName": "app_name",
|
|
"groupId": "group_id",
|
|
"iconUrl": "icon_url",
|
|
"description": "description",
|
|
"initiatorScope": "initiator_scope",
|
|
"initiatorUserIds": "initiator_user_ids",
|
|
"adminScope": "admin_scope",
|
|
"adminUserIds": "admin_user_ids",
|
|
"status": "status",
|
|
"orderNum": "order_num",
|
|
"remark": "remark",
|
|
}
|
|
kwargs = {field_map[k]: basic[k] for k in field_map if k in basic}
|
|
if kwargs:
|
|
update_app(db, app_id, tenant_no=tenant_no, user_id=user_id, **kwargs)
|
|
app = get_app(db, app_id, tenant_no)
|
|
draft = _find_draft(db, app_id, tenant_no)
|
|
if not draft:
|
|
raise ValueError("草稿不存在")
|
|
now = datetime.now()
|
|
if form_schema_json is not None:
|
|
draft.form_schema_json = _dumps(form_schema_json)
|
|
if flow_tree_json is not None:
|
|
draft.flow_tree_json = _dumps(flow_tree_json)
|
|
if advanced_settings_json is not None:
|
|
if isinstance(advanced_settings_json, dict):
|
|
merged = dict(DEFAULT_ADVANCED_SETTINGS)
|
|
merged.update(advanced_settings_json)
|
|
draft.advanced_settings_json = _dumps(merged)
|
|
else:
|
|
draft.advanced_settings_json = _dumps(advanced_settings_json)
|
|
draft.update_by = user_id
|
|
draft.update_time = now
|
|
db.add(draft)
|
|
db.commit()
|
|
db.refresh(draft)
|
|
assert app is not None
|
|
return _version_draft_to_dict(app, draft)
|