shutro-erp 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (768) hide show
  1. package/dist/account_asset/.kosh/bindings.js +15 -0
  2. package/dist/{mrp_account → account_asset}/.kosh/schema.d.ts +785 -614
  3. package/dist/account_asset/board.d.ts +61 -0
  4. package/dist/account_asset/board.js +86 -0
  5. package/dist/account_asset/books.d.ts +42 -0
  6. package/dist/account_asset/books.js +398 -0
  7. package/dist/account_asset/demo.d.ts +7 -0
  8. package/dist/account_asset/demo.js +72 -0
  9. package/dist/account_asset/i18n/bn.d.ts +2 -0
  10. package/dist/account_asset/i18n/bn.js +97 -0
  11. package/dist/account_asset/index.d.ts +9 -0
  12. package/dist/account_asset/index.js +337 -0
  13. package/dist/account_asset/module.d.ts +11076 -0
  14. package/dist/account_asset/module.js +230 -0
  15. package/dist/account_budget/.kosh/bindings.js +15 -0
  16. package/dist/account_budget/.kosh/schema.d.ts +2841 -0
  17. package/dist/account_budget/budget.d.ts +5 -0
  18. package/dist/account_budget/budget.js +142 -0
  19. package/dist/account_budget/demo.d.ts +6 -0
  20. package/dist/account_budget/demo.js +30 -0
  21. package/dist/account_budget/i18n/bn.d.ts +2 -0
  22. package/dist/account_budget/i18n/bn.js +63 -0
  23. package/dist/account_budget/index.d.ts +4 -0
  24. package/dist/account_budget/index.js +237 -0
  25. package/dist/account_budget/module.d.ts +27289 -0
  26. package/dist/account_budget/module.js +182 -0
  27. package/dist/account_portal/.kosh/bindings.js +1 -1
  28. package/dist/account_portal/.kosh/schema.d.ts +651 -10
  29. package/dist/account_portal/i18n/bn.d.ts +2 -0
  30. package/dist/account_portal/i18n/bn.js +6 -0
  31. package/dist/account_portal/index.js +2 -0
  32. package/dist/account_portal/module.d.ts +5212 -1592
  33. package/dist/accounting/.kosh/bindings.js +1 -1
  34. package/dist/accounting/.kosh/schema.d.ts +651 -10
  35. package/dist/accounting/closing.js +1 -0
  36. package/dist/accounting/fiscal.d.ts +578 -0
  37. package/dist/accounting/fiscal.js +401 -0
  38. package/dist/accounting/followups.d.ts +618 -0
  39. package/dist/accounting/followups.js +420 -0
  40. package/dist/accounting/i18n/bn.d.ts +2 -0
  41. package/dist/accounting/i18n/bn.js +425 -0
  42. package/dist/accounting/index.d.ts +3 -1
  43. package/dist/accounting/index.js +780 -5
  44. package/dist/accounting/module.d.ts +5386 -1543
  45. package/dist/accounting/module.js +155 -18
  46. package/dist/accounting/statements.d.ts +878 -0
  47. package/dist/accounting/statements.js +728 -0
  48. package/dist/accounting/web/BankReconcilePage.d.ts +9 -0
  49. package/dist/accounting/web/BankReconcilePage.js +138 -0
  50. package/dist/accounting/web/FollowupsPage.d.ts +8 -0
  51. package/dist/accounting/web/FollowupsPage.js +54 -0
  52. package/dist/accounting/web/PostingPreview.d.ts +8 -0
  53. package/dist/accounting/web/PostingPreview.js +26 -0
  54. package/dist/accounting/web/index.d.ts +3 -0
  55. package/dist/accounting/web/index.js +9 -0
  56. package/dist/accounting/web/messages.js +140 -0
  57. package/dist/approvals/.kosh/bindings.d.ts +22 -0
  58. package/dist/approvals/.kosh/bindings.js +15 -0
  59. package/dist/approvals/.kosh/schema.d.ts +879 -0
  60. package/dist/approvals/demo.d.ts +7 -0
  61. package/dist/approvals/demo.js +41 -0
  62. package/dist/approvals/i18n/bn.d.ts +2 -0
  63. package/dist/approvals/i18n/bn.js +133 -0
  64. package/dist/approvals/index.d.ts +36 -0
  65. package/dist/approvals/index.js +772 -0
  66. package/dist/approvals/mails.d.ts +30 -0
  67. package/dist/approvals/mails.js +87 -0
  68. package/dist/approvals/module.d.ts +2930 -0
  69. package/dist/approvals/module.js +473 -0
  70. package/dist/approvals/web/Config.d.ts +12 -0
  71. package/dist/approvals/web/Config.js +60 -0
  72. package/dist/approvals/web/History.d.ts +4 -0
  73. package/dist/approvals/web/History.js +13 -0
  74. package/dist/approvals/web/Inbox.d.ts +9 -0
  75. package/dist/approvals/web/Inbox.js +114 -0
  76. package/dist/approvals/web/Matrix.d.ts +9 -0
  77. package/dist/approvals/web/Matrix.js +26 -0
  78. package/dist/approvals/web/Mine.d.ts +7 -0
  79. package/dist/approvals/web/Mine.js +27 -0
  80. package/dist/approvals/web/index.d.ts +15 -0
  81. package/dist/approvals/web/index.js +29 -0
  82. package/dist/approvals/web/messages.d.ts +2 -0
  83. package/dist/approvals/web/messages.js +211 -0
  84. package/dist/approvals/web/shared.d.ts +59 -0
  85. package/dist/approvals/web/shared.js +46 -0
  86. package/dist/approvals_purchase/.kosh/bindings.d.ts +22 -0
  87. package/dist/approvals_purchase/.kosh/bindings.js +15 -0
  88. package/dist/approvals_purchase/.kosh/schema.d.ts +1997 -0
  89. package/dist/approvals_purchase/i18n/bn.d.ts +2 -0
  90. package/dist/approvals_purchase/i18n/bn.js +5 -0
  91. package/dist/approvals_purchase/index.d.ts +3 -0
  92. package/dist/approvals_purchase/index.js +47 -0
  93. package/dist/approvals_purchase/module.d.ts +12670 -0
  94. package/dist/approvals_purchase/module.js +84 -0
  95. package/dist/attendance/.kosh/bindings.d.ts +22 -0
  96. package/dist/attendance/.kosh/bindings.js +15 -0
  97. package/dist/attendance/.kosh/schema.d.ts +1106 -0
  98. package/dist/attendance/days.d.ts +96 -0
  99. package/dist/attendance/days.js +430 -0
  100. package/dist/attendance/demo.d.ts +7 -0
  101. package/dist/attendance/demo.js +58 -0
  102. package/dist/attendance/i18n/bn.d.ts +2 -0
  103. package/dist/attendance/i18n/bn.js +147 -0
  104. package/dist/attendance/index.d.ts +4 -0
  105. package/dist/attendance/index.js +515 -0
  106. package/dist/attendance/module.d.ts +4413 -0
  107. package/dist/attendance/module.js +412 -0
  108. package/dist/attendance/routes.d.ts +16 -0
  109. package/dist/attendance/routes.js +266 -0
  110. package/dist/attendance/web/Check.d.ts +8 -0
  111. package/dist/attendance/web/Check.js +40 -0
  112. package/dist/attendance/web/Kiosk.d.ts +8 -0
  113. package/dist/attendance/web/Kiosk.js +62 -0
  114. package/dist/attendance/web/PinButton.d.ts +7 -0
  115. package/dist/attendance/web/PinButton.js +29 -0
  116. package/dist/attendance/web/index.d.ts +13 -0
  117. package/dist/attendance/web/index.js +22 -0
  118. package/dist/attendance/web/messages.d.ts +2 -0
  119. package/dist/attendance/web/messages.js +61 -0
  120. package/dist/automation/engine.js +1 -1
  121. package/dist/calendar/.kosh/bindings.js +1 -1
  122. package/dist/calendar/.kosh/schema.d.ts +2 -1
  123. package/dist/calendar/i18n/bn.d.ts +2 -0
  124. package/dist/calendar/i18n/bn.js +80 -0
  125. package/dist/calendar/index.js +77 -0
  126. package/dist/calendar/module.d.ts +26 -0
  127. package/dist/calendar/module.js +14 -4
  128. package/dist/cli/apps.js +25 -5
  129. package/dist/cli/init.js +46 -4
  130. package/dist/cli/templates.d.ts +1 -1
  131. package/dist/cli/templates.js +123 -5
  132. package/dist/contacts/.kosh/bindings.js +1 -1
  133. package/dist/contacts/.kosh/schema.d.ts +2 -1
  134. package/dist/contacts/i18n/bn.d.ts +2 -0
  135. package/dist/contacts/i18n/bn.js +67 -0
  136. package/dist/contacts/index.js +3 -0
  137. package/dist/contacts/module.d.ts +42 -0
  138. package/dist/contacts/module.js +19 -3
  139. package/dist/crm/.kosh/bindings.js +1 -1
  140. package/dist/crm/.kosh/schema.d.ts +3 -1
  141. package/dist/crm/i18n/bn.d.ts +2 -0
  142. package/dist/crm/i18n/bn.js +80 -0
  143. package/dist/crm/index.js +22 -0
  144. package/dist/crm/module.d.ts +22 -0
  145. package/dist/crm/module.js +6 -3
  146. package/dist/documents/.kosh/bindings.d.ts +22 -0
  147. package/dist/documents/.kosh/bindings.js +15 -0
  148. package/dist/documents/.kosh/schema.d.ts +863 -0
  149. package/dist/documents/demo.d.ts +6 -0
  150. package/dist/documents/demo.js +22 -0
  151. package/dist/documents/documents.d.ts +98 -0
  152. package/dist/documents/documents.js +318 -0
  153. package/dist/documents/i18n/bn.d.ts +2 -0
  154. package/dist/documents/i18n/bn.js +83 -0
  155. package/dist/documents/index.d.ts +30 -0
  156. package/dist/documents/index.js +569 -0
  157. package/dist/documents/module.d.ts +2801 -0
  158. package/dist/documents/module.js +215 -0
  159. package/dist/documents/web/Versions.d.ts +7 -0
  160. package/dist/documents/web/Versions.js +45 -0
  161. package/dist/documents/web/index.d.ts +11 -0
  162. package/dist/documents/web/index.js +16 -0
  163. package/dist/documents/web/messages.d.ts +2 -0
  164. package/dist/documents/web/messages.js +21 -0
  165. package/dist/event/.kosh/bindings.js +1 -1
  166. package/dist/event/.kosh/schema.d.ts +2 -1
  167. package/dist/event/i18n/bn.d.ts +2 -0
  168. package/dist/event/i18n/bn.js +101 -0
  169. package/dist/event/index.js +66 -0
  170. package/dist/event/module.d.ts +20 -0
  171. package/dist/event/module.js +8 -1
  172. package/dist/event_account/.kosh/bindings.js +1 -1
  173. package/dist/event_account/.kosh/schema.d.ts +651 -10
  174. package/dist/event_account/i18n/bn.d.ts +2 -0
  175. package/dist/event_account/i18n/bn.js +5 -0
  176. package/dist/event_account/index.js +2 -0
  177. package/dist/event_account/module.d.ts +5133 -1513
  178. package/dist/expense/.kosh/bindings.d.ts +22 -0
  179. package/dist/expense/.kosh/bindings.js +15 -0
  180. package/dist/expense/.kosh/schema.d.ts +1620 -0
  181. package/dist/expense/demo.d.ts +7 -0
  182. package/dist/expense/demo.js +69 -0
  183. package/dist/expense/i18n/bn.d.ts +2 -0
  184. package/dist/expense/i18n/bn.js +155 -0
  185. package/dist/expense/index.d.ts +3 -0
  186. package/dist/expense/index.js +502 -0
  187. package/dist/expense/module.d.ts +6768 -0
  188. package/dist/expense/module.js +649 -0
  189. package/dist/expense_account/.kosh/bindings.d.ts +22 -0
  190. package/dist/expense_account/.kosh/bindings.js +15 -0
  191. package/dist/expense_account/.kosh/schema.d.ts +2515 -0
  192. package/dist/expense_account/i18n/bn.d.ts +2 -0
  193. package/dist/expense_account/i18n/bn.js +15 -0
  194. package/dist/expense_account/index.d.ts +3 -0
  195. package/dist/expense_account/index.js +76 -0
  196. package/dist/expense_account/module.d.ts +14628 -0
  197. package/dist/expense_account/module.js +318 -0
  198. package/dist/helpdesk/.kosh/bindings.d.ts +22 -0
  199. package/dist/helpdesk/.kosh/bindings.js +15 -0
  200. package/dist/helpdesk/.kosh/schema.d.ts +886 -0
  201. package/dist/helpdesk/demo.d.ts +7 -0
  202. package/dist/helpdesk/demo.js +65 -0
  203. package/dist/helpdesk/hours.d.ts +17 -0
  204. package/dist/helpdesk/hours.js +94 -0
  205. package/dist/helpdesk/i18n/bn.d.ts +2 -0
  206. package/dist/helpdesk/i18n/bn.js +127 -0
  207. package/dist/helpdesk/index.d.ts +10 -0
  208. package/dist/helpdesk/index.js +463 -0
  209. package/dist/helpdesk/module.d.ts +2960 -0
  210. package/dist/helpdesk/module.js +326 -0
  211. package/dist/helpdesk/routes.d.ts +12 -0
  212. package/dist/helpdesk/routes.js +132 -0
  213. package/dist/helpdesk/sla.d.ts +37 -0
  214. package/dist/helpdesk/sla.js +226 -0
  215. package/dist/hr/.kosh/bindings.js +1 -1
  216. package/dist/hr/.kosh/schema.d.ts +59 -1
  217. package/dist/hr/demo.js +11 -1
  218. package/dist/hr/i18n/bn.d.ts +2 -0
  219. package/dist/hr/i18n/bn.js +126 -0
  220. package/dist/hr/index.d.ts +1 -1
  221. package/dist/hr/index.js +158 -12
  222. package/dist/hr/module.d.ts +932 -59
  223. package/dist/hr/module.js +114 -17
  224. package/dist/indent/.kosh/bindings.d.ts +22 -0
  225. package/dist/indent/.kosh/bindings.js +15 -0
  226. package/dist/indent/.kosh/schema.d.ts +2084 -0
  227. package/dist/indent/demo.d.ts +7 -0
  228. package/dist/indent/demo.js +48 -0
  229. package/dist/indent/i18n/bn.d.ts +2 -0
  230. package/dist/indent/i18n/bn.js +143 -0
  231. package/dist/indent/index.d.ts +3 -0
  232. package/dist/indent/index.js +450 -0
  233. package/dist/indent/module.d.ts +13424 -0
  234. package/dist/indent/module.js +792 -0
  235. package/dist/indent_purchase/.kosh/bindings.d.ts +22 -0
  236. package/dist/indent_purchase/.kosh/bindings.js +15 -0
  237. package/dist/indent_purchase/.kosh/schema.d.ts +2710 -0
  238. package/dist/indent_purchase/i18n/bn.d.ts +2 -0
  239. package/dist/indent_purchase/i18n/bn.js +13 -0
  240. package/dist/indent_purchase/index.d.ts +3 -0
  241. package/dist/indent_purchase/index.js +61 -0
  242. package/dist/indent_purchase/module.d.ts +27950 -0
  243. package/dist/indent_purchase/module.js +163 -0
  244. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  245. package/dist/l10n_bd/.kosh/schema.d.ts +651 -10
  246. package/dist/l10n_bd/i18n/bn.d.ts +2 -0
  247. package/dist/l10n_bd/i18n/bn.js +69 -0
  248. package/dist/l10n_bd/index.js +2 -0
  249. package/dist/l10n_bd/module.d.ts +5212 -1592
  250. package/dist/l10n_bd_payroll/.kosh/bindings.d.ts +22 -0
  251. package/dist/l10n_bd_payroll/.kosh/bindings.js +15 -0
  252. package/dist/l10n_bd_payroll/.kosh/schema.d.ts +2891 -0
  253. package/dist/l10n_bd_payroll/data.d.ts +47 -0
  254. package/dist/l10n_bd_payroll/data.js +347 -0
  255. package/dist/l10n_bd_payroll/i18n/bn.d.ts +2 -0
  256. package/dist/l10n_bd_payroll/i18n/bn.js +4 -0
  257. package/dist/l10n_bd_payroll/index.d.ts +4 -0
  258. package/dist/l10n_bd_payroll/index.js +26 -0
  259. package/dist/l10n_bd_payroll/module.d.ts +17294 -0
  260. package/dist/l10n_bd_payroll/module.js +86 -0
  261. package/dist/l10n_bd_pos/.kosh/bindings.d.ts +22 -0
  262. package/dist/{mrp_account → l10n_bd_pos}/.kosh/bindings.js +2 -2
  263. package/dist/l10n_bd_pos/.kosh/schema.d.ts +2341 -0
  264. package/dist/l10n_bd_pos/i18n/bn.d.ts +2 -0
  265. package/dist/l10n_bd_pos/i18n/bn.js +32 -0
  266. package/dist/l10n_bd_pos/index.d.ts +3 -0
  267. package/dist/l10n_bd_pos/index.js +111 -0
  268. package/dist/{mrp_account → l10n_bd_pos}/module.d.ts +9047 -6981
  269. package/dist/l10n_bd_pos/module.js +43 -0
  270. package/dist/l10n_in/.kosh/bindings.js +1 -1
  271. package/dist/l10n_in/.kosh/schema.d.ts +651 -10
  272. package/dist/l10n_in/i18n/bn.d.ts +2 -0
  273. package/dist/l10n_in/i18n/bn.js +95 -0
  274. package/dist/l10n_in/index.js +2 -0
  275. package/dist/l10n_in/module.d.ts +6778 -1580
  276. package/dist/l10n_in/module.js +25 -5
  277. package/dist/l10n_in/web/GstSettings.js +2 -2
  278. package/dist/mail/inbound/handle.js +8 -5
  279. package/dist/mail/inbound/models.js +5 -3
  280. package/dist/mail/index.d.ts +12 -1
  281. package/dist/mail/index.js +139 -2
  282. package/dist/mail/send.d.ts +56 -0
  283. package/dist/mail/send.js +394 -0
  284. package/dist/mail/web/SendButton.d.ts +9 -0
  285. package/dist/mail/web/SendButton.js +71 -0
  286. package/dist/mail/web/SendSelected.d.ts +8 -0
  287. package/dist/mail/web/SendSelected.js +33 -0
  288. package/dist/mail/web/index.d.ts +2 -0
  289. package/dist/mail/web/index.js +6 -0
  290. package/dist/mail/web/messages.js +36 -0
  291. package/dist/maintenance/.kosh/bindings.js +1 -1
  292. package/dist/maintenance/.kosh/schema.d.ts +2 -1
  293. package/dist/maintenance/i18n/bn.d.ts +2 -0
  294. package/dist/maintenance/i18n/bn.js +132 -0
  295. package/dist/maintenance/index.js +85 -0
  296. package/dist/maintenance/module.d.ts +25 -0
  297. package/dist/maintenance/module.js +27 -9
  298. package/dist/maintenance_stock/.kosh/bindings.js +1 -1
  299. package/dist/maintenance_stock/.kosh/schema.d.ts +374 -3
  300. package/dist/maintenance_stock/i18n/bn.d.ts +2 -0
  301. package/dist/maintenance_stock/i18n/bn.js +21 -0
  302. package/dist/maintenance_stock/index.js +2 -0
  303. package/dist/maintenance_stock/module.d.ts +6095 -1327
  304. package/dist/maintenance_stock/module.js +1 -0
  305. package/dist/modules-sdk/apps.d.ts +10 -0
  306. package/dist/modules-sdk/apps.js +32 -0
  307. package/dist/modules-sdk/index.d.ts +1 -1
  308. package/dist/modules-sdk/manifest.d.ts +105 -2
  309. package/dist/payment/.kosh/bindings.js +1 -1
  310. package/dist/payment/.kosh/schema.d.ts +653 -11
  311. package/dist/payment/i18n/bn.d.ts +2 -0
  312. package/dist/payment/i18n/bn.js +59 -0
  313. package/dist/payment/index.d.ts +41 -0
  314. package/dist/payment/index.js +216 -74
  315. package/dist/payment/module.d.ts +5353 -1675
  316. package/dist/payment/module.js +36 -6
  317. package/dist/payment/settle.js +11 -1
  318. package/dist/payroll/.kosh/bindings.d.ts +22 -0
  319. package/dist/payroll/.kosh/bindings.js +15 -0
  320. package/dist/payroll/.kosh/schema.d.ts +1448 -0
  321. package/dist/payroll/compute.d.ts +35 -0
  322. package/dist/payroll/compute.js +398 -0
  323. package/dist/payroll/demo.d.ts +55 -0
  324. package/dist/payroll/demo.js +121 -0
  325. package/dist/payroll/formula.d.ts +62 -0
  326. package/dist/payroll/formula.js +320 -0
  327. package/dist/payroll/i18n/bn.d.ts +2 -0
  328. package/dist/payroll/i18n/bn.js +206 -0
  329. package/dist/payroll/index.d.ts +230 -0
  330. package/dist/payroll/index.js +715 -0
  331. package/dist/{mrp → payroll}/module.d.ts +4294 -2624
  332. package/dist/payroll/module.js +606 -0
  333. package/dist/payroll/routes.d.ts +10 -0
  334. package/dist/payroll/routes.js +112 -0
  335. package/dist/payroll_account/.kosh/bindings.d.ts +22 -0
  336. package/dist/payroll_account/.kosh/bindings.js +15 -0
  337. package/dist/payroll_account/.kosh/schema.d.ts +2887 -0
  338. package/dist/payroll_account/i18n/bn.d.ts +2 -0
  339. package/dist/payroll_account/i18n/bn.js +10 -0
  340. package/dist/payroll_account/index.d.ts +3 -0
  341. package/dist/payroll_account/index.js +42 -0
  342. package/dist/payroll_account/module.d.ts +16738 -0
  343. package/dist/payroll_account/module.js +256 -0
  344. package/dist/permissions/approvals.d.ts +227 -12
  345. package/dist/permissions/approvals.js +1421 -67
  346. package/dist/permissions/index.d.ts +1 -1
  347. package/dist/permissions/index.js +2 -2
  348. package/dist/permissions/models.js +364 -13
  349. package/dist/permissions/routes.d.ts +9 -1
  350. package/dist/permissions/routes.js +72 -5
  351. package/dist/permissions/web/ApprovalPanel.d.ts +5 -4
  352. package/dist/permissions/web/ApprovalPanel.js +75 -11
  353. package/dist/permissions/web/index.d.ts +6 -4
  354. package/dist/permissions/web/index.js +13 -4
  355. package/dist/permissions/web/messages.d.ts +2 -0
  356. package/dist/permissions/web/messages.js +95 -0
  357. package/dist/portal/.kosh/bindings.js +1 -1
  358. package/dist/portal/.kosh/schema.d.ts +2 -1
  359. package/dist/portal/documents.d.ts +3 -0
  360. package/dist/portal/documents.js +21 -1
  361. package/dist/portal/i18n/bn.d.ts +2 -0
  362. package/dist/portal/i18n/bn.js +33 -0
  363. package/dist/portal/index.js +3 -1
  364. package/dist/portal/module.d.ts +20 -0
  365. package/dist/portal/routes-documents.js +76 -0
  366. package/dist/pos/.kosh/bindings.js +1 -1
  367. package/dist/pos/.kosh/schema.d.ts +652 -10
  368. package/dist/pos/i18n/bn.d.ts +2 -0
  369. package/dist/pos/i18n/bn.js +133 -0
  370. package/dist/pos/index.js +87 -1
  371. package/dist/pos/module.d.ts +6967 -1709
  372. package/dist/pos/module.js +49 -5
  373. package/dist/pos/routes.js +80 -7
  374. package/dist/pos/web/Register.js +2 -5
  375. package/dist/pos_stock/.kosh/bindings.js +1 -1
  376. package/dist/pos_stock/.kosh/schema.d.ts +822 -12
  377. package/dist/pos_stock/i18n/bn.d.ts +2 -0
  378. package/dist/pos_stock/i18n/bn.js +6 -0
  379. package/dist/pos_stock/index.js +2 -0
  380. package/dist/pos_stock/module.d.ts +11241 -2851
  381. package/dist/product/.kosh/bindings.js +1 -1
  382. package/dist/product/.kosh/schema.d.ts +203 -1
  383. package/dist/product/i18n/bn.d.ts +2 -0
  384. package/dist/product/i18n/bn.js +175 -0
  385. package/dist/product/index.d.ts +1 -0
  386. package/dist/product/index.js +301 -1
  387. package/dist/product/module.d.ts +1727 -0
  388. package/dist/product/module.js +67 -6
  389. package/dist/product/pricelist.d.ts +575 -0
  390. package/dist/product/pricelist.js +257 -0
  391. package/dist/product/variants.d.ts +908 -0
  392. package/dist/product/variants.js +251 -0
  393. package/dist/production/.kosh/bindings.d.ts +22 -0
  394. package/dist/production/.kosh/bindings.js +15 -0
  395. package/dist/production/.kosh/schema.d.ts +2345 -0
  396. package/dist/{mrp → production}/demo.d.ts +1 -1
  397. package/dist/{mrp → production}/demo.js +5 -5
  398. package/dist/production/depth.d.ts +790 -0
  399. package/dist/production/depth.js +262 -0
  400. package/dist/production/i18n/bn.d.ts +2 -0
  401. package/dist/production/i18n/bn.js +350 -0
  402. package/dist/production/index.d.ts +6 -0
  403. package/dist/production/index.js +1256 -0
  404. package/dist/production/module.d.ts +17327 -0
  405. package/dist/production/module.js +1333 -0
  406. package/dist/production/plan.d.ts +921 -0
  407. package/dist/production/plan.js +511 -0
  408. package/dist/production/schedule.d.ts +656 -0
  409. package/dist/production/schedule.js +309 -0
  410. package/dist/production_account/.kosh/bindings.d.ts +22 -0
  411. package/dist/production_account/.kosh/bindings.js +15 -0
  412. package/dist/production_account/.kosh/schema.d.ts +3496 -0
  413. package/dist/production_account/i18n/bn.d.ts +2 -0
  414. package/dist/production_account/i18n/bn.js +93 -0
  415. package/dist/production_account/index.d.ts +4 -0
  416. package/dist/production_account/index.js +246 -0
  417. package/dist/production_account/module.d.ts +1192 -0
  418. package/dist/{mrp_account → production_account}/module.js +72 -18
  419. package/dist/production_account/overhead.d.ts +1195 -0
  420. package/dist/production_account/overhead.js +554 -0
  421. package/dist/project/.kosh/bindings.js +1 -1
  422. package/dist/project/.kosh/schema.d.ts +735 -1
  423. package/dist/project/i18n/bn.d.ts +2 -0
  424. package/dist/project/i18n/bn.js +383 -0
  425. package/dist/project/index.d.ts +3 -1
  426. package/dist/project/index.js +1024 -14
  427. package/dist/project/module.d.ts +9912 -390
  428. package/dist/project/module.js +1590 -14
  429. package/dist/project/routes.d.ts +44 -0
  430. package/dist/project/routes.js +314 -0
  431. package/dist/project/schedule.d.ts +72 -0
  432. package/dist/project/schedule.js +192 -0
  433. package/dist/project/web/Refresh.d.ts +8 -0
  434. package/dist/project/web/Refresh.js +29 -0
  435. package/dist/project/web/Schedule.d.ts +9 -0
  436. package/dist/project/web/Schedule.js +93 -0
  437. package/dist/project/web/Structure.d.ts +8 -0
  438. package/dist/project/web/Structure.js +86 -0
  439. package/dist/project/web/Workload.d.ts +8 -0
  440. package/dist/project/web/Workload.js +43 -0
  441. package/dist/project/web/index.d.ts +14 -0
  442. package/dist/project/web/index.js +25 -0
  443. package/dist/project/web/messages.d.ts +2 -0
  444. package/dist/project/web/messages.js +85 -0
  445. package/dist/project_account/.kosh/bindings.d.ts +22 -0
  446. package/dist/project_account/.kosh/bindings.js +15 -0
  447. package/dist/project_account/.kosh/schema.d.ts +2763 -0
  448. package/dist/project_account/i18n/bn.d.ts +2 -0
  449. package/dist/project_account/i18n/bn.js +6 -0
  450. package/dist/project_account/index.d.ts +3 -0
  451. package/dist/project_account/index.js +29 -0
  452. package/dist/project_account/module.d.ts +15013 -0
  453. package/dist/project_account/module.js +76 -0
  454. package/dist/project_portal/.kosh/bindings.js +1 -1
  455. package/dist/project_portal/.kosh/schema.d.ts +735 -1
  456. package/dist/project_portal/i18n/bn.d.ts +2 -0
  457. package/dist/project_portal/i18n/bn.js +6 -0
  458. package/dist/project_portal/index.js +2 -0
  459. package/dist/project_portal/module.d.ts +4796 -87
  460. package/dist/project_purchase/.kosh/bindings.d.ts +22 -0
  461. package/dist/project_purchase/.kosh/bindings.js +15 -0
  462. package/dist/project_purchase/.kosh/schema.d.ts +3393 -0
  463. package/dist/project_purchase/i18n/bn.d.ts +2 -0
  464. package/dist/project_purchase/i18n/bn.js +4 -0
  465. package/dist/project_purchase/index.d.ts +9 -0
  466. package/dist/project_purchase/index.js +24 -0
  467. package/dist/project_purchase/module.d.ts +38613 -0
  468. package/dist/project_purchase/module.js +56 -0
  469. package/dist/purchase/.kosh/bindings.js +1 -1
  470. package/dist/purchase/.kosh/schema.d.ts +908 -5
  471. package/dist/purchase/comparison.d.ts +993 -0
  472. package/dist/purchase/comparison.js +415 -0
  473. package/dist/purchase/extras.d.ts +1509 -0
  474. package/dist/purchase/extras.js +445 -0
  475. package/dist/purchase/i18n/bn.d.ts +2 -0
  476. package/dist/purchase/i18n/bn.js +413 -0
  477. package/dist/purchase/index.d.ts +5 -0
  478. package/dist/purchase/index.js +1280 -4
  479. package/dist/purchase/module.d.ts +7147 -395
  480. package/dist/purchase/module.js +303 -10
  481. package/dist/purchase/request.d.ts +696 -0
  482. package/dist/purchase/request.js +446 -0
  483. package/dist/purchase/tender.d.ts +1208 -0
  484. package/dist/purchase/tender.js +507 -0
  485. package/dist/purchase/vendorprice.d.ts +340 -0
  486. package/dist/purchase/vendorprice.js +153 -0
  487. package/dist/purchase_account/.kosh/bindings.js +1 -1
  488. package/dist/purchase_account/.kosh/schema.d.ts +1209 -16
  489. package/dist/purchase_account/i18n/bn.d.ts +2 -0
  490. package/dist/purchase_account/i18n/bn.js +22 -0
  491. package/dist/purchase_account/index.js +20 -0
  492. package/dist/purchase_account/module.d.ts +12232 -2028
  493. package/dist/purchase_account/module.js +252 -9
  494. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  495. package/dist/purchase_portal/.kosh/schema.d.ts +908 -5
  496. package/dist/purchase_portal/i18n/bn.d.ts +2 -0
  497. package/dist/purchase_portal/i18n/bn.js +5 -0
  498. package/dist/purchase_portal/index.js +2 -0
  499. package/dist/purchase_portal/module.d.ts +6853 -352
  500. package/dist/purchase_stock/.kosh/bindings.js +1 -1
  501. package/dist/purchase_stock/.kosh/schema.d.ts +1096 -21
  502. package/dist/purchase_stock/i18n/bn.d.ts +2 -0
  503. package/dist/purchase_stock/i18n/bn.js +19 -0
  504. package/dist/purchase_stock/index.d.ts +2 -2
  505. package/dist/purchase_stock/index.js +38 -2
  506. package/dist/purchase_stock/module.d.ts +12930 -1612
  507. package/dist/purchase_stock/module.js +317 -9
  508. package/dist/purchase_stock/scorecard.d.ts +22 -0
  509. package/dist/purchase_stock/scorecard.js +77 -0
  510. package/dist/purchase_stock/web/Scorecard.d.ts +7 -0
  511. package/dist/purchase_stock/web/Scorecard.js +22 -0
  512. package/dist/purchase_stock/web/index.d.ts +11 -0
  513. package/dist/purchase_stock/web/index.js +16 -0
  514. package/dist/purchase_stock/web/messages.d.ts +2 -0
  515. package/dist/purchase_stock/web/messages.js +27 -0
  516. package/dist/purchase_stock_account/.kosh/bindings.js +1 -1
  517. package/dist/purchase_stock_account/.kosh/schema.d.ts +1444 -20
  518. package/dist/purchase_stock_account/i18n/bn.d.ts +2 -0
  519. package/dist/purchase_stock_account/i18n/bn.js +8 -0
  520. package/dist/purchase_stock_account/index.js +18 -0
  521. package/dist/purchase_stock_account/module.d.ts +7 -35346
  522. package/dist/purchase_stock_account/module.js +35 -8
  523. package/dist/recruitment/.kosh/bindings.js +1 -1
  524. package/dist/recruitment/.kosh/schema.d.ts +60 -1
  525. package/dist/recruitment/i18n/bn.d.ts +2 -0
  526. package/dist/recruitment/i18n/bn.js +93 -0
  527. package/dist/recruitment/index.js +64 -0
  528. package/dist/recruitment/module.d.ts +460 -8
  529. package/dist/recruitment/module.js +13 -4
  530. package/dist/reporting/documents.d.ts +5 -0
  531. package/dist/reporting/documents.js +11 -1
  532. package/dist/reporting/index.d.ts +1 -1
  533. package/dist/reporting/index.js +1 -1
  534. package/dist/reporting/web/CalendarView.d.ts +18 -5
  535. package/dist/reporting/web/CalendarView.js +163 -29
  536. package/dist/reporting/web/messages.js +18 -0
  537. package/dist/sale_account/.kosh/bindings.js +1 -1
  538. package/dist/sale_account/.kosh/schema.d.ts +763 -18
  539. package/dist/sale_account/i18n/bn.d.ts +2 -0
  540. package/dist/sale_account/i18n/bn.js +48 -0
  541. package/dist/sale_account/index.d.ts +1 -1
  542. package/dist/sale_account/index.js +102 -4
  543. package/dist/sale_account/module.d.ts +8241 -2052
  544. package/dist/sale_account/module.js +535 -46
  545. package/dist/sale_crm/.kosh/bindings.d.ts +22 -0
  546. package/dist/sale_crm/.kosh/bindings.js +15 -0
  547. package/dist/{mrp → sale_crm}/.kosh/schema.d.ts +391 -494
  548. package/dist/sale_crm/i18n/bn.d.ts +2 -0
  549. package/dist/sale_crm/i18n/bn.js +9 -0
  550. package/dist/sale_crm/index.d.ts +7 -0
  551. package/dist/sale_crm/index.js +104 -0
  552. package/dist/sale_crm/module.d.ts +5609 -0
  553. package/dist/sale_crm/module.js +73 -0
  554. package/dist/sale_portal/.kosh/bindings.js +1 -1
  555. package/dist/sale_portal/.kosh/schema.d.ts +292 -4
  556. package/dist/sale_portal/i18n/bn.d.ts +2 -0
  557. package/dist/sale_portal/i18n/bn.js +7 -0
  558. package/dist/sale_portal/index.js +10 -0
  559. package/dist/sale_portal/module.d.ts +2563 -292
  560. package/dist/sale_project/.kosh/bindings.js +1 -1
  561. package/dist/sale_project/.kosh/schema.d.ts +1025 -4
  562. package/dist/sale_project/i18n/bn.d.ts +2 -0
  563. package/dist/sale_project/i18n/bn.js +12 -0
  564. package/dist/sale_project/index.js +2 -0
  565. package/dist/sale_project/module.d.ts +9280 -762
  566. package/dist/sale_project/module.js +17 -1
  567. package/dist/sale_stock/.kosh/bindings.js +1 -1
  568. package/dist/sale_stock/.kosh/schema.d.ts +463 -6
  569. package/dist/sale_stock/i18n/bn.d.ts +2 -0
  570. package/dist/sale_stock/i18n/bn.js +15 -0
  571. package/dist/sale_stock/index.js +2 -0
  572. package/dist/sale_stock/module.d.ts +8355 -1334
  573. package/dist/sale_stock/module.js +20 -3
  574. package/dist/sales/.kosh/bindings.js +1 -1
  575. package/dist/sales/.kosh/schema.d.ts +292 -4
  576. package/dist/sales/i18n/bn.d.ts +2 -0
  577. package/dist/sales/i18n/bn.js +128 -0
  578. package/dist/sales/index.js +124 -5
  579. package/dist/sales/module.d.ts +3568 -633
  580. package/dist/sales/module.js +306 -11
  581. package/dist/server/api/duplicate.d.ts +14 -0
  582. package/dist/server/api/duplicate.js +75 -0
  583. package/dist/server/api/explain.d.ts +70 -0
  584. package/dist/server/api/explain.js +111 -0
  585. package/dist/server/api/metadata.d.ts +2 -0
  586. package/dist/server/api/metadata.js +9 -0
  587. package/dist/server/api/openapi.js +15 -0
  588. package/dist/server/api/rest.d.ts +1 -0
  589. package/dist/server/api/rest.js +76 -12
  590. package/dist/server/api/views.js +1 -1
  591. package/dist/server/apps.d.ts +16 -1
  592. package/dist/server/apps.js +164 -2
  593. package/dist/server/auth/base.js +2 -0
  594. package/dist/server/auth/service.d.ts +8 -0
  595. package/dist/server/auth/service.js +19 -0
  596. package/dist/server/events.d.ts +13 -0
  597. package/dist/server/events.js +22 -0
  598. package/dist/server/i18n.d.ts +27 -0
  599. package/dist/server/i18n.js +143 -0
  600. package/dist/server/index.d.ts +2 -0
  601. package/dist/server/index.js +2 -0
  602. package/dist/server/server.js +6 -0
  603. package/dist/settings/company.d.ts +3 -1
  604. package/dist/settings/company.js +26 -5
  605. package/dist/settings/routes.d.ts +10 -0
  606. package/dist/settings/routes.js +19 -3
  607. package/dist/settings/web/AppsPage.d.ts +3 -2
  608. package/dist/settings/web/AppsPage.js +26 -8
  609. package/dist/settings/web/CompaniesPage.js +91 -6
  610. package/dist/settings/web/messages.js +64 -0
  611. package/dist/sign/.kosh/bindings.d.ts +22 -0
  612. package/dist/{mrp → sign}/.kosh/bindings.js +2 -2
  613. package/dist/sign/.kosh/schema.d.ts +991 -0
  614. package/dist/sign/i18n/bn.d.ts +2 -0
  615. package/dist/sign/i18n/bn.js +97 -0
  616. package/dist/sign/index.d.ts +29 -0
  617. package/dist/sign/index.js +478 -0
  618. package/dist/sign/module.d.ts +4518 -0
  619. package/dist/sign/module.js +167 -0
  620. package/dist/sign/sign.d.ts +82 -0
  621. package/dist/sign/sign.js +474 -0
  622. package/dist/stock/.kosh/bindings.js +1 -1
  623. package/dist/stock/.kosh/schema.d.ts +374 -3
  624. package/dist/stock/demo.d.ts +2 -1
  625. package/dist/stock/demo.js +28 -1
  626. package/dist/stock/i18n/bn.d.ts +2 -0
  627. package/dist/stock/i18n/bn.js +311 -0
  628. package/dist/stock/index.d.ts +4 -1
  629. package/dist/stock/index.js +674 -6
  630. package/dist/stock/ledger.d.ts +78 -0
  631. package/dist/stock/ledger.js +582 -0
  632. package/dist/stock/module.d.ts +6142 -1380
  633. package/dist/stock/module.js +780 -26
  634. package/dist/stock/replenish.d.ts +38 -0
  635. package/dist/stock/replenish.js +191 -0
  636. package/dist/stock/returns.d.ts +157 -0
  637. package/dist/stock/returns.js +163 -0
  638. package/dist/stock/web/Ledger.d.ts +9 -0
  639. package/dist/stock/web/Ledger.js +108 -0
  640. package/dist/stock/web/Operations.d.ts +8 -0
  641. package/dist/stock/web/Operations.js +33 -0
  642. package/dist/stock/web/Replenishment.d.ts +8 -0
  643. package/dist/stock/web/Replenishment.js +84 -0
  644. package/dist/stock/web/ReturnButton.d.ts +7 -0
  645. package/dist/stock/web/ReturnButton.js +62 -0
  646. package/dist/stock/web/index.d.ts +7 -2
  647. package/dist/stock/web/index.js +15 -2
  648. package/dist/stock/web/messages.js +206 -0
  649. package/dist/stock_account/.kosh/bindings.js +1 -1
  650. package/dist/stock_account/.kosh/schema.d.ts +878 -12
  651. package/dist/stock_account/i18n/bn.d.ts +2 -0
  652. package/dist/stock_account/i18n/bn.js +73 -0
  653. package/dist/stock_account/index.d.ts +1 -0
  654. package/dist/stock_account/index.js +141 -3
  655. package/dist/stock_account/landed.d.ts +429 -0
  656. package/dist/stock_account/landed.js +335 -0
  657. package/dist/stock_account/module.d.ts +12143 -3391
  658. package/dist/stock_account/module.js +135 -6
  659. package/dist/stock_account/web/LandedPreview.d.ts +7 -0
  660. package/dist/stock_account/web/LandedPreview.js +23 -0
  661. package/dist/stock_account/web/index.d.ts +11 -0
  662. package/dist/stock_account/web/index.js +16 -0
  663. package/dist/stock_account/web/messages.d.ts +2 -0
  664. package/dist/stock_account/web/messages.js +27 -0
  665. package/dist/todo/i18n/bn.d.ts +2 -0
  666. package/dist/todo/i18n/bn.js +66 -0
  667. package/dist/todo/index.js +59 -0
  668. package/dist/todo/module.d.ts +5 -0
  669. package/dist/todo/module.js +5 -3
  670. package/dist/trade_finance/.kosh/bindings.js +1 -1
  671. package/dist/trade_finance/.kosh/schema.d.ts +2 -1
  672. package/dist/trade_finance/i18n/bn.d.ts +2 -0
  673. package/dist/trade_finance/i18n/bn.js +162 -0
  674. package/dist/trade_finance/index.js +95 -0
  675. package/dist/trade_finance/module.d.ts +26 -0
  676. package/dist/trade_finance/module.js +29 -8
  677. package/dist/trade_finance_account/.kosh/bindings.js +1 -1
  678. package/dist/trade_finance_account/.kosh/schema.d.ts +651 -10
  679. package/dist/trade_finance_account/i18n/bn.d.ts +2 -0
  680. package/dist/trade_finance_account/i18n/bn.js +27 -0
  681. package/dist/trade_finance_account/index.js +3 -0
  682. package/dist/trade_finance_account/module.d.ts +5129 -1509
  683. package/dist/trade_finance_account/module.js +2 -0
  684. package/dist/trade_finance_purchase/.kosh/bindings.js +1 -1
  685. package/dist/trade_finance_purchase/.kosh/schema.d.ts +908 -5
  686. package/dist/trade_finance_purchase/i18n/bn.d.ts +2 -0
  687. package/dist/trade_finance_purchase/i18n/bn.js +6 -0
  688. package/dist/trade_finance_purchase/index.js +2 -0
  689. package/dist/trade_finance_purchase/module.d.ts +6909 -408
  690. package/dist/trade_finance_sale/.kosh/bindings.js +1 -1
  691. package/dist/trade_finance_sale/.kosh/schema.d.ts +292 -4
  692. package/dist/trade_finance_sale/i18n/bn.d.ts +2 -0
  693. package/dist/trade_finance_sale/i18n/bn.js +6 -0
  694. package/dist/trade_finance_sale/index.js +2 -0
  695. package/dist/trade_finance_sale/module.d.ts +2602 -331
  696. package/dist/ui/api.d.ts +2 -0
  697. package/dist/ui/app.d.ts +9 -0
  698. package/dist/ui/app.js +47 -15
  699. package/dist/ui/components/AppIcon.js +9 -2
  700. package/dist/ui/components/Explain.d.ts +69 -0
  701. package/dist/ui/components/Explain.js +62 -0
  702. package/dist/ui/context.d.ts +8 -1
  703. package/dist/ui/context.js +34 -1
  704. package/dist/ui/i18n/messages.d.ts +40 -0
  705. package/dist/ui/i18n/messages.js +80 -0
  706. package/dist/ui/index.d.ts +2 -1
  707. package/dist/ui/index.js +1 -0
  708. package/dist/ui/registry.d.ts +14 -0
  709. package/dist/ui/registry.js +10 -0
  710. package/dist/ui/views/FormView.d.ts +6 -0
  711. package/dist/ui/views/FormView.js +51 -12
  712. package/dist/ui/views/KanbanView.js +9 -1
  713. package/dist/ui/views/ListView.d.ts +8 -0
  714. package/dist/ui/views/ListView.js +14 -5
  715. package/dist/ui/views/StatsRow.js +2 -1
  716. package/dist/ui/views/widgets.js +7 -1
  717. package/dist/views/extend.js +12 -0
  718. package/dist/views/types.d.ts +13 -2
  719. package/dist/views/validate.js +8 -1
  720. package/dist/website/.kosh/bindings.js +1 -1
  721. package/dist/website/.kosh/schema.d.ts +2 -1
  722. package/dist/website/i18n/bn.d.ts +2 -0
  723. package/dist/website/i18n/bn.js +113 -0
  724. package/dist/website/index.js +37 -0
  725. package/dist/website/module.d.ts +20 -0
  726. package/dist/website/module.js +30 -5
  727. package/dist/website_crm/.kosh/bindings.js +1 -1
  728. package/dist/website_crm/.kosh/schema.d.ts +3 -1
  729. package/dist/website_crm/i18n/bn.d.ts +2 -0
  730. package/dist/website_crm/i18n/bn.js +5 -0
  731. package/dist/website_crm/index.js +2 -0
  732. package/dist/website_crm/module.d.ts +41 -0
  733. package/dist/website_sale/.kosh/bindings.d.ts +22 -0
  734. package/dist/website_sale/.kosh/bindings.js +15 -0
  735. package/dist/website_sale/.kosh/schema.d.ts +2909 -0
  736. package/dist/website_sale/couriers.d.ts +50 -0
  737. package/dist/website_sale/couriers.js +59 -0
  738. package/dist/website_sale/demo.d.ts +7 -0
  739. package/dist/website_sale/demo.js +90 -0
  740. package/dist/website_sale/growth.d.ts +31 -0
  741. package/dist/website_sale/growth.js +163 -0
  742. package/dist/website_sale/i18n/bn.d.ts +2 -0
  743. package/dist/website_sale/i18n/bn.js +132 -0
  744. package/dist/website_sale/index.d.ts +49 -0
  745. package/dist/website_sale/index.js +620 -0
  746. package/dist/website_sale/module.d.ts +35182 -0
  747. package/dist/website_sale/module.js +419 -0
  748. package/dist/website_sale/shipments.d.ts +16 -0
  749. package/dist/website_sale/shipments.js +113 -0
  750. package/dist/website_sale/shop.d.ts +81 -0
  751. package/dist/website_sale/shop.js +662 -0
  752. package/dist/website_sale_stock/.kosh/bindings.d.ts +22 -0
  753. package/dist/website_sale_stock/.kosh/bindings.js +15 -0
  754. package/dist/website_sale_stock/.kosh/schema.d.ts +3400 -0
  755. package/dist/website_sale_stock/i18n/bn.d.ts +2 -0
  756. package/dist/website_sale_stock/i18n/bn.js +4 -0
  757. package/dist/website_sale_stock/index.d.ts +9 -0
  758. package/dist/website_sale_stock/index.js +22 -0
  759. package/dist/website_sale_stock/module.d.ts +11 -0
  760. package/dist/website_sale_stock/module.js +32 -0
  761. package/package.json +117 -8
  762. package/dist/mrp/index.d.ts +0 -3
  763. package/dist/mrp/index.js +0 -392
  764. package/dist/mrp/module.js +0 -695
  765. package/dist/mrp_account/index.d.ts +0 -3
  766. package/dist/mrp_account/index.js +0 -22
  767. /package/dist/{mrp → account_asset}/.kosh/bindings.d.ts +0 -0
  768. /package/dist/{mrp_account → account_budget}/.kosh/bindings.d.ts +0 -0
