oa_approval.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. """OA 审批管理接口(钉钉交互)"""
  2. from typing import Any, Dict, List, Optional
  3. from fastapi import APIRouter, Depends, Query
  4. from pydantic import BaseModel, ConfigDict, Field
  5. from sqlalchemy.orm import Session
  6. from app.core.database import get_db
  7. from app.core.menu_guard import require_admin_or_super_admin
  8. from app.core.response import error_response, success_response
  9. from app.crud import oa_approval as oa_crud
  10. from app.models.models import SysUser
  11. router = APIRouter(prefix="/web/admin/oa-approval", tags=["OA审批"])
  12. def _get_tenant_no(db: Session, user: dict) -> str:
  13. tenant = (user.get("tenant_no") or "").strip()
  14. if tenant:
  15. return tenant
  16. uid = user.get("user_id")
  17. if uid:
  18. row = db.query(SysUser).filter(SysUser.id == uid, SysUser.del_flag == 0).first()
  19. if row and row.tenant_no:
  20. return row.tenant_no
  21. return "zkxw"
  22. # ---------------------------------------------------------------------------
  23. # 工作流角色
  24. # ---------------------------------------------------------------------------
  25. class OaRoleCreateBody(BaseModel):
  26. model_config = ConfigDict(populate_by_name=True)
  27. role_name: str = Field(..., alias="roleName")
  28. role_code: str = Field(..., alias="roleCode")
  29. status: Optional[str] = Field("1")
  30. order_num: Optional[int] = Field(None, alias="orderNum")
  31. remark: Optional[str] = None
  32. class OaRoleUpdateBody(BaseModel):
  33. model_config = ConfigDict(populate_by_name=True)
  34. role_name: Optional[str] = Field(None, alias="roleName")
  35. status: Optional[str] = None
  36. order_num: Optional[int] = Field(None, alias="orderNum")
  37. remark: Optional[str] = None
  38. class OaRoleUsersBody(BaseModel):
  39. model_config = ConfigDict(populate_by_name=True)
  40. user_ids: List[str] = Field(default_factory=list, alias="userIds")
  41. @router.get("/roles")
  42. async def list_oa_roles(
  43. keyword: Optional[str] = None,
  44. status: Optional[str] = None,
  45. user: dict = Depends(require_admin_or_super_admin),
  46. db: Session = Depends(get_db),
  47. ):
  48. tenant_no = _get_tenant_no(db, user)
  49. items = oa_crud.list_roles(db, tenant_no=tenant_no, keyword=keyword, status=status)
  50. return success_response({"list": items})
  51. @router.post("/roles/ensure-presets")
  52. async def ensure_oa_role_presets(
  53. user: dict = Depends(require_admin_or_super_admin),
  54. db: Session = Depends(get_db),
  55. ):
  56. tenant_no = _get_tenant_no(db, user)
  57. created = oa_crud.ensure_presets(db, tenant_no, user_id=user.get("user_id"))
  58. return success_response(
  59. {"createdCount": len(created)},
  60. message="预置角色已就绪",
  61. )
  62. @router.post("/roles")
  63. async def create_oa_role(
  64. body: OaRoleCreateBody,
  65. user: dict = Depends(require_admin_or_super_admin),
  66. db: Session = Depends(get_db),
  67. ):
  68. try:
  69. tenant_no = _get_tenant_no(db, user)
  70. obj = oa_crud.create_role(
  71. db,
  72. tenant_no=tenant_no,
  73. role_name=body.role_name,
  74. role_code=body.role_code,
  75. status=body.status or "1",
  76. order_num=body.order_num,
  77. remark=body.remark,
  78. user_id=user.get("user_id"),
  79. )
  80. return success_response(oa_crud._role_to_dict(obj), message="创建成功")
  81. except ValueError as e:
  82. return error_response(400, str(e))
  83. @router.put("/roles/{role_id}")
  84. async def update_oa_role(
  85. role_id: str,
  86. body: OaRoleUpdateBody,
  87. user: dict = Depends(require_admin_or_super_admin),
  88. db: Session = Depends(get_db),
  89. ):
  90. try:
  91. tenant_no = _get_tenant_no(db, user)
  92. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  93. obj = oa_crud.update_role(
  94. db,
  95. role_id,
  96. tenant_no=tenant_no,
  97. role_name=payload.get("role_name"),
  98. status=payload.get("status"),
  99. order_num=payload.get("order_num"),
  100. remark=payload.get("remark"),
  101. user_id=user.get("user_id"),
  102. )
  103. return success_response(oa_crud._role_to_dict(obj), message="更新成功")
  104. except ValueError as e:
  105. return error_response(400, str(e))
  106. @router.delete("/roles/{role_id}")
  107. async def delete_oa_role(
  108. role_id: str,
  109. user: dict = Depends(require_admin_or_super_admin),
  110. db: Session = Depends(get_db),
  111. ):
  112. try:
  113. tenant_no = _get_tenant_no(db, user)
  114. oa_crud.delete_role(db, role_id, tenant_no=tenant_no, user_id=user.get("user_id"))
  115. return success_response(None, message="删除成功")
  116. except ValueError as e:
  117. return error_response(400, str(e))
  118. @router.get("/roles/{role_id}/users")
  119. async def list_oa_role_users(
  120. role_id: str,
  121. user: dict = Depends(require_admin_or_super_admin),
  122. db: Session = Depends(get_db),
  123. ):
  124. tenant_no = _get_tenant_no(db, user)
  125. if not oa_crud.get_role(db, role_id, tenant_no):
  126. return error_response(404, "角色不存在")
  127. return success_response({"list": oa_crud.list_role_users(db, role_id, tenant_no)})
  128. @router.put("/roles/{role_id}/users")
  129. async def replace_oa_role_users(
  130. role_id: str,
  131. body: OaRoleUsersBody,
  132. user: dict = Depends(require_admin_or_super_admin),
  133. db: Session = Depends(get_db),
  134. ):
  135. try:
  136. tenant_no = _get_tenant_no(db, user)
  137. oa_crud.replace_role_users(
  138. db,
  139. role_id,
  140. tenant_no=tenant_no,
  141. user_ids=body.user_ids,
  142. operator_id=user.get("user_id"),
  143. )
  144. return success_response(
  145. {"list": oa_crud.list_role_users(db, role_id, tenant_no)},
  146. message="成员已更新",
  147. )
  148. except ValueError as e:
  149. return error_response(400, str(e))
  150. # ---------------------------------------------------------------------------
  151. # 分组
  152. # ---------------------------------------------------------------------------
  153. class OaGroupCreateBody(BaseModel):
  154. model_config = ConfigDict(populate_by_name=True)
  155. group_name: str = Field(..., alias="groupName")
  156. order_num: Optional[int] = Field(None, alias="orderNum")
  157. remark: Optional[str] = None
  158. class OaGroupUpdateBody(BaseModel):
  159. model_config = ConfigDict(populate_by_name=True)
  160. group_name: Optional[str] = Field(None, alias="groupName")
  161. order_num: Optional[int] = Field(None, alias="orderNum")
  162. remark: Optional[str] = None
  163. @router.get("/groups")
  164. async def list_oa_groups(
  165. user: dict = Depends(require_admin_or_super_admin),
  166. db: Session = Depends(get_db),
  167. ):
  168. tenant_no = _get_tenant_no(db, user)
  169. return success_response({"list": oa_crud.list_groups(db, tenant_no=tenant_no)})
  170. @router.post("/groups")
  171. async def create_oa_group(
  172. body: OaGroupCreateBody,
  173. user: dict = Depends(require_admin_or_super_admin),
  174. db: Session = Depends(get_db),
  175. ):
  176. try:
  177. tenant_no = _get_tenant_no(db, user)
  178. obj = oa_crud.create_group(
  179. db,
  180. tenant_no=tenant_no,
  181. group_name=body.group_name,
  182. order_num=body.order_num,
  183. remark=body.remark,
  184. user_id=user.get("user_id"),
  185. )
  186. return success_response(oa_crud._group_to_dict(obj), message="创建成功")
  187. except ValueError as e:
  188. return error_response(400, str(e))
  189. @router.put("/groups/{group_id}")
  190. async def update_oa_group(
  191. group_id: str,
  192. body: OaGroupUpdateBody,
  193. user: dict = Depends(require_admin_or_super_admin),
  194. db: Session = Depends(get_db),
  195. ):
  196. try:
  197. tenant_no = _get_tenant_no(db, user)
  198. payload = body.model_dump(exclude_unset=True, by_alias=False)
  199. obj = oa_crud.update_group(
  200. db,
  201. group_id,
  202. tenant_no=tenant_no,
  203. group_name=payload.get("group_name"),
  204. order_num=payload.get("order_num"),
  205. remark=payload.get("remark"),
  206. user_id=user.get("user_id"),
  207. )
  208. return success_response(oa_crud._group_to_dict(obj), message="更新成功")
  209. except ValueError as e:
  210. return error_response(400, str(e))
  211. @router.delete("/groups/{group_id}")
  212. async def delete_oa_group(
  213. group_id: str,
  214. user: dict = Depends(require_admin_or_super_admin),
  215. db: Session = Depends(get_db),
  216. ):
  217. try:
  218. tenant_no = _get_tenant_no(db, user)
  219. oa_crud.delete_group(db, group_id, tenant_no=tenant_no, user_id=user.get("user_id"))
  220. return success_response(None, message="删除成功")
  221. except ValueError as e:
  222. return error_response(400, str(e))
  223. # ---------------------------------------------------------------------------
  224. # 应用 / 草稿
  225. # ---------------------------------------------------------------------------
  226. class OaAppCreateBody(BaseModel):
  227. model_config = ConfigDict(populate_by_name=True)
  228. app_name: str = Field(..., alias="appName")
  229. group_id: Optional[str] = Field(None, alias="groupId")
  230. icon_url: Optional[str] = Field(None, alias="iconUrl")
  231. description: Optional[str] = None
  232. initiator_scope: Optional[str] = Field("all", alias="initiatorScope")
  233. initiator_user_ids: Optional[List[str]] = Field(None, alias="initiatorUserIds")
  234. admin_scope: Optional[str] = Field("all_oa_admin", alias="adminScope")
  235. admin_user_ids: Optional[List[str]] = Field(None, alias="adminUserIds")
  236. status: Optional[str] = "1"
  237. order_num: Optional[int] = Field(None, alias="orderNum")
  238. remark: Optional[str] = None
  239. class OaAppUpdateBody(BaseModel):
  240. model_config = ConfigDict(populate_by_name=True)
  241. app_name: Optional[str] = Field(None, alias="appName")
  242. group_id: Optional[str] = Field(None, alias="groupId")
  243. icon_url: Optional[str] = Field(None, alias="iconUrl")
  244. description: Optional[str] = None
  245. initiator_scope: Optional[str] = Field(None, alias="initiatorScope")
  246. initiator_user_ids: Optional[List[str]] = Field(None, alias="initiatorUserIds")
  247. admin_scope: Optional[str] = Field(None, alias="adminScope")
  248. admin_user_ids: Optional[List[str]] = Field(None, alias="adminUserIds")
  249. status: Optional[str] = None
  250. order_num: Optional[int] = Field(None, alias="orderNum")
  251. remark: Optional[str] = None
  252. class OaDraftSaveBody(BaseModel):
  253. model_config = ConfigDict(populate_by_name=True)
  254. basic: Optional[Dict[str, Any]] = None
  255. form_schema_json: Optional[Any] = Field(None, alias="formSchemaJson")
  256. flow_tree_json: Optional[Any] = Field(None, alias="flowTreeJson")
  257. advanced_settings_json: Optional[Any] = Field(None, alias="advancedSettingsJson")
  258. @router.get("/apps")
  259. async def list_oa_apps(
  260. groupId: Optional[str] = None,
  261. keyword: Optional[str] = None,
  262. status: Optional[str] = None,
  263. user: dict = Depends(require_admin_or_super_admin),
  264. db: Session = Depends(get_db),
  265. ):
  266. tenant_no = _get_tenant_no(db, user)
  267. items = oa_crud.list_apps(
  268. db, tenant_no=tenant_no, group_id=groupId, keyword=keyword, status=status
  269. )
  270. return success_response({"list": items})
  271. @router.post("/apps")
  272. async def create_oa_app(
  273. body: OaAppCreateBody,
  274. user: dict = Depends(require_admin_or_super_admin),
  275. db: Session = Depends(get_db),
  276. ):
  277. try:
  278. tenant_no = _get_tenant_no(db, user)
  279. obj = oa_crud.create_app(
  280. db,
  281. tenant_no=tenant_no,
  282. app_name=body.app_name,
  283. group_id=body.group_id,
  284. icon_url=body.icon_url,
  285. description=body.description,
  286. initiator_scope=body.initiator_scope or "all",
  287. initiator_user_ids=body.initiator_user_ids,
  288. admin_scope=body.admin_scope or "all_oa_admin",
  289. admin_user_ids=body.admin_user_ids,
  290. status=body.status or "1",
  291. order_num=body.order_num,
  292. remark=body.remark,
  293. user_id=user.get("user_id"),
  294. )
  295. group_name = ""
  296. if obj.group_id:
  297. g = oa_crud.get_group(db, obj.group_id, tenant_no)
  298. group_name = g.group_name if g else ""
  299. return success_response(
  300. oa_crud._app_to_dict(obj, group_name=group_name, has_draft=True),
  301. message="创建成功",
  302. )
  303. except ValueError as e:
  304. return error_response(400, str(e))
  305. @router.put("/apps/{app_id}")
  306. async def update_oa_app(
  307. app_id: str,
  308. body: OaAppUpdateBody,
  309. user: dict = Depends(require_admin_or_super_admin),
  310. db: Session = Depends(get_db),
  311. ):
  312. try:
  313. tenant_no = _get_tenant_no(db, user)
  314. payload = body.model_dump(exclude_unset=True, by_alias=False)
  315. obj = oa_crud.update_app(db, app_id, tenant_no=tenant_no, user_id=user.get("user_id"), **payload)
  316. group_name = ""
  317. if obj.group_id:
  318. g = oa_crud.get_group(db, obj.group_id, tenant_no)
  319. group_name = g.group_name if g else ""
  320. return success_response(oa_crud._app_to_dict(obj, group_name=group_name), message="更新成功")
  321. except ValueError as e:
  322. return error_response(400, str(e))
  323. @router.delete("/apps/{app_id}")
  324. async def delete_oa_app(
  325. app_id: str,
  326. user: dict = Depends(require_admin_or_super_admin),
  327. db: Session = Depends(get_db),
  328. ):
  329. try:
  330. tenant_no = _get_tenant_no(db, user)
  331. oa_crud.delete_app(db, app_id, tenant_no=tenant_no, user_id=user.get("user_id"))
  332. return success_response(None, message="删除成功")
  333. except ValueError as e:
  334. return error_response(400, str(e))
  335. @router.get("/apps/{app_id}/draft")
  336. async def get_oa_app_draft(
  337. app_id: str,
  338. user: dict = Depends(require_admin_or_super_admin),
  339. db: Session = Depends(get_db),
  340. ):
  341. try:
  342. tenant_no = _get_tenant_no(db, user)
  343. data = oa_crud.get_or_create_draft(
  344. db, app_id, tenant_no=tenant_no, user_id=user.get("user_id")
  345. )
  346. return success_response(data)
  347. except ValueError as e:
  348. return error_response(400, str(e))
  349. @router.put("/apps/{app_id}/draft")
  350. async def save_oa_app_draft(
  351. app_id: str,
  352. body: OaDraftSaveBody,
  353. user: dict = Depends(require_admin_or_super_admin),
  354. db: Session = Depends(get_db),
  355. ):
  356. try:
  357. tenant_no = _get_tenant_no(db, user)
  358. data = oa_crud.save_draft(
  359. db,
  360. app_id,
  361. tenant_no=tenant_no,
  362. basic=body.basic,
  363. form_schema_json=body.form_schema_json,
  364. flow_tree_json=body.flow_tree_json,
  365. advanced_settings_json=body.advanced_settings_json,
  366. user_id=user.get("user_id"),
  367. )
  368. return success_response(data, message="草稿已保存")
  369. except ValueError as e:
  370. return error_response(400, str(e))
  371. @router.post("/apps/{app_id}/publish")
  372. async def publish_oa_app(
  373. app_id: str,
  374. user: dict = Depends(require_admin_or_super_admin),
  375. db: Session = Depends(get_db),
  376. ):
  377. from app.services.oa_publish import publish_app
  378. tenant_no = _get_tenant_no(db, user)
  379. data, errors = publish_app(
  380. db, app_id, tenant_no=tenant_no, user_id=user.get("user_id")
  381. )
  382. if errors:
  383. return error_response(400, "发布校验未通过", data={"errors": errors})
  384. return success_response(data, message="发布成功")
  385. # ---------------------------------------------------------------------------
  386. # 运行时:模板 / 实例 / 任务
  387. # ---------------------------------------------------------------------------
  388. class OaStartInstanceBody(BaseModel):
  389. model_config = ConfigDict(populate_by_name=True)
  390. app_id: str = Field(..., alias="appId")
  391. form_data: Optional[Dict[str, Any]] = Field(None, alias="formData")
  392. title: Optional[str] = None
  393. initiator_dept_id: Optional[str] = Field(None, alias="initiatorDeptId")
  394. initiator_select_by_node: Optional[Dict[str, List[str]]] = Field(
  395. None, alias="initiatorSelectByNode"
  396. )
  397. related_instance_ids: Optional[List[str]] = Field(None, alias="relatedInstanceIds")
  398. source_instance_id: Optional[str] = Field(None, alias="sourceInstanceId")
  399. class OaFlowPreviewBody(BaseModel):
  400. model_config = ConfigDict(populate_by_name=True)
  401. form_data: Optional[Dict[str, Any]] = Field(None, alias="formData")
  402. initiator_select_by_node: Optional[Dict[str, List[str]]] = Field(
  403. None, alias="initiatorSelectByNode"
  404. )
  405. class OaTaskActionBody(BaseModel):
  406. model_config = ConfigDict(populate_by_name=True)
  407. comment: Optional[str] = None
  408. class OaAddSignBody(BaseModel):
  409. model_config = ConfigDict(populate_by_name=True)
  410. user_ids: List[str] = Field(default_factory=list, alias="userIds")
  411. comment: Optional[str] = None
  412. class OaTransferBody(BaseModel):
  413. model_config = ConfigDict(populate_by_name=True)
  414. target_user_id: str = Field(..., alias="targetUserId")
  415. comment: Optional[str] = None
  416. class OaResubmitBody(BaseModel):
  417. model_config = ConfigDict(populate_by_name=True)
  418. form_data: Optional[Dict[str, Any]] = Field(None, alias="formData")
  419. initiator_select_by_node: Optional[Dict[str, List[str]]] = Field(
  420. None, alias="initiatorSelectByNode"
  421. )
  422. @router.get("/templates")
  423. async def list_oa_templates(
  424. user: dict = Depends(require_admin_or_super_admin),
  425. db: Session = Depends(get_db),
  426. ):
  427. from app.services import oa_engine
  428. tenant_no = _get_tenant_no(db, user)
  429. items = oa_engine.list_templates(db, tenant_no=tenant_no, user_id=user.get("user_id") or "")
  430. return success_response({"list": items})
  431. @router.get("/templates/{app_id}")
  432. async def get_oa_template(
  433. app_id: str,
  434. user: dict = Depends(require_admin_or_super_admin),
  435. db: Session = Depends(get_db),
  436. ):
  437. from app.services.oa_engine import OaEngineError, get_template_detail
  438. try:
  439. tenant_no = _get_tenant_no(db, user)
  440. data = get_template_detail(
  441. db,
  442. tenant_no=tenant_no,
  443. app_id=app_id,
  444. user_id=user.get("user_id") or "",
  445. )
  446. return success_response(data)
  447. except OaEngineError as e:
  448. return error_response(400, str(e))
  449. @router.post("/templates/{app_id}/flow-preview")
  450. async def preview_oa_template_flow(
  451. app_id: str,
  452. body: OaFlowPreviewBody,
  453. user: dict = Depends(require_admin_or_super_admin),
  454. db: Session = Depends(get_db),
  455. ):
  456. """发起前预览审批链(条件齐后解析最终路径与审批人)。"""
  457. from app.services.oa_engine import OaEngineError, preview_start_flow
  458. try:
  459. tenant_no = _get_tenant_no(db, user)
  460. uid = user.get("user_id") or ""
  461. dept_id = None
  462. if uid:
  463. row = db.query(SysUser).filter(SysUser.id == uid, SysUser.del_flag == 0).first()
  464. if row:
  465. dept_id = row.dept_id
  466. data = preview_start_flow(
  467. db,
  468. tenant_no=tenant_no,
  469. app_id=app_id,
  470. initiator_user_id=uid,
  471. initiator_dept_id=dept_id,
  472. initiator_select_by_node=body.initiator_select_by_node,
  473. form_data=body.form_data,
  474. )
  475. return success_response(data)
  476. except OaEngineError as e:
  477. return error_response(400, str(e))
  478. @router.get("/templates/{app_id}/flow-preview")
  479. async def preview_oa_template_flow_get(
  480. app_id: str,
  481. user: dict = Depends(require_admin_or_super_admin),
  482. db: Session = Depends(get_db),
  483. ):
  484. """兼容旧 GET:无 formData,含条件分支时多为待确定占位。"""
  485. from app.services.oa_engine import OaEngineError, preview_start_flow
  486. try:
  487. tenant_no = _get_tenant_no(db, user)
  488. uid = user.get("user_id") or ""
  489. dept_id = None
  490. if uid:
  491. row = db.query(SysUser).filter(SysUser.id == uid, SysUser.del_flag == 0).first()
  492. if row:
  493. dept_id = row.dept_id
  494. data = preview_start_flow(
  495. db,
  496. tenant_no=tenant_no,
  497. app_id=app_id,
  498. initiator_user_id=uid,
  499. initiator_dept_id=dept_id,
  500. initiator_select_by_node=None,
  501. form_data=None,
  502. )
  503. return success_response(data)
  504. except OaEngineError as e:
  505. return error_response(400, str(e))
  506. @router.post("/instances")
  507. async def start_oa_instance(
  508. body: OaStartInstanceBody,
  509. user: dict = Depends(require_admin_or_super_admin),
  510. db: Session = Depends(get_db),
  511. ):
  512. from app.services.oa_engine import OaEngineError, start_instance
  513. try:
  514. tenant_no = _get_tenant_no(db, user)
  515. data = start_instance(
  516. db,
  517. tenant_no=tenant_no,
  518. app_id=body.app_id,
  519. initiator_user_id=user.get("user_id") or "",
  520. initiator_dept_id=body.initiator_dept_id,
  521. form_data=body.form_data,
  522. title=body.title,
  523. initiator_select_by_node=body.initiator_select_by_node,
  524. related_instance_ids=body.related_instance_ids,
  525. source_instance_id=body.source_instance_id,
  526. )
  527. return success_response(data, message="发起成功")
  528. except OaEngineError as e:
  529. return error_response(400, str(e))
  530. @router.get("/instances/{instance_id}")
  531. async def get_oa_instance(
  532. instance_id: str,
  533. user: dict = Depends(require_admin_or_super_admin),
  534. db: Session = Depends(get_db),
  535. ):
  536. from app.services.oa_engine import OaEngineError, get_instance_detail
  537. try:
  538. tenant_no = _get_tenant_no(db, user)
  539. return success_response(get_instance_detail(db, instance_id, tenant_no=tenant_no))
  540. except OaEngineError as e:
  541. return error_response(400, str(e))
  542. @router.get("/related-candidates")
  543. async def list_related_candidates(
  544. keyword: str = Query(""),
  545. page: int = Query(1),
  546. pageSize: int = Query(20, alias="pageSize"),
  547. user: dict = Depends(require_admin_or_super_admin),
  548. db: Session = Depends(get_db),
  549. ):
  550. from app.services import oa_related_approval as rel
  551. tenant_no = _get_tenant_no(db, user)
  552. data = rel.list_related_candidates(
  553. db,
  554. tenant_no=tenant_no,
  555. user_id=user.get("user_id") or "",
  556. keyword=keyword,
  557. page=page,
  558. page_size=pageSize,
  559. )
  560. return success_response(data)
  561. @router.get("/related-summaries")
  562. async def get_related_summaries(
  563. ids: str = Query("", description="comma-separated instance ids"),
  564. mode: str = Query("picker"),
  565. user: dict = Depends(require_admin_or_super_admin),
  566. db: Session = Depends(get_db),
  567. ):
  568. from app.services import oa_related_approval as rel
  569. from app.services.oa_engine import OaEngineError
  570. tenant_no = _get_tenant_no(db, user)
  571. id_list = [x.strip() for x in (ids or "").split(",") if x.strip()]
  572. try:
  573. items = rel.build_related_summaries(
  574. db,
  575. tenant_no=tenant_no,
  576. user_id=user.get("user_id") or "",
  577. ids=id_list,
  578. mode=mode,
  579. )
  580. except OaEngineError as e:
  581. return error_response(400, str(e))
  582. return success_response({"list": items})
  583. @router.get("/tasks/{kind}")
  584. async def list_oa_tasks(
  585. kind: str,
  586. user: dict = Depends(require_admin_or_super_admin),
  587. db: Session = Depends(get_db),
  588. ):
  589. from app.services.oa_engine import OaEngineError, list_tasks
  590. try:
  591. tenant_no = _get_tenant_no(db, user)
  592. items = list_tasks(
  593. db, tenant_no=tenant_no, user_id=user.get("user_id") or "", kind=kind
  594. )
  595. return success_response({"list": items})
  596. except OaEngineError as e:
  597. return error_response(400, str(e))
  598. @router.post("/tasks/{task_id}/approve")
  599. async def approve_oa_task(
  600. task_id: str,
  601. body: Optional[OaTaskActionBody] = None,
  602. user: dict = Depends(require_admin_or_super_admin),
  603. db: Session = Depends(get_db),
  604. ):
  605. from app.services.oa_engine import OaEngineError, complete_task
  606. try:
  607. tenant_no = _get_tenant_no(db, user)
  608. data = complete_task(
  609. db,
  610. task_id,
  611. tenant_no=tenant_no,
  612. user_id=user.get("user_id") or "",
  613. action="approve",
  614. comment=(body.comment if body else None),
  615. )
  616. return success_response(data, message="已同意")
  617. except OaEngineError as e:
  618. return error_response(400, str(e))
  619. @router.post("/tasks/{task_id}/reject")
  620. async def reject_oa_task(
  621. task_id: str,
  622. body: Optional[OaTaskActionBody] = None,
  623. user: dict = Depends(require_admin_or_super_admin),
  624. db: Session = Depends(get_db),
  625. ):
  626. from app.services.oa_engine import OaEngineError, complete_task
  627. try:
  628. tenant_no = _get_tenant_no(db, user)
  629. data = complete_task(
  630. db,
  631. task_id,
  632. tenant_no=tenant_no,
  633. user_id=user.get("user_id") or "",
  634. action="reject",
  635. comment=(body.comment if body else None),
  636. )
  637. return success_response(data, message="已拒绝")
  638. except OaEngineError as e:
  639. return error_response(400, str(e))
  640. @router.post("/tasks/{task_id}/return")
  641. async def return_oa_task(
  642. task_id: str,
  643. body: Optional[OaTaskActionBody] = None,
  644. user: dict = Depends(require_admin_or_super_admin),
  645. db: Session = Depends(get_db),
  646. ):
  647. from app.services.oa_engine import OaEngineError, return_task
  648. try:
  649. tenant_no = _get_tenant_no(db, user)
  650. data = return_task(
  651. db,
  652. task_id,
  653. tenant_no=tenant_no,
  654. user_id=user.get("user_id") or "",
  655. comment=(body.comment if body else None),
  656. )
  657. return success_response(data, message="已打回")
  658. except OaEngineError as e:
  659. return error_response(400, str(e))
  660. @router.post("/instances/{instance_id}/resubmit")
  661. async def resubmit_oa_instance(
  662. instance_id: str,
  663. body: Optional[OaResubmitBody] = None,
  664. user: dict = Depends(require_admin_or_super_admin),
  665. db: Session = Depends(get_db),
  666. ):
  667. from app.services.oa_engine import OaEngineError, resubmit_instance
  668. try:
  669. tenant_no = _get_tenant_no(db, user)
  670. data = resubmit_instance(
  671. db,
  672. instance_id,
  673. tenant_no=tenant_no,
  674. user_id=user.get("user_id") or "",
  675. form_data=(body.form_data if body else None),
  676. initiator_select_by_node=(body.initiator_select_by_node if body else None),
  677. )
  678. return success_response(data, message="已再次提交")
  679. except OaEngineError as e:
  680. return error_response(400, str(e))
  681. @router.post("/tasks/{task_id}/add-sign")
  682. async def add_sign_oa_task(
  683. task_id: str,
  684. body: OaAddSignBody,
  685. user: dict = Depends(require_admin_or_super_admin),
  686. db: Session = Depends(get_db),
  687. ):
  688. from app.services.oa_engine import OaEngineError, add_sign
  689. try:
  690. tenant_no = _get_tenant_no(db, user)
  691. data = add_sign(
  692. db,
  693. task_id,
  694. tenant_no=tenant_no,
  695. user_id=user.get("user_id") or "",
  696. user_ids=body.user_ids,
  697. comment=body.comment,
  698. )
  699. return success_response(data, message="加签成功")
  700. except OaEngineError as e:
  701. return error_response(400, str(e))
  702. @router.post("/tasks/{task_id}/transfer")
  703. async def transfer_oa_task(
  704. task_id: str,
  705. body: OaTransferBody,
  706. user: dict = Depends(require_admin_or_super_admin),
  707. db: Session = Depends(get_db),
  708. ):
  709. from app.services.oa_engine import OaEngineError, transfer_task
  710. try:
  711. tenant_no = _get_tenant_no(db, user)
  712. data = transfer_task(
  713. db,
  714. task_id,
  715. tenant_no=tenant_no,
  716. user_id=user.get("user_id") or "",
  717. target_user_id=body.target_user_id,
  718. comment=body.comment,
  719. )
  720. return success_response(data, message="已转交")
  721. except OaEngineError as e:
  722. return error_response(400, str(e))
  723. @router.post("/instances/{instance_id}/withdraw")
  724. async def withdraw_oa_instance(
  725. instance_id: str,
  726. user: dict = Depends(require_admin_or_super_admin),
  727. db: Session = Depends(get_db),
  728. ):
  729. from app.services.oa_engine import OaEngineError, withdraw_instance
  730. try:
  731. tenant_no = _get_tenant_no(db, user)
  732. data = withdraw_instance(
  733. db, instance_id, tenant_no=tenant_no, user_id=user.get("user_id") or ""
  734. )
  735. return success_response(data, message="已撤销")
  736. except OaEngineError as e:
  737. return error_response(400, str(e))