oa_approval.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. """OA 审批 CRUD(分组、应用、草稿等)"""
  2. from __future__ import annotations
  3. import copy
  4. import json
  5. from datetime import datetime
  6. from typing import Any, Dict, List, Optional, Tuple
  7. from sqlalchemy.orm import Session
  8. from app.core.utils import generate_uuid
  9. from app.models.models import OaApp, OaAppGroup, OaAppVersion
  10. DEFAULT_ADVANCED_SETTINGS: Dict[str, Any] = {
  11. "supervisorFallbackToDeptLeader": True,
  12. "allowWithdrawRunning": True,
  13. "approverDedup": False,
  14. "commentRequired": False,
  15. "allowAddSign": True,
  16. "allowReturn": True,
  17. "autoPassWhenApproverIsInitiator": False,
  18. }
  19. def _dumps(data: Any) -> Optional[str]:
  20. if data is None:
  21. return None
  22. if isinstance(data, str):
  23. return data
  24. return json.dumps(data, ensure_ascii=False)
  25. def _loads(text: Optional[str], default: Any = None) -> Any:
  26. if text is None or text == "":
  27. return default
  28. if not isinstance(text, str):
  29. return text
  30. try:
  31. return json.loads(text)
  32. except (TypeError, ValueError):
  33. return default
  34. # ---------------------------------------------------------------------------
  35. # 分组 oa_app_group
  36. # ---------------------------------------------------------------------------
  37. def _group_to_dict(g: OaAppGroup) -> Dict[str, Any]:
  38. return {
  39. "id": g.id,
  40. "groupName": g.group_name,
  41. "tenantNo": g.tenant_no,
  42. "orderNum": g.order_num if g.order_num is not None else 0,
  43. "remark": g.remark or "",
  44. "createTime": g.create_time.strftime("%Y-%m-%d %H:%M:%S") if g.create_time else "",
  45. "updateTime": g.update_time.strftime("%Y-%m-%d %H:%M:%S") if g.update_time else "",
  46. }
  47. def list_groups(db: Session, *, tenant_no: str) -> List[Dict[str, Any]]:
  48. rows = (
  49. db.query(OaAppGroup)
  50. .filter(OaAppGroup.tenant_no == tenant_no, OaAppGroup.del_flag == 0)
  51. .order_by(OaAppGroup.order_num.asc(), OaAppGroup.create_time.asc())
  52. .all()
  53. )
  54. return [_group_to_dict(r) for r in rows]
  55. def get_group(db: Session, group_id: str, tenant_no: str) -> Optional[OaAppGroup]:
  56. return (
  57. db.query(OaAppGroup)
  58. .filter(OaAppGroup.id == group_id, OaAppGroup.tenant_no == tenant_no, OaAppGroup.del_flag == 0)
  59. .first()
  60. )
  61. def create_group(
  62. db: Session,
  63. *,
  64. tenant_no: str,
  65. group_name: str,
  66. order_num: Optional[int] = None,
  67. remark: Optional[str] = None,
  68. user_id: Optional[str] = None,
  69. ) -> OaAppGroup:
  70. name = (group_name or "").strip()
  71. if not name:
  72. raise ValueError("分组名称不能为空")
  73. now = datetime.now()
  74. obj = OaAppGroup(
  75. id=generate_uuid(),
  76. group_name=name,
  77. tenant_no=tenant_no,
  78. order_num=order_num if order_num is not None else 0,
  79. remark=(remark or "").strip() or None,
  80. create_by=user_id,
  81. create_dept_id="",
  82. create_time=now,
  83. update_by=user_id,
  84. update_time=now,
  85. del_flag=0,
  86. )
  87. db.add(obj)
  88. db.commit()
  89. db.refresh(obj)
  90. return obj
  91. def update_group(
  92. db: Session,
  93. group_id: str,
  94. *,
  95. tenant_no: str,
  96. group_name: Optional[str] = None,
  97. order_num: Optional[int] = None,
  98. remark: Optional[str] = None,
  99. user_id: Optional[str] = None,
  100. ) -> OaAppGroup:
  101. obj = get_group(db, group_id, tenant_no)
  102. if not obj:
  103. raise ValueError("分组不存在")
  104. if group_name is not None:
  105. name = (group_name or "").strip()
  106. if not name:
  107. raise ValueError("分组名称不能为空")
  108. obj.group_name = name
  109. if order_num is not None:
  110. obj.order_num = order_num
  111. if remark is not None:
  112. obj.remark = (remark or "").strip() or None
  113. obj.update_by = user_id
  114. obj.update_time = datetime.now()
  115. db.add(obj)
  116. db.commit()
  117. db.refresh(obj)
  118. return obj
  119. def delete_group(db: Session, group_id: str, *, tenant_no: str, user_id: Optional[str] = None) -> None:
  120. obj = get_group(db, group_id, tenant_no)
  121. if not obj:
  122. raise ValueError("分组不存在")
  123. used = (
  124. db.query(OaApp.id)
  125. .filter(OaApp.group_id == group_id, OaApp.tenant_no == tenant_no, OaApp.del_flag == 0)
  126. .first()
  127. )
  128. if used:
  129. raise ValueError("分组下仍有应用,无法删除")
  130. obj.del_flag = 1
  131. obj.update_by = user_id
  132. obj.update_time = datetime.now()
  133. db.add(obj)
  134. db.commit()
  135. # ---------------------------------------------------------------------------
  136. # 应用 oa_app / 草稿 oa_app_version
  137. # ---------------------------------------------------------------------------
  138. def _app_to_dict(a: OaApp, *, group_name: str = "", has_draft: bool = False, latest_published: Optional[int] = None) -> Dict[str, Any]:
  139. return {
  140. "id": a.id,
  141. "groupId": a.group_id or "",
  142. "groupName": group_name or "",
  143. "appName": a.app_name,
  144. "iconUrl": a.icon_url or "",
  145. "description": a.description or "",
  146. "initiatorScope": a.initiator_scope or "all",
  147. "initiatorUserIds": _loads(a.initiator_user_ids, []),
  148. "adminScope": a.admin_scope or "all_oa_admin",
  149. "adminUserIds": _loads(a.admin_user_ids, []),
  150. "status": a.status,
  151. "tenantNo": a.tenant_no,
  152. "orderNum": a.order_num if a.order_num is not None else 0,
  153. "remark": a.remark or "",
  154. "hasDraft": has_draft,
  155. "latestPublishedVersion": latest_published,
  156. "createTime": a.create_time.strftime("%Y-%m-%d %H:%M:%S") if a.create_time else "",
  157. "updateTime": a.update_time.strftime("%Y-%m-%d %H:%M:%S") if a.update_time else "",
  158. }
  159. def get_app(db: Session, app_id: str, tenant_no: str) -> Optional[OaApp]:
  160. return (
  161. db.query(OaApp)
  162. .filter(OaApp.id == app_id, OaApp.tenant_no == tenant_no, OaApp.del_flag == 0)
  163. .first()
  164. )
  165. def list_apps(
  166. db: Session,
  167. *,
  168. tenant_no: str,
  169. group_id: Optional[str] = None,
  170. keyword: Optional[str] = None,
  171. status: Optional[str] = None,
  172. ) -> List[Dict[str, Any]]:
  173. q = db.query(OaApp).filter(OaApp.tenant_no == tenant_no, OaApp.del_flag == 0)
  174. if group_id:
  175. q = q.filter(OaApp.group_id == group_id)
  176. if status:
  177. q = q.filter(OaApp.status == status)
  178. kw = (keyword or "").strip()
  179. if kw:
  180. like = f"%{kw}%"
  181. q = q.filter(OaApp.app_name.like(like))
  182. rows = q.order_by(OaApp.order_num.asc(), OaApp.create_time.desc()).all()
  183. if not rows:
  184. return []
  185. group_ids = list({r.group_id for r in rows if r.group_id})
  186. group_names: Dict[str, str] = {}
  187. if group_ids:
  188. for g in (
  189. db.query(OaAppGroup)
  190. .filter(OaAppGroup.id.in_(group_ids), OaAppGroup.del_flag == 0)
  191. .all()
  192. ):
  193. group_names[g.id] = g.group_name
  194. app_ids = [r.id for r in rows]
  195. # 批量取草稿/最新发布版本号,避免 N+1,且不拉取 form/flow 大 JSON
  196. draft_app_ids = {
  197. app_id
  198. for (app_id,) in (
  199. db.query(OaAppVersion.app_id)
  200. .filter(
  201. OaAppVersion.app_id.in_(app_ids),
  202. OaAppVersion.tenant_no == tenant_no,
  203. OaAppVersion.status == "draft",
  204. OaAppVersion.del_flag == 0,
  205. )
  206. .distinct()
  207. .all()
  208. )
  209. }
  210. latest_published: Dict[str, int] = {
  211. app_id: version_no
  212. for app_id, version_no in (
  213. db.query(OaAppVersion.app_id, OaAppVersion.version_no)
  214. .filter(
  215. OaAppVersion.app_id.in_(app_ids),
  216. OaAppVersion.tenant_no == tenant_no,
  217. OaAppVersion.is_latest_published == "1",
  218. OaAppVersion.del_flag == 0,
  219. )
  220. .all()
  221. )
  222. }
  223. return [
  224. _app_to_dict(
  225. r,
  226. group_name=group_names.get(r.group_id or "", ""),
  227. has_draft=r.id in draft_app_ids,
  228. latest_published=latest_published.get(r.id),
  229. )
  230. for r in rows
  231. ]
  232. def _create_version(
  233. db: Session,
  234. *,
  235. app_id: str,
  236. tenant_no: str,
  237. version_no: int,
  238. status: str,
  239. form_schema: Any,
  240. flow_tree: Any,
  241. advanced: Any,
  242. user_id: Optional[str],
  243. is_latest_published: str = "0",
  244. ) -> OaAppVersion:
  245. now = datetime.now()
  246. ver = OaAppVersion(
  247. id=generate_uuid(),
  248. app_id=app_id,
  249. version_no=version_no,
  250. status=status,
  251. is_latest_published=is_latest_published,
  252. form_schema_json=_dumps(form_schema if form_schema is not None else {}),
  253. flow_tree_json=_dumps(flow_tree if flow_tree is not None else {}),
  254. advanced_settings_json=_dumps(advanced if advanced is not None else DEFAULT_ADVANCED_SETTINGS),
  255. tenant_no=tenant_no,
  256. order_num=0,
  257. remark=None,
  258. create_by=user_id,
  259. create_dept_id="",
  260. create_time=now,
  261. update_by=user_id,
  262. update_time=now,
  263. del_flag=0,
  264. )
  265. db.add(ver)
  266. return ver
  267. def create_app(
  268. db: Session,
  269. *,
  270. tenant_no: str,
  271. app_name: str,
  272. group_id: Optional[str] = None,
  273. icon_url: Optional[str] = None,
  274. description: Optional[str] = None,
  275. initiator_scope: str = "all",
  276. initiator_user_ids: Optional[List[str]] = None,
  277. admin_scope: str = "all_oa_admin",
  278. admin_user_ids: Optional[List[str]] = None,
  279. status: str = "1",
  280. order_num: Optional[int] = None,
  281. remark: Optional[str] = None,
  282. user_id: Optional[str] = None,
  283. ) -> OaApp:
  284. name = (app_name or "").strip()
  285. if not name:
  286. raise ValueError("应用名称不能为空")
  287. if len(name) > 50:
  288. raise ValueError("应用名称不能超过50字")
  289. gid = (group_id or "").strip() or None
  290. if gid and not get_group(db, gid, tenant_no):
  291. raise ValueError("分组不存在")
  292. now = datetime.now()
  293. obj = OaApp(
  294. id=generate_uuid(),
  295. group_id=gid,
  296. app_name=name,
  297. icon_url=(icon_url or "").strip() or None,
  298. description=(description or "").strip() or None,
  299. initiator_scope=initiator_scope or "all",
  300. initiator_user_ids=_dumps(initiator_user_ids or []),
  301. admin_scope=admin_scope or "all_oa_admin",
  302. admin_user_ids=_dumps(admin_user_ids or []),
  303. status=status or "1",
  304. tenant_no=tenant_no,
  305. order_num=order_num if order_num is not None else 0,
  306. remark=(remark or "").strip() or None,
  307. create_by=user_id,
  308. create_dept_id="",
  309. create_time=now,
  310. update_by=user_id,
  311. update_time=now,
  312. del_flag=0,
  313. )
  314. db.add(obj)
  315. _create_version(
  316. db,
  317. app_id=obj.id,
  318. tenant_no=tenant_no,
  319. version_no=1,
  320. status="draft",
  321. form_schema={},
  322. flow_tree={},
  323. advanced=DEFAULT_ADVANCED_SETTINGS,
  324. user_id=user_id,
  325. )
  326. db.commit()
  327. db.refresh(obj)
  328. return obj
  329. def _copied_app_name(source_name: str) -> str:
  330. """原名称 +「复制」;超 50 字时截断前缀并保留后缀。"""
  331. suffix = "复制"
  332. base = (source_name or "").strip()
  333. candidate = f"{base}{suffix}"
  334. if len(candidate) <= 50:
  335. return candidate
  336. max_base = max(0, 50 - len(suffix))
  337. return f"{base[:max_base]}{suffix}"
  338. def copy_app(
  339. db: Session,
  340. source_app_id: str,
  341. *,
  342. tenant_no: str,
  343. user_id: Optional[str] = None,
  344. ) -> OaApp:
  345. """复制应用:基础信息 + 草稿/最新发布版的表单·流程·高级设置,结果为未发布草稿。"""
  346. src = get_app(db, source_app_id, tenant_no)
  347. if not src:
  348. raise ValueError("应用不存在")
  349. source_ver = _find_draft(db, source_app_id, tenant_no) or _find_latest_published(
  350. db, source_app_id, tenant_no
  351. )
  352. if source_ver:
  353. form_schema = copy.deepcopy(_loads(source_ver.form_schema_json, {}) or {})
  354. flow_tree = copy.deepcopy(_loads(source_ver.flow_tree_json, {}) or {})
  355. advanced = copy.deepcopy(
  356. _loads(source_ver.advanced_settings_json, DEFAULT_ADVANCED_SETTINGS)
  357. or DEFAULT_ADVANCED_SETTINGS
  358. )
  359. else:
  360. form_schema = {}
  361. flow_tree = {}
  362. advanced = dict(DEFAULT_ADVANCED_SETTINGS)
  363. now = datetime.now()
  364. obj = OaApp(
  365. id=generate_uuid(),
  366. group_id=src.group_id,
  367. app_name=_copied_app_name(src.app_name),
  368. icon_url=src.icon_url,
  369. description=src.description,
  370. initiator_scope=src.initiator_scope or "all",
  371. initiator_user_ids=src.initiator_user_ids,
  372. admin_scope=src.admin_scope or "all_oa_admin",
  373. admin_user_ids=src.admin_user_ids,
  374. status=src.status or "1",
  375. tenant_no=tenant_no,
  376. order_num=src.order_num if src.order_num is not None else 0,
  377. remark=src.remark,
  378. create_by=user_id,
  379. create_dept_id="",
  380. create_time=now,
  381. update_by=user_id,
  382. update_time=now,
  383. del_flag=0,
  384. )
  385. db.add(obj)
  386. _create_version(
  387. db,
  388. app_id=obj.id,
  389. tenant_no=tenant_no,
  390. version_no=1,
  391. status="draft",
  392. form_schema=form_schema,
  393. flow_tree=flow_tree,
  394. advanced=advanced,
  395. user_id=user_id,
  396. )
  397. db.commit()
  398. db.refresh(obj)
  399. return obj
  400. def update_app(
  401. db: Session,
  402. app_id: str,
  403. *,
  404. tenant_no: str,
  405. app_name: Optional[str] = None,
  406. group_id: Optional[str] = None,
  407. icon_url: Optional[str] = None,
  408. description: Optional[str] = None,
  409. initiator_scope: Optional[str] = None,
  410. initiator_user_ids: Optional[List[str]] = None,
  411. admin_scope: Optional[str] = None,
  412. admin_user_ids: Optional[List[str]] = None,
  413. status: Optional[str] = None,
  414. order_num: Optional[int] = None,
  415. remark: Optional[str] = None,
  416. user_id: Optional[str] = None,
  417. ) -> OaApp:
  418. obj = get_app(db, app_id, tenant_no)
  419. if not obj:
  420. raise ValueError("应用不存在")
  421. if app_name is not None:
  422. name = (app_name or "").strip()
  423. if not name:
  424. raise ValueError("应用名称不能为空")
  425. if len(name) > 50:
  426. raise ValueError("应用名称不能超过50字")
  427. obj.app_name = name
  428. if group_id is not None:
  429. gid = (group_id or "").strip() or None
  430. if gid and not get_group(db, gid, tenant_no):
  431. raise ValueError("分组不存在")
  432. obj.group_id = gid
  433. if icon_url is not None:
  434. obj.icon_url = (icon_url or "").strip() or None
  435. if description is not None:
  436. desc = (description or "").strip()
  437. if len(desc) > 100:
  438. raise ValueError("表单说明不能超过100字")
  439. obj.description = desc or None
  440. if initiator_scope is not None:
  441. obj.initiator_scope = initiator_scope
  442. if initiator_user_ids is not None:
  443. obj.initiator_user_ids = _dumps(initiator_user_ids)
  444. if admin_scope is not None:
  445. obj.admin_scope = admin_scope
  446. if admin_user_ids is not None:
  447. obj.admin_user_ids = _dumps(admin_user_ids)
  448. if status is not None:
  449. obj.status = status
  450. if order_num is not None:
  451. obj.order_num = order_num
  452. if remark is not None:
  453. obj.remark = (remark or "").strip() or None
  454. obj.update_by = user_id
  455. obj.update_time = datetime.now()
  456. db.add(obj)
  457. db.commit()
  458. db.refresh(obj)
  459. return obj
  460. def delete_app(db: Session, app_id: str, *, tenant_no: str, user_id: Optional[str] = None) -> None:
  461. obj = get_app(db, app_id, tenant_no)
  462. if not obj:
  463. raise ValueError("应用不存在")
  464. now = datetime.now()
  465. obj.del_flag = 1
  466. obj.update_by = user_id
  467. obj.update_time = now
  468. db.add(obj)
  469. for ver in (
  470. db.query(OaAppVersion)
  471. .filter(OaAppVersion.app_id == app_id, OaAppVersion.tenant_no == tenant_no, OaAppVersion.del_flag == 0)
  472. .all()
  473. ):
  474. ver.del_flag = 1
  475. ver.update_by = user_id
  476. ver.update_time = now
  477. db.add(ver)
  478. db.commit()
  479. def _find_draft(db: Session, app_id: str, tenant_no: str) -> Optional[OaAppVersion]:
  480. return (
  481. db.query(OaAppVersion)
  482. .filter(
  483. OaAppVersion.app_id == app_id,
  484. OaAppVersion.tenant_no == tenant_no,
  485. OaAppVersion.status == "draft",
  486. OaAppVersion.del_flag == 0,
  487. )
  488. .order_by(OaAppVersion.version_no.desc())
  489. .first()
  490. )
  491. def _find_latest_published(db: Session, app_id: str, tenant_no: str) -> Optional[OaAppVersion]:
  492. return (
  493. db.query(OaAppVersion)
  494. .filter(
  495. OaAppVersion.app_id == app_id,
  496. OaAppVersion.tenant_no == tenant_no,
  497. OaAppVersion.is_latest_published == "1",
  498. OaAppVersion.del_flag == 0,
  499. )
  500. .first()
  501. )
  502. def _version_draft_to_dict(app: OaApp, ver: OaAppVersion) -> Dict[str, Any]:
  503. advanced = _loads(ver.advanced_settings_json, None)
  504. if not isinstance(advanced, dict):
  505. advanced = dict(DEFAULT_ADVANCED_SETTINGS)
  506. else:
  507. merged = dict(DEFAULT_ADVANCED_SETTINGS)
  508. merged.update(advanced)
  509. advanced = merged
  510. return {
  511. "appId": app.id,
  512. "versionId": ver.id,
  513. "versionNo": ver.version_no,
  514. "status": ver.status,
  515. "basic": {
  516. "groupId": app.group_id or "",
  517. "appName": app.app_name,
  518. "iconUrl": app.icon_url or "",
  519. "description": app.description or "",
  520. "initiatorScope": app.initiator_scope or "all",
  521. "initiatorUserIds": _loads(app.initiator_user_ids, []),
  522. "adminScope": app.admin_scope or "all_oa_admin",
  523. "adminUserIds": _loads(app.admin_user_ids, []),
  524. "status": app.status,
  525. "orderNum": app.order_num if app.order_num is not None else 0,
  526. "remark": app.remark or "",
  527. },
  528. "formSchemaJson": _loads(ver.form_schema_json, {}),
  529. "flowTreeJson": _loads(ver.flow_tree_json, {}),
  530. "advancedSettingsJson": advanced,
  531. "updateTime": ver.update_time.strftime("%Y-%m-%d %H:%M:%S") if ver.update_time else "",
  532. }
  533. def get_or_create_draft(
  534. db: Session, app_id: str, *, tenant_no: str, user_id: Optional[str] = None
  535. ) -> Dict[str, Any]:
  536. app = get_app(db, app_id, tenant_no)
  537. if not app:
  538. raise ValueError("应用不存在")
  539. draft = _find_draft(db, app_id, tenant_no)
  540. if draft:
  541. return _version_draft_to_dict(app, draft)
  542. published = _find_latest_published(db, app_id, tenant_no)
  543. if published:
  544. next_no = (published.version_no or 0) + 1
  545. draft = _create_version(
  546. db,
  547. app_id=app_id,
  548. tenant_no=tenant_no,
  549. version_no=next_no,
  550. status="draft",
  551. form_schema=_loads(published.form_schema_json, {}),
  552. flow_tree=_loads(published.flow_tree_json, {}),
  553. advanced=_loads(published.advanced_settings_json, DEFAULT_ADVANCED_SETTINGS),
  554. user_id=user_id,
  555. )
  556. else:
  557. max_row = (
  558. db.query(OaAppVersion)
  559. .filter(OaAppVersion.app_id == app_id, OaAppVersion.tenant_no == tenant_no, OaAppVersion.del_flag == 0)
  560. .order_by(OaAppVersion.version_no.desc())
  561. .first()
  562. )
  563. next_no = (max_row.version_no if max_row else 0) + 1
  564. draft = _create_version(
  565. db,
  566. app_id=app_id,
  567. tenant_no=tenant_no,
  568. version_no=next_no or 1,
  569. status="draft",
  570. form_schema={},
  571. flow_tree={},
  572. advanced=DEFAULT_ADVANCED_SETTINGS,
  573. user_id=user_id,
  574. )
  575. db.commit()
  576. db.refresh(draft)
  577. return _version_draft_to_dict(app, draft)
  578. def save_draft(
  579. db: Session,
  580. app_id: str,
  581. *,
  582. tenant_no: str,
  583. basic: Optional[Dict[str, Any]] = None,
  584. form_schema_json: Any = None,
  585. flow_tree_json: Any = None,
  586. advanced_settings_json: Any = None,
  587. user_id: Optional[str] = None,
  588. ) -> Dict[str, Any]:
  589. app = get_app(db, app_id, tenant_no)
  590. if not app:
  591. raise ValueError("应用不存在")
  592. # 确保草稿存在
  593. get_or_create_draft(db, app_id, tenant_no=tenant_no, user_id=user_id)
  594. draft = _find_draft(db, app_id, tenant_no)
  595. if not draft:
  596. raise ValueError("草稿不存在")
  597. if basic:
  598. field_map = {
  599. "appName": "app_name",
  600. "groupId": "group_id",
  601. "iconUrl": "icon_url",
  602. "description": "description",
  603. "initiatorScope": "initiator_scope",
  604. "initiatorUserIds": "initiator_user_ids",
  605. "adminScope": "admin_scope",
  606. "adminUserIds": "admin_user_ids",
  607. "status": "status",
  608. "orderNum": "order_num",
  609. "remark": "remark",
  610. }
  611. kwargs = {field_map[k]: basic[k] for k in field_map if k in basic}
  612. if kwargs:
  613. update_app(db, app_id, tenant_no=tenant_no, user_id=user_id, **kwargs)
  614. app = get_app(db, app_id, tenant_no)
  615. draft = _find_draft(db, app_id, tenant_no)
  616. if not draft:
  617. raise ValueError("草稿不存在")
  618. now = datetime.now()
  619. if form_schema_json is not None:
  620. draft.form_schema_json = _dumps(form_schema_json)
  621. if flow_tree_json is not None:
  622. draft.flow_tree_json = _dumps(flow_tree_json)
  623. if advanced_settings_json is not None:
  624. if isinstance(advanced_settings_json, dict):
  625. merged = dict(DEFAULT_ADVANCED_SETTINGS)
  626. merged.update(advanced_settings_json)
  627. draft.advanced_settings_json = _dumps(merged)
  628. else:
  629. draft.advanced_settings_json = _dumps(advanced_settings_json)
  630. draft.update_by = user_id
  631. draft.update_time = now
  632. db.add(draft)
  633. db.commit()
  634. db.refresh(draft)
  635. assert app is not None
  636. return _version_draft_to_dict(app, draft)