@@ -0,0 +1,111 @@
1
+ import { HttpProblem } from '../errors.js';
2
+ /** Which module declares each model and field, from the loaded modules' Shutro definitions. */
3
+ function provenance(registry) {
4
+ const owner = new Map();
5
+ const fieldOwner = new Map();
6
+ const extenders = new Map();
7
+ for (const m of registry.loaded.modules) {
8
+ const shutro = m.shutro;
9
+ for (const model of shutro?.models ?? []) {
10
+ owner.set(model.name, m.name);
11
+ for (const key of Object.keys(model.fields ?? {}))
12
+ fieldOwner.set(`${model.name}.${key}`, m.name);
13
+ }
14
+ }
15
+ for (const m of registry.loaded.modules) {
16
+ const shutro = m.shutro;
17
+ for (const ext of shutro?.extensions ?? []) {
18
+ const set = extenders.get(ext.name) ?? new Set();
19
+ set.add(m.name);
20
+ extenders.set(ext.name, set);
21
+ for (const key of Object.keys(ext.fields ?? {}))
22
+ fieldOwner.set(`${ext.name}.${key}`, m.name);
23
+ }
24
+ }
25
+ return { owner, fieldOwner, extenders };
26
+ }
27
+ const show = (value) => {
28
+ if (value === undefined || value === null || typeof value === 'function')
29
+ return null;
30
+ if (typeof value === 'string')
31
+ return value;
32
+ if (typeof value === 'number' || typeof value === 'boolean')
33
+ return String(value);
34
+ try {
35
+ return JSON.stringify(value);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ };
41
+ /** A model explained, from the registry and the API metadata. */
42
+ export function explainModel(registry, models, name) {
43
+ const model = models.get(name);
44
+ if (model === undefined)
45
+ return null;
46
+ const { owner, fieldOwner, extenders } = provenance(registry);
47
+ const descriptors = registry.registry.get(name).model.fields;
48
+ const description = registry.registry.describe(name).fields;
49
+ const fields = model.fields.map((f) => {
50
+ const raw = descriptors[f.key] ?? {};
51
+ const d = description[f.key] ?? {};
52
+ const depends = Array.isArray(d['depends']) ? d['depends'].map(String) : [];
53
+ const defaultValue = raw['default'];
54
+ return {
55
+ key: f.key,
56
+ label: f.label,
57
+ kind: f.kind,
58
+ help: f.help,
59
+ target: f.target,
60
+ inverse: typeof raw['inverseField'] === 'string' ? raw['inverseField'] : null,
61
+ required: f.required,
62
+ readonly: f.readonly,
63
+ stored: f.stored,
64
+ computed: d['computed'] === true,
65
+ depends,
66
+ hasDefault: defaultValue !== undefined,
67
+ default: typeof defaultValue === 'function' ? '(computed when created)' : show(defaultValue),
68
+ ondelete: typeof raw['ondelete'] === 'string' ? raw['ondelete'] : null,
69
+ translate: f.translate,
70
+ index: raw['index'] !== undefined && raw['index'] !== false,
71
+ options: f.options,
72
+ module: fieldOwner.get(`${name}.${f.key}`) ?? owner.get(name) ?? null,
73
+ };
74
+ });
75
+ const incoming = [];
76
+ for (const other of models.values())
77
+ for (const f of other.fields)
78
+ if (f.target === name && f.kind !== 'one2many')
79
+ incoming.push({ model: other.name, field: f.key, label: f.label, kind: f.kind });
80
+ return {
81
+ model: name,
82
+ label: model.label,
83
+ module: owner.get(name) ?? null,
84
+ extendedBy: [...(extenders.get(name) ?? [])].sort(),
85
+ fields,
86
+ transitions: model.transitionSpecs.map((t) => ({
87
+ name: t.name,
88
+ field: t.field,
89
+ from: t.from,
90
+ to: t.to,
91
+ guarded: t.guard !== null,
92
+ message: t.message,
93
+ requires: t.requires,
94
+ })),
95
+ incoming: incoming.sort((a, b) => a.model.localeCompare(b.model) || a.field.localeCompare(b.field)),
96
+ };
97
+ }
98
+ /** The group that may read explanations besides administrators. */
99
+ export const DEVELOPER_GROUP = 'base.developer';
100
+ export function registerExplain(app, options) {
101
+ app.get('/api/v1/meta/explain/:model', async (request) => request.env(async (raw) => {
102
+ const env = raw;
103
+ if (!env.administrator && !env.su && !env.groups.has(DEVELOPER_GROUP))
104
+ throw new HttpProblem(403, 'not-developer', 'Developers only', 'Explanations are for administrators and developers.', 'Ask an administrator for the Developer role.');
105
+ const name = String(request.params.model ?? '');
106
+ const explained = explainModel(options.registry, options.models, name);
107
+ if (explained === null)
108
+ throw new HttpProblem(404, 'unknown-model', 'Unknown model', `No installed model is named '${name}'.`);
109
+ return explained;
110
+ }));
111
+ }
@@ -51,6 +51,8 @@ export interface ApiModel {
51
51
  readonly onchange: readonly string[];
52
52
  /** The stored field names are searched by (`name` by default), or `null`. */
53
53
  readonly recName: string | null;
54
+ /** The field sets of the model's unique constraints (a copy leaves them out, RFC 0023 §3.2). */
55
+ readonly unique: readonly (readonly string[])[];
54
56
  }
55
57
  /** `res.partner` → `ResPartner`. */
56
58
  export declare function schemaName(model: string): string;
@@ -71,10 +71,19 @@ export function apiModels(registry) {
71
71
  transitionSpecs,
72
72
  onchange: [...description.onchange],
73
73
  recName: recNameOf(registry, name, fields),
74
+ unique: uniqueOf(registry, name),
74
75
  });
75
76
  }
76
77
  return out;
77
78
  }
79
+ function uniqueOf(registry, name) {
80
+ // Shutro resolves a model's constraints to `{ kind, fields }` (the definition's object form also accepted).
81
+ const raw = registry.get(name).model.sqlConstraints;
82
+ const list = Array.isArray(raw)
83
+ ? raw
84
+ : [];
85
+ return list.flatMap((c) => c.kind === 'unique' && Array.isArray(c.fields) ? [c.fields.map(String)] : []);
86
+ }
78
87
  function recNameOf(registry, name, fields) {
79
88
  const declared = registry.get(name).model.recName;
80
89
  const key = typeof declared === 'string' ? declared : 'name';
@@ -186,6 +186,12 @@ function modelPaths(model) {
186
186
  schema: { type: 'boolean' },
187
187
  description: 'Also return the total count.',
188
188
  },
189
+ {
190
+ name: 'archived',
191
+ in: 'query',
192
+ schema: { type: 'string', enum: ['include', 'only'] },
193
+ description: 'Archived records too (include), or alone (only); hidden otherwise.',
194
+ },
189
195
  ],
190
196
  responses: responses({
191
197
  type: 'object',
@@ -263,6 +269,15 @@ function modelPaths(model) {
263
269
  },
264
270
  },
265
271
  };
272
+ paths[`${base}/{id}/duplicate`] = {
273
+ post: {
274
+ operationId: `duplicate${s}`,
275
+ tags: tag,
276
+ summary: 'A copy made as the caller, its lines with it (states and numbers start again)',
277
+ parameters: [ID_PARAM, IDEMPOTENCY],
278
+ responses: responses(ref, '201'),
279
+ },
280
+ };
266
281
  for (const t of model.transitions)
267
282
  paths[`${base}/{id}/transitions/${t}`] = {
268
283
  post: {
@@ -11,6 +11,7 @@
11
11
  * - `POST /api/v1/models/:model/aggregate` grouped totals
12
12
  * - `POST /api/v1/models/:model/onchange` form assistance
13
13
  * - `POST /api/v1/models/:model/:id/transitions/:name` state transition (Idempotency-Key)
14
+ * - `POST /api/v1/models/:model/:id/duplicate` a copy made as the user, its lines with it (RFC 0023 R1)
14
15
  * - `POST /api/v1/models/:model/export` export (needs `export`; audited)
15
16
  * - `POST /api/v1/models/:model/import` import rows (needs `import` and `create`; audited)
16
17
  * - `GET /api/v1/meta/:model` the caller's view of the model (needs `read`)
@@ -1,6 +1,7 @@
1
1
  import { audit } from '../audit.js';
2
2
  import { HttpProblem } from '../errors.js';
3
3
  import { announceChange } from './changes.js';
4
+ import { duplicateValues } from './duplicate.js';
4
5
  import { idempotent } from './idempotency.js';
5
6
  import { defaultFields } from './metadata.js';
6
7
  const ref = (env, model) => env.model(model);
@@ -128,16 +129,47 @@ async function send(reply, outcome) {
128
129
  }
129
130
  /**
130
131
  * JSON has no dates: datetime fields arrive as ISO strings and become `Date`s (Shutro stores them in UTC); other
131
- * kinds are left to Shutro's own validation. Applied to create, update and import values.
132
+ * kinds are left to Shutro's own validation. Applied to create, update and import values, and through the lines
133
+ * of one2many and many2many commands (`create` rows and `update` values) to the lines' own datetimes.
132
134
  */
133
- function coerceValues(model, values) {
135
+ function coerceValues(models, model, values, depth = 0) {
134
136
  const out = { ...values };
135
137
  for (const [key, value] of Object.entries(values)) {
136
- if (model.byKey.get(key)?.kind !== 'datetime' || typeof value !== 'string')
138
+ const field = model.byKey.get(key);
139
+ if (field === undefined)
140
+ continue;
141
+ if (field.kind === 'datetime' && typeof value === 'string') {
142
+ const date = new Date(value);
143
+ if (!Number.isNaN(date.getTime()))
144
+ out[key] = date;
137
145
  continue;
138
- const date = new Date(value);
139
- if (!Number.isNaN(date.getTime()))
140
- out[key] = date;
146
+ }
147
+ if ((field.kind === 'one2many' || field.kind === 'many2many') &&
148
+ field.target !== null &&
149
+ depth < 4 &&
150
+ typeof value === 'object' &&
151
+ value !== null &&
152
+ !Array.isArray(value)) {
153
+ const target = models.get(field.target);
154
+ if (target === undefined)
155
+ continue;
156
+ const commands = value;
157
+ const next = { ...commands };
158
+ if (Array.isArray(commands.create))
159
+ next['create'] = commands.create.map((row) => typeof row === 'object' && row !== null
160
+ ? coerceValues(models, target, row, depth + 1)
161
+ : row);
162
+ if (Array.isArray(commands.update))
163
+ next['update'] = commands.update.map((u) => typeof u === 'object' &&
164
+ u !== null &&
165
+ typeof u.values === 'object'
166
+ ? {
167
+ ...u,
168
+ values: coerceValues(models, target, u.values, depth + 1),
169
+ }
170
+ : u);
171
+ out[key] = next;
172
+ }
141
173
  }
142
174
  return out;
143
175
  }
@@ -173,9 +205,16 @@ export function registerRestApi(app, options) {
173
205
  throw problem(400, 'invalid-limit', 'Invalid limit', 'limit must be 1–1000.');
174
206
  if (!Number.isInteger(offset) || offset < 0)
175
207
  throw problem(400, 'invalid-offset', 'Invalid offset', 'offset must be 0 or more.');
176
- const where = parseFilter(q['filter']);
208
+ const filter = parseFilter(q['filter']);
177
209
  const order = parseOrder(model, q['order']);
178
- return request.env(async (env) => {
210
+ // Archived records are hidden unless asked for: `archived=include` lists them with the rest, `archived=only`
211
+ // lists them alone (RFC 0023: archive, never delete).
212
+ const archived = q['archived'];
213
+ if (archived !== undefined && archived !== 'include' && archived !== 'only')
214
+ throw problem(400, 'invalid-archived', 'Invalid archived', "archived is 'include' or 'only'.");
215
+ const where = archived === 'only' ? { ...(filter ?? {}), active: false } : filter;
216
+ return request.env(async (requestEnv) => {
217
+ const env = archived === undefined ? requestEnv : requestEnv.withFlags({ activeTest: false });
179
218
  const fields = fieldsOf(model, env, q['fields']);
180
219
  const found = await ref(env, model.name).search({
181
220
  ...(where === undefined ? {} : { where }),
@@ -197,7 +236,7 @@ export function registerRestApi(app, options) {
197
236
  const model = modelOf(request.params);
198
237
  const values = body(request);
199
238
  const outcome = await request.env((env) => idempotent(request, env, driver, async () => {
200
- const created = await ref(env, model.name).create([coerceValues(model, values)]);
239
+ const created = await ref(env, model.name).create([coerceValues(models, model, values)]);
201
240
  await env.flush(); // computes and constraints run now: the response shows the stored record
202
241
  announceChange(env, driver, {
203
242
  model: model.name,
@@ -232,7 +271,7 @@ export function registerRestApi(app, options) {
232
271
  const outcome = await request.env((env) => idempotent(request, env, driver, async () => {
233
272
  await ref(env, model.name)
234
273
  .browse([id])
235
- .write(coerceValues(model, values), expected === undefined ? {} : { expect: { updatedAt: expected } });
274
+ .write(coerceValues(models, model, values), expected === undefined ? {} : { expect: { updatedAt: expected } });
236
275
  await env.flush();
237
276
  announceChange(env, driver, { model: model.name, ids: [id], op: 'update' });
238
277
  return record(env, model, id);
@@ -267,7 +306,7 @@ export function registerRestApi(app, options) {
267
306
  changed.some((c) => typeof c !== 'string'))
268
307
  throw problem(400, 'invalid-body', 'Invalid onchange request', 'The body is { values: { ...the record }, changed: ["field", ...] }.');
269
308
  // Datetimes arrive as ISO strings, as for create and update.
270
- const values = coerceValues(model, input.values);
309
+ const values = coerceValues(models, model, input.values);
271
310
  return request.env((env) => ref(env, model.name).onchange({ ...input, values }));
272
311
  });
273
312
  // RFC 0022 §3.4: what comes next for a record — the transitions from its state the user may run, and for
@@ -322,6 +361,31 @@ export function registerRestApi(app, options) {
322
361
  await send(reply, outcome);
323
362
  });
324
363
  // Name search (many2one pickers, display names of ids): the model's record name, within ACL and rules.
364
+ app.post('/api/v1/models/:model/:id/duplicate', async (request, reply) => {
365
+ const model = modelOf(request.params);
366
+ const id = idOf(request.params);
367
+ const outcome = await request.env((env) => idempotent(request, env, driver, async () => {
368
+ await ref(env, model.name).browse([id]).load(['id']); // the original, as the user may read it
369
+ const values = await duplicateValues(env, models, model, id);
370
+ const created = await ref(env, model.name).create([coerceValues(models, model, values)]);
371
+ await env.flush();
372
+ announceChange(env, driver, {
373
+ model: model.name,
374
+ ids: [created.ids[0]],
375
+ op: 'create',
376
+ });
377
+ const result = await record(env, model, created.ids[0]);
378
+ return {
379
+ ...result,
380
+ status: 201,
381
+ headers: {
382
+ ...result.headers,
383
+ location: `/api/v1/models/${model.name}/${created.ids[0]}`,
384
+ },
385
+ };
386
+ }));
387
+ await send(reply, outcome);
388
+ });
325
389
  app.get('/api/v1/models/:model/names', async (request) => {
326
390
  const model = modelOf(request.params);
327
391
  const q = query(request);
@@ -450,7 +514,7 @@ export function registerRestApi(app, options) {
450
514
  throw problem(400, 'invalid-rows', 'Invalid rows', 'Each row is an object of field values.');
451
515
  const outcome = await request.env((env) => idempotent(request, env, driver, async () => {
452
516
  env.access.check(model.name, 'import'); // and `create`, checked by Shutro
453
- const created = await ref(env, model.name).create(input.map((row) => coerceValues(model, row)));
517
+ const created = await ref(env, model.name).create(input.map((row) => coerceValues(models, model, row)));
454
518
  await env.flush(); // all-or-nothing: constraints run before the response
455
519
  announceChange(env, driver, { model: model.name, ids: [...created.ids], op: 'create' });
456
520
  await audit(env, {
@@ -174,7 +174,7 @@ export class ViewRegistry {
174
174
  case 'form': {
175
175
  if (view.header.statusbar !== undefined)
176
176
  meta(model, view.header.statusbar);
177
- const buttons = view.header.buttons.filter((b) => allowed.has(b.transition));
177
+ const buttons = view.header.buttons.filter((b) => b.open !== undefined || (b.transition !== undefined && allowed.has(b.transition)));
178
178
  served = {
179
179
  ...view,
180
180
  header: { ...view.header, buttons },
@@ -10,6 +10,10 @@ export declare const APPS_LOG_TABLE = "kosh_app_log";
10
10
  * installed set (`createAppHost`).
11
11
  */
12
12
  export declare const APPS_CHANNEL = "kosh_apps";
13
+ /** What an uninstall dropped, kept for {@link SNAPSHOT_DAYS} days (RFC 0008 §5.4). */
14
+ export declare const APPS_SNAPSHOT_TABLE = "kosh_app_snapshot";
15
+ /** How long an uninstall's snapshot is kept. */
16
+ export declare const SNAPSHOT_DAYS = 30;
13
17
  /** Tables the framework owns (not a model's): migrations never alter or drop them. */
14
18
  export declare const FRAMEWORK_TABLES: readonly string[];
15
19
  /** Creates the App manager's tables (idempotent). */
@@ -50,8 +54,16 @@ export interface PreparedApps {
50
54
  readonly migration: MigrationPlan;
51
55
  /** True when the plan changes nothing. */
52
56
  readonly empty: boolean;
53
- /** An upgrade may drop columns or tables its new code no longer has (`removeData`). */
57
+ /** An upgrade may drop columns or tables its new code no longer has (`removeData`); an uninstall drops its own. */
54
58
  readonly allowDestructive: boolean;
59
+ /** The installed and disabled modules before the change (an uninstall's hooks run on it). */
60
+ readonly before: KoshRegistry;
61
+ /** What an uninstall drops: each table, or column, with the rows that hold data. */
62
+ readonly drops: readonly {
63
+ readonly table: string;
64
+ readonly column: string | null;
65
+ readonly rows: number;
66
+ }[];
55
67
  }
56
68
  /** Options of {@link prepareApps}. */
57
69
  export interface PrepareAppsOptions {
@@ -111,6 +123,9 @@ export interface AppsResult {
111
123
  readonly enabled: readonly string[];
112
124
  readonly disabled: readonly string[];
113
125
  readonly upgraded: readonly string[];
126
+ readonly uninstalled: readonly string[];
127
+ /** The snapshot of what the uninstall dropped (`kosh_app_snapshot.id`), or null. */
128
+ readonly snapshot: number | null;
114
129
  }
115
130
  /**
116
131
  * Runs every served module's `hooks.defaults` (idempotent: approval policies, document templates, activity types,
@@ -33,8 +33,17 @@ export const APPS_LOG_TABLE = 'kosh_app_log';
33
33
  * installed set (`createAppHost`).
34
34
  */
35
35
  export const APPS_CHANNEL = 'kosh_apps';
36
+ /** What an uninstall dropped, kept for {@link SNAPSHOT_DAYS} days (RFC 0008 §5.4). */
37
+ export const APPS_SNAPSHOT_TABLE = 'kosh_app_snapshot';
38
+ /** How long an uninstall's snapshot is kept. */
39
+ export const SNAPSHOT_DAYS = 30;
36
40
  /** Tables the framework owns (not a model's): migrations never alter or drop them. */
37
- export const FRAMEWORK_TABLES = [IDEMPOTENCY_TABLE, APPS_TABLE, APPS_LOG_TABLE];
41
+ export const FRAMEWORK_TABLES = [
42
+ IDEMPOTENCY_TABLE,
43
+ APPS_TABLE,
44
+ APPS_LOG_TABLE,
45
+ APPS_SNAPSHOT_TABLE,
46
+ ];
38
47
  /** Creates the App manager's tables (idempotent). */
39
48
  export function appTableStatements() {
40
49
  return [
@@ -62,6 +71,16 @@ export function appTableStatements() {
62
71
  digest text NOT NULL,
63
72
  migration_file text,
64
73
  migration_content text
74
+ )`,
75
+ params: [],
76
+ },
77
+ {
78
+ text: `CREATE TABLE IF NOT EXISTS ${APPS_SNAPSHOT_TABLE} (
79
+ id bigserial PRIMARY KEY,
80
+ at timestamptz NOT NULL DEFAULT now(),
81
+ actor text NOT NULL,
82
+ modules text[] NOT NULL,
83
+ tables jsonb NOT NULL
65
84
  )`,
66
85
  params: [],
67
86
  },
@@ -209,7 +228,7 @@ export async function prepareApps(options) {
209
228
  registry: schema.registry,
210
229
  ignore: FRAMEWORK_TABLES,
211
230
  });
212
- const removeData = options.removeData === true && plan.upgrade.length > 0;
231
+ const removeData = (options.removeData === true && plan.upgrade.length > 0) || plan.uninstall.length > 0;
213
232
  if (migration.destructive.length > 0 && !removeData)
214
233
  throw new AppPlanError(`The change would drop or alter data: ${migration.destructive.map((op) => migrate.renderOperation(op)).join('; ')}.`, plan.upgrade.length > 0
215
234
  ? 'Upgrade with "remove data" (shutro-erp apps upgrade --remove-data) to drop them, or keep the fields in the code.'
@@ -234,6 +253,21 @@ WHERE table_schema = 'public' AND table_name = $1 AND column_name = $2`,
234
253
  }
235
254
  if (orphaned.length > 0)
236
255
  throw new AppPlanError(`Disabling would leave required fields that no enabled module fills: ${orphaned.join(', ')}.`, 'Disable the modules that extend these models too, or make the fields optional.');
256
+ // What an uninstall drops, with the rows that hold data: the plan shows it before anyone confirms.
257
+ const drops = [];
258
+ if (plan.uninstall.length > 0)
259
+ for (const op of migration.destructive) {
260
+ if (op.kind !== 'dropTable' && op.kind !== 'dropColumn')
261
+ continue;
262
+ const column = op.kind === 'dropColumn' ? op.column : null;
263
+ const [row] = await driver.query({
264
+ text: column === null
265
+ ? `SELECT count(*)::text AS n FROM "${op.table}"`
266
+ : `SELECT count(*)::text AS n FROM "${op.table}" WHERE "${column}" IS NOT NULL`,
267
+ params: [],
268
+ });
269
+ drops.push({ table: op.table, column, rows: Number(row?.n ?? 0) });
270
+ }
237
271
  return {
238
272
  plan,
239
273
  current,
@@ -244,8 +278,11 @@ WHERE table_schema = 'public' AND table_name = $1 AND column_name = $2`,
244
278
  plan.enable.length === 0 &&
245
279
  plan.disable.length === 0 &&
246
280
  plan.upgrade.length === 0 &&
281
+ plan.uninstall.length === 0 &&
247
282
  migration.statements.length === 0,
248
283
  allowDestructive: removeData,
284
+ before,
285
+ drops,
249
286
  };
250
287
  }
251
288
  /**
@@ -311,6 +348,7 @@ const defaultLoad = async (context, modules) => {
311
348
  export async function applyApps(prepared, options) {
312
349
  const { plan } = prepared;
313
350
  const step = options.onStep ?? (() => { });
351
+ let snapshotId = null;
314
352
  const file = prepared.migration.statements.length > 0
315
353
  ? await migrate.writeMigrationFile({
316
354
  dir: options.dir,
@@ -328,6 +366,45 @@ export async function applyApps(prepared, options) {
328
366
  // Before the first change the table was just created empty: compare with what was planned from.
329
367
  if (now !== null && now.length > 0 && digestOf(now) !== digestOf(prepared.current))
330
368
  throw new AppPlanError('The installed apps changed since this change was planned.', 'Plan it again.');
369
+ const removed = plan.uninstall.map((u) => u.name);
370
+ let snapshot = null;
371
+ if (removed.length > 0) {
372
+ // Each module may refuse (Accounting while posted entries exist), on the modules as they are now.
373
+ const before = orm.createShutro({ driver: tx, registry: prepared.before.registry });
374
+ for (const name of removed) {
375
+ const hook = prepared.before.loaded.byName.get(name)?.hooks?.preUninstall;
376
+ if (hook === undefined)
377
+ continue;
378
+ step('hooks', name);
379
+ try {
380
+ await before.bootstrap({ reason: `uninstall ${name}` }, (env) => hook(env));
381
+ }
382
+ catch (error) {
383
+ throw new AppPlanError(`${name} cannot be uninstalled: ${error instanceof Error ? error.message : String(error)}`, 'Disable it instead: the app goes away and its data stays.');
384
+ }
385
+ }
386
+ // The snapshot: every dropped table's rows and every dropped column's values, kept for 30 days.
387
+ step('state', 'snapshot');
388
+ const tables = [];
389
+ for (const d of prepared.drops) {
390
+ const [row] = await tx.query({
391
+ text: d.column === null
392
+ ? `SELECT coalesce(json_agg(t), '[]'::json) AS rows FROM "${d.table}" t`
393
+ : `SELECT coalesce(json_agg(json_build_object('id', id, 'value', "${d.column}")), '[]'::json) AS rows FROM "${d.table}" WHERE "${d.column}" IS NOT NULL`,
394
+ params: [],
395
+ });
396
+ tables.push({ table: d.table, column: d.column, rows: row?.rows ?? [] });
397
+ }
398
+ await tx.query({
399
+ text: `DELETE FROM ${APPS_SNAPSHOT_TABLE} WHERE at < now() - make_interval(days => $1)`,
400
+ params: [SNAPSHOT_DAYS],
401
+ });
402
+ const [made] = await tx.query({
403
+ text: `INSERT INTO ${APPS_SNAPSHOT_TABLE} (actor, modules, tables) VALUES ($1, $2, $3::jsonb) RETURNING id::text AS id`,
404
+ params: [options.actor, removed, tables],
405
+ });
406
+ snapshot = made === undefined ? null : Number(made.id);
407
+ }
331
408
  // Upgrades run the modules' data migrations even without schema statements (Shutro tracks the versions).
332
409
  if (file !== null || plan.upgrade.length > 0) {
333
410
  step('schema', `${prepared.migration.statements.length} statement(s)`);
@@ -373,6 +450,14 @@ export async function applyApps(prepared, options) {
373
450
  step('data', upgraded.join(', '));
374
451
  await (options.load ?? defaultLoad)({ shutro, driver: tx, registry: prepared.runtime, company: companyId }, upgraded);
375
452
  }
453
+ if (removed.length > 0) {
454
+ step('data', `remove ${removed.join(', ')}`);
455
+ await removeModuleData(tx, shutro, prepared, removed);
456
+ await tx.query({
457
+ text: `DELETE FROM ${APPS_TABLE} WHERE name = ANY($1)`,
458
+ params: [removed],
459
+ });
460
+ }
376
461
  step('state', `${plan.after.length} module(s)`);
377
462
  for (const row of plan.after)
378
463
  await tx.query({
@@ -396,6 +481,8 @@ VALUES ($1, $2::jsonb, $3, $4, $5, $6)`,
396
481
  enable: plan.enable,
397
482
  disable: plan.disable,
398
483
  upgrade: plan.upgrade,
484
+ uninstall: plan.uninstall,
485
+ snapshot,
399
486
  migration: file === null ? null : `${file.version}_${file.name}.sql`,
400
487
  },
401
488
  sql,
@@ -410,6 +497,7 @@ VALUES ($1, $2::jsonb, $3, $4, $5, $6)`,
410
497
  ...plan.enable.map((n) => `enabled ${n}`),
411
498
  ...plan.disable.map((m) => `disabled ${m.name}`),
412
499
  ...plan.upgrade.map((u) => `upgraded ${u.name} ${u.from} → ${u.to}`),
500
+ ...plan.uninstall.map((u) => `uninstalled ${u.name} (data removed)`),
413
501
  ];
414
502
  await shutro.bootstrap({ reason: 'apps change audit' }, (env) => audit(env, {
415
503
  kind: 'apps.change',
@@ -419,6 +507,7 @@ VALUES ($1, $2::jsonb, $3, $4, $5, $6)`,
419
507
  details: {
420
508
  by: options.actor,
421
509
  migration: file === null ? null : `${file.version}_${file.name}.sql`,
510
+ ...(removed.length === 0 ? {} : { snapshot, drops: prepared.drops }),
422
511
  },
423
512
  ...(options.audit?.ip === undefined ? {} : { ip: options.audit.ip }),
424
513
  }));
@@ -428,6 +517,7 @@ VALUES ($1, $2::jsonb, $3, $4, $5, $6)`,
428
517
  text: 'SELECT pg_notify($1, $2)',
429
518
  params: [APPS_CHANNEL, digestOf(plan.after)],
430
519
  });
520
+ snapshotId = snapshot;
431
521
  });
432
522
  }
433
523
  catch (error) {
@@ -441,5 +531,77 @@ VALUES ($1, $2::jsonb, $3, $4, $5, $6)`,
441
531
  enabled: plan.enable,
442
532
  disabled: plan.disable.map((m) => m.name),
443
533
  upgraded: plan.upgrade.map((u) => u.name),
534
+ uninstalled: plan.uninstall.map((u) => u.name),
535
+ snapshot: snapshotId,
444
536
  };
445
537
  }
538
+ /** Whether a table exists (a failed query would abort the transaction). */
539
+ async function hasTable(tx, table) {
540
+ const [row] = await tx.query({
541
+ text: 'SELECT to_regclass($1)::text AS found',
542
+ params: [`public.${table}`],
543
+ });
544
+ return row?.found !== null && row?.found !== undefined;
545
+ }
546
+ /**
547
+ * After an uninstall's schema change (RFC 0008 §5.4): the modules' records in the models that stay (by external
548
+ * id; one that others still use is kept and logged), their external ids, their groups and role grants, the runtime
549
+ * roles' rights on models that went, and Shutro's record of the modules.
550
+ */
551
+ async function removeModuleData(tx, shutro, prepared, removed) {
552
+ const models = new Set([...prepared.runtime.registry.models.keys()].map(String));
553
+ const ids = await tx.query({
554
+ text: 'SELECT model, res_id::text AS res_id, name FROM shutro_external_id WHERE module = ANY($1)',
555
+ params: [removed],
556
+ });
557
+ for (const row of ids) {
558
+ if (!models.has(row.model))
559
+ continue;
560
+ try {
561
+ await shutro.bootstrap({ reason: `uninstall: ${row.name}` }, async (env) => {
562
+ const records = env.model(row.model);
563
+ await records.browse([Number(row.res_id)]).unlink();
564
+ });
565
+ }
566
+ catch {
567
+ // Still used by records that stay (a sequence, a category): kept, without its module.
568
+ }
569
+ }
570
+ await tx.query({
571
+ text: 'DELETE FROM shutro_external_id WHERE module = ANY($1)',
572
+ params: [removed],
573
+ });
574
+ const byName = prepared.before.loaded.byName;
575
+ const groups = removed.flatMap((n) => (byName.get(n)?.shutro.groups ?? []).map((g) => g.code));
576
+ const permissions = removed.flatMap((n) => (byName.get(n)?.shutro.permissions ?? []).map((p) => p.code));
577
+ if (groups.length > 0 && (await hasTable(tx, 'auth_user_group')))
578
+ await tx.query({
579
+ text: 'DELETE FROM auth_user_group WHERE "group" = ANY($1)',
580
+ params: [groups],
581
+ });
582
+ const kept = [...models];
583
+ if (await hasTable(tx, 'shutro_role')) {
584
+ await tx.query({
585
+ text: 'UPDATE shutro_role SET implies = array(SELECT g FROM unnest(implies) AS g WHERE NOT (g = ANY($1))) WHERE implies && $1',
586
+ params: [groups],
587
+ });
588
+ await tx.query({
589
+ text: 'DELETE FROM shutro_role_access WHERE role = ANY($1) OR NOT (model = ANY($2))',
590
+ params: [groups, kept],
591
+ });
592
+ await tx.query({
593
+ text: 'DELETE FROM shutro_role_rule WHERE role = ANY($1) OR NOT (model = ANY($2))',
594
+ params: [groups, kept],
595
+ });
596
+ await tx.query({
597
+ text: 'DELETE FROM shutro_role_permission WHERE role = ANY($1) OR permission = ANY($2)',
598
+ params: [groups, permissions],
599
+ });
600
+ await tx.query({
601
+ text: 'DELETE FROM shutro_role_field WHERE NOT (model = ANY($1))',
602
+ params: [kept],
603
+ });
604
+ }
605
+ if (await hasTable(tx, 'shutro_module'))
606
+ await tx.query({ text: 'DELETE FROM shutro_module WHERE name = ANY($1)', params: [removed] });
607
+ }
@@ -273,6 +273,8 @@ export const baseShutro = defineModule({
273
273
  version: '0.1.0',
274
274
  groups: [
275
275
  defineGroup({ code: 'base.user', label: 'Internal user' }),
276
+ // Developer mode (RFC 0031 §3.1): the explain popovers of fields, buttons and models.
277
+ defineGroup({ code: 'base.developer', label: 'Developer', implies: ['base.user'] }),
276
278
  defineGroup({
277
279
  code: 'base.admin',
278
280
  label: 'Administrator',
@@ -50,6 +50,8 @@ export interface UserInfo {
50
50
  readonly administrator: boolean;
51
51
  /** Administrator rights are withheld until this session has a second factor (enable or verify 2FA). */
52
52
  readonly administratorNeedsSecondFactor: boolean;
53
+ /** May turn on Developer mode (RFC 0031 §3.1): administrators and the `base.developer` group. */
54
+ readonly developer: boolean;
53
55
  /** The policy requires two-factor sign-in of everyone and it is not on: nothing but setting it up is allowed. */
54
56
  readonly twoFactorSetupRequired: boolean;
55
57
  }
@@ -174,6 +176,12 @@ export declare class AuthService {
174
176
  /** Completes a pending sign-in with a TOTP or recovery code (a recovery code works once). */
175
177
  verifyTwoFactor(session: SessionInfo, code: string): Promise<void>;
176
178
  /** Turns 2FA off (password and a current code required). */
179
+ /**
180
+ * A step-up check (RFC 0008 §5.4): the user's current authenticator code, asked again for a dangerous action
181
+ * (removing an app with its data). Recovery codes do not count. @throws {HttpProblem} 409 when two-factor is off,
182
+ * 422 when the code does not match
183
+ */
184
+ confirmSecondFactor(user: number, code: string): Promise<void>;
177
185
  disableTwoFactor(session: SessionInfo, password: string, code: string): Promise<void>;
178
186
  /** Changes the password; every other session of the user is revoked. */
179
187
  changePassword(session: SessionInfo, current: string, next: string): Promise<void>;