shutro-erp 0.3.6 → 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 (623) hide show
  1. package/dist/account_asset/.kosh/bindings.js +1 -1
  2. package/dist/account_asset/.kosh/schema.d.ts +530 -8
  3. package/dist/account_asset/i18n/bn.d.ts +2 -0
  4. package/dist/account_asset/i18n/bn.js +97 -0
  5. package/dist/account_asset/index.js +2 -0
  6. package/dist/account_asset/module.d.ts +4899 -1445
  7. package/dist/account_budget/.kosh/bindings.js +1 -1
  8. package/dist/account_budget/.kosh/schema.d.ts +1130 -56
  9. package/dist/account_budget/i18n/bn.d.ts +2 -0
  10. package/dist/account_budget/i18n/bn.js +63 -0
  11. package/dist/account_budget/index.js +14 -0
  12. package/dist/account_budget/module.d.ts +17958 -4532
  13. package/dist/account_budget/module.js +15 -0
  14. package/dist/account_portal/.kosh/bindings.js +1 -1
  15. package/dist/account_portal/.kosh/schema.d.ts +530 -8
  16. package/dist/account_portal/i18n/bn.d.ts +2 -0
  17. package/dist/account_portal/i18n/bn.js +6 -0
  18. package/dist/account_portal/index.js +2 -0
  19. package/dist/account_portal/module.d.ts +5167 -1693
  20. package/dist/accounting/.kosh/bindings.js +1 -1
  21. package/dist/accounting/.kosh/schema.d.ts +530 -8
  22. package/dist/accounting/closing.js +1 -0
  23. package/dist/accounting/fiscal.d.ts +578 -0
  24. package/dist/accounting/fiscal.js +401 -0
  25. package/dist/accounting/followups.d.ts +618 -0
  26. package/dist/accounting/followups.js +420 -0
  27. package/dist/accounting/i18n/bn.d.ts +2 -0
  28. package/dist/accounting/i18n/bn.js +425 -0
  29. package/dist/accounting/index.d.ts +2 -0
  30. package/dist/accounting/index.js +649 -2
  31. package/dist/accounting/module.d.ts +4998 -1451
  32. package/dist/accounting/module.js +124 -18
  33. package/dist/accounting/statements.d.ts +878 -0
  34. package/dist/accounting/statements.js +728 -0
  35. package/dist/accounting/web/BankReconcilePage.d.ts +9 -0
  36. package/dist/accounting/web/BankReconcilePage.js +138 -0
  37. package/dist/accounting/web/FollowupsPage.d.ts +8 -0
  38. package/dist/accounting/web/FollowupsPage.js +54 -0
  39. package/dist/accounting/web/PostingPreview.d.ts +8 -0
  40. package/dist/accounting/web/PostingPreview.js +26 -0
  41. package/dist/accounting/web/index.d.ts +3 -0
  42. package/dist/accounting/web/index.js +9 -0
  43. package/dist/accounting/web/messages.js +140 -0
  44. package/dist/approvals/.kosh/bindings.js +1 -1
  45. package/dist/approvals/.kosh/schema.d.ts +272 -4
  46. package/dist/approvals/demo.d.ts +7 -0
  47. package/dist/approvals/demo.js +41 -0
  48. package/dist/approvals/i18n/bn.d.ts +2 -0
  49. package/dist/approvals/i18n/bn.js +133 -0
  50. package/dist/approvals/index.d.ts +19 -1
  51. package/dist/approvals/index.js +477 -7
  52. package/dist/approvals/mails.d.ts +7 -2
  53. package/dist/approvals/mails.js +8 -3
  54. package/dist/approvals/module.d.ts +2930 -1
  55. package/dist/approvals/module.js +468 -7
  56. package/dist/approvals/web/Config.d.ts +12 -0
  57. package/dist/approvals/web/Config.js +60 -0
  58. package/dist/approvals/web/Matrix.d.ts +9 -0
  59. package/dist/approvals/web/Matrix.js +26 -0
  60. package/dist/approvals/web/index.d.ts +3 -1
  61. package/dist/approvals/web/index.js +8 -1
  62. package/dist/approvals/web/messages.js +92 -0
  63. package/dist/approvals_purchase/.kosh/bindings.js +15 -0
  64. package/dist/approvals_purchase/.kosh/schema.d.ts +1997 -0
  65. package/dist/approvals_purchase/i18n/bn.d.ts +2 -0
  66. package/dist/approvals_purchase/i18n/bn.js +5 -0
  67. package/dist/approvals_purchase/index.d.ts +3 -0
  68. package/dist/approvals_purchase/index.js +47 -0
  69. package/dist/approvals_purchase/module.d.ts +12670 -0
  70. package/dist/approvals_purchase/module.js +84 -0
  71. package/dist/attendance/.kosh/bindings.js +1 -1
  72. package/dist/attendance/.kosh/schema.d.ts +59 -1
  73. package/dist/attendance/i18n/bn.d.ts +2 -0
  74. package/dist/attendance/i18n/bn.js +147 -0
  75. package/dist/attendance/index.js +3 -1
  76. package/dist/attendance/module.d.ts +452 -8
  77. package/dist/automation/engine.js +1 -1
  78. package/dist/calendar/.kosh/bindings.js +1 -1
  79. package/dist/calendar/.kosh/schema.d.ts +2 -1
  80. package/dist/calendar/i18n/bn.d.ts +2 -0
  81. package/dist/calendar/i18n/bn.js +80 -0
  82. package/dist/calendar/index.js +77 -0
  83. package/dist/calendar/module.d.ts +26 -0
  84. package/dist/calendar/module.js +14 -4
  85. package/dist/cli/apps.js +25 -5
  86. package/dist/cli/init.js +14 -4
  87. package/dist/cli/templates.d.ts +1 -1
  88. package/dist/cli/templates.js +34 -5
  89. package/dist/contacts/.kosh/bindings.js +1 -1
  90. package/dist/contacts/.kosh/schema.d.ts +2 -1
  91. package/dist/contacts/i18n/bn.d.ts +2 -0
  92. package/dist/contacts/i18n/bn.js +67 -0
  93. package/dist/contacts/index.js +3 -0
  94. package/dist/contacts/module.d.ts +42 -0
  95. package/dist/contacts/module.js +6 -1
  96. package/dist/crm/.kosh/bindings.js +1 -1
  97. package/dist/crm/.kosh/schema.d.ts +2 -1
  98. package/dist/crm/i18n/bn.d.ts +2 -0
  99. package/dist/crm/i18n/bn.js +80 -0
  100. package/dist/crm/index.js +22 -0
  101. package/dist/crm/module.d.ts +20 -0
  102. package/dist/crm/module.js +4 -2
  103. package/dist/documents/.kosh/bindings.js +1 -1
  104. package/dist/documents/.kosh/schema.d.ts +2 -1
  105. package/dist/documents/i18n/bn.d.ts +2 -0
  106. package/dist/documents/i18n/bn.js +83 -0
  107. package/dist/documents/index.js +128 -1
  108. package/dist/documents/module.d.ts +20 -0
  109. package/dist/documents/module.js +1 -0
  110. package/dist/event/.kosh/bindings.js +1 -1
  111. package/dist/event/.kosh/schema.d.ts +2 -1
  112. package/dist/event/i18n/bn.d.ts +2 -0
  113. package/dist/event/i18n/bn.js +101 -0
  114. package/dist/event/index.js +66 -0
  115. package/dist/event/module.d.ts +20 -0
  116. package/dist/event/module.js +8 -1
  117. package/dist/event_account/.kosh/bindings.js +1 -1
  118. package/dist/event_account/.kosh/schema.d.ts +530 -8
  119. package/dist/event_account/i18n/bn.d.ts +2 -0
  120. package/dist/event_account/i18n/bn.js +5 -0
  121. package/dist/event_account/index.js +2 -0
  122. package/dist/event_account/module.d.ts +4905 -1431
  123. package/dist/expense/.kosh/bindings.js +1 -1
  124. package/dist/expense/.kosh/schema.d.ts +323 -2
  125. package/dist/expense/i18n/bn.d.ts +2 -0
  126. package/dist/expense/i18n/bn.js +155 -0
  127. package/dist/expense/index.js +8 -1
  128. package/dist/expense/module.d.ts +2715 -694
  129. package/dist/expense/module.js +3 -1
  130. package/dist/expense_account/.kosh/bindings.js +1 -1
  131. package/dist/expense_account/.kosh/schema.d.ts +587 -8
  132. package/dist/expense_account/i18n/bn.d.ts +2 -0
  133. package/dist/expense_account/i18n/bn.js +15 -0
  134. package/dist/expense_account/index.js +2 -0
  135. package/dist/expense_account/module.d.ts +8094 -2619
  136. package/dist/helpdesk/.kosh/bindings.js +1 -1
  137. package/dist/helpdesk/.kosh/schema.d.ts +2 -1
  138. package/dist/helpdesk/i18n/bn.d.ts +2 -0
  139. package/dist/helpdesk/i18n/bn.js +127 -0
  140. package/dist/helpdesk/index.js +2 -0
  141. package/dist/helpdesk/module.d.ts +20 -0
  142. package/dist/hr/.kosh/bindings.js +1 -1
  143. package/dist/hr/.kosh/schema.d.ts +59 -1
  144. package/dist/hr/demo.js +11 -1
  145. package/dist/hr/i18n/bn.d.ts +2 -0
  146. package/dist/hr/i18n/bn.js +126 -0
  147. package/dist/hr/index.d.ts +1 -1
  148. package/dist/hr/index.js +158 -12
  149. package/dist/hr/module.d.ts +932 -59
  150. package/dist/hr/module.js +114 -17
  151. package/dist/{mrp → indent}/.kosh/bindings.js +2 -2
  152. package/dist/indent/.kosh/schema.d.ts +2084 -0
  153. package/dist/indent/demo.d.ts +7 -0
  154. package/dist/indent/demo.js +48 -0
  155. package/dist/indent/i18n/bn.d.ts +2 -0
  156. package/dist/indent/i18n/bn.js +143 -0
  157. package/dist/indent/index.d.ts +3 -0
  158. package/dist/indent/index.js +450 -0
  159. package/dist/{mrp → indent}/module.d.ts +7164 -3075
  160. package/dist/indent/module.js +792 -0
  161. package/dist/indent_purchase/.kosh/bindings.d.ts +22 -0
  162. package/dist/indent_purchase/.kosh/bindings.js +15 -0
  163. package/dist/indent_purchase/.kosh/schema.d.ts +2710 -0
  164. package/dist/indent_purchase/i18n/bn.d.ts +2 -0
  165. package/dist/indent_purchase/i18n/bn.js +13 -0
  166. package/dist/indent_purchase/index.d.ts +3 -0
  167. package/dist/indent_purchase/index.js +61 -0
  168. package/dist/indent_purchase/module.d.ts +27950 -0
  169. package/dist/indent_purchase/module.js +163 -0
  170. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  171. package/dist/l10n_bd/.kosh/schema.d.ts +530 -8
  172. package/dist/l10n_bd/i18n/bn.d.ts +2 -0
  173. package/dist/l10n_bd/i18n/bn.js +69 -0
  174. package/dist/l10n_bd/index.js +2 -0
  175. package/dist/l10n_bd/module.d.ts +5167 -1693
  176. package/dist/l10n_bd_payroll/.kosh/bindings.js +1 -1
  177. package/dist/l10n_bd_payroll/.kosh/schema.d.ts +587 -8
  178. package/dist/l10n_bd_payroll/i18n/bn.d.ts +2 -0
  179. package/dist/l10n_bd_payroll/i18n/bn.js +4 -0
  180. package/dist/l10n_bd_payroll/index.js +2 -0
  181. package/dist/l10n_bd_payroll/module.d.ts +5910 -1548
  182. package/dist/l10n_bd_pos/.kosh/bindings.d.ts +22 -0
  183. package/dist/{mrp_account → l10n_bd_pos}/.kosh/bindings.js +2 -2
  184. package/dist/{mrp_account → l10n_bd_pos}/.kosh/schema.d.ts +693 -651
  185. package/dist/l10n_bd_pos/i18n/bn.d.ts +2 -0
  186. package/dist/l10n_bd_pos/i18n/bn.js +32 -0
  187. package/dist/l10n_bd_pos/index.d.ts +3 -0
  188. package/dist/l10n_bd_pos/index.js +111 -0
  189. package/dist/{mrp_account → l10n_bd_pos}/module.d.ts +7402 -7647
  190. package/dist/l10n_bd_pos/module.js +43 -0
  191. package/dist/l10n_in/.kosh/bindings.js +1 -1
  192. package/dist/l10n_in/.kosh/schema.d.ts +530 -8
  193. package/dist/l10n_in/i18n/bn.d.ts +2 -0
  194. package/dist/l10n_in/i18n/bn.js +95 -0
  195. package/dist/l10n_in/index.js +2 -0
  196. package/dist/l10n_in/module.d.ts +6703 -1652
  197. package/dist/l10n_in/module.js +25 -5
  198. package/dist/l10n_in/web/GstSettings.js +2 -2
  199. package/dist/mail/inbound/handle.js +2 -4
  200. package/dist/mail/inbound/models.js +5 -3
  201. package/dist/mail/index.d.ts +12 -1
  202. package/dist/mail/index.js +139 -2
  203. package/dist/mail/send.d.ts +56 -0
  204. package/dist/mail/send.js +394 -0
  205. package/dist/mail/web/SendButton.d.ts +9 -0
  206. package/dist/mail/web/SendButton.js +71 -0
  207. package/dist/mail/web/SendSelected.d.ts +8 -0
  208. package/dist/mail/web/SendSelected.js +33 -0
  209. package/dist/mail/web/index.d.ts +2 -0
  210. package/dist/mail/web/index.js +6 -0
  211. package/dist/mail/web/messages.js +36 -0
  212. package/dist/maintenance/.kosh/bindings.js +1 -1
  213. package/dist/maintenance/.kosh/schema.d.ts +2 -1
  214. package/dist/maintenance/i18n/bn.d.ts +2 -0
  215. package/dist/maintenance/i18n/bn.js +132 -0
  216. package/dist/maintenance/index.js +85 -0
  217. package/dist/maintenance/module.d.ts +25 -0
  218. package/dist/maintenance/module.js +27 -9
  219. package/dist/maintenance_stock/.kosh/bindings.js +1 -1
  220. package/dist/maintenance_stock/.kosh/schema.d.ts +298 -2
  221. package/dist/maintenance_stock/i18n/bn.d.ts +2 -0
  222. package/dist/maintenance_stock/i18n/bn.js +21 -0
  223. package/dist/maintenance_stock/index.js +2 -0
  224. package/dist/maintenance_stock/module.d.ts +5302 -1256
  225. package/dist/maintenance_stock/module.js +1 -0
  226. package/dist/modules-sdk/apps.d.ts +10 -0
  227. package/dist/modules-sdk/apps.js +32 -0
  228. package/dist/modules-sdk/index.d.ts +1 -1
  229. package/dist/modules-sdk/manifest.d.ts +68 -2
  230. package/dist/payment/.kosh/bindings.js +1 -1
  231. package/dist/payment/.kosh/schema.d.ts +530 -8
  232. package/dist/payment/i18n/bn.d.ts +2 -0
  233. package/dist/payment/i18n/bn.js +59 -0
  234. package/dist/payment/index.js +85 -0
  235. package/dist/payment/module.d.ts +5134 -1660
  236. package/dist/payment/module.js +17 -3
  237. package/dist/payment/settle.js +1 -0
  238. package/dist/payroll/.kosh/bindings.js +1 -1
  239. package/dist/payroll/.kosh/schema.d.ts +59 -1
  240. package/dist/payroll/i18n/bn.d.ts +2 -0
  241. package/dist/payroll/i18n/bn.js +206 -0
  242. package/dist/payroll/index.js +2 -0
  243. package/dist/payroll/module.d.ts +936 -48
  244. package/dist/payroll_account/.kosh/bindings.js +1 -1
  245. package/dist/payroll_account/.kosh/schema.d.ts +587 -8
  246. package/dist/payroll_account/i18n/bn.d.ts +2 -0
  247. package/dist/payroll_account/i18n/bn.js +10 -0
  248. package/dist/payroll_account/index.js +2 -0
  249. package/dist/payroll_account/module.d.ts +7534 -3192
  250. package/dist/permissions/approvals.d.ts +97 -4
  251. package/dist/permissions/approvals.js +780 -31
  252. package/dist/permissions/index.d.ts +1 -1
  253. package/dist/permissions/index.js +2 -2
  254. package/dist/permissions/models.js +177 -5
  255. package/dist/permissions/routes.d.ts +3 -0
  256. package/dist/permissions/routes.js +36 -2
  257. package/dist/permissions/web/ApprovalPanel.js +43 -5
  258. package/dist/permissions/web/messages.js +52 -0
  259. package/dist/portal/.kosh/bindings.js +1 -1
  260. package/dist/portal/.kosh/schema.d.ts +2 -1
  261. package/dist/portal/documents.d.ts +3 -0
  262. package/dist/portal/documents.js +21 -1
  263. package/dist/portal/i18n/bn.d.ts +2 -0
  264. package/dist/portal/i18n/bn.js +33 -0
  265. package/dist/portal/index.js +2 -0
  266. package/dist/portal/module.d.ts +20 -0
  267. package/dist/portal/routes-documents.js +76 -0
  268. package/dist/pos/.kosh/bindings.js +1 -1
  269. package/dist/pos/.kosh/schema.d.ts +531 -8
  270. package/dist/pos/i18n/bn.d.ts +2 -0
  271. package/dist/pos/i18n/bn.js +133 -0
  272. package/dist/pos/index.js +83 -1
  273. package/dist/pos/module.d.ts +6888 -1777
  274. package/dist/pos/module.js +49 -5
  275. package/dist/pos/routes.js +80 -7
  276. package/dist/pos/web/Register.js +2 -5
  277. package/dist/pos_stock/.kosh/bindings.js +1 -1
  278. package/dist/pos_stock/.kosh/schema.d.ts +626 -9
  279. package/dist/pos_stock/i18n/bn.d.ts +2 -0
  280. package/dist/pos_stock/i18n/bn.js +6 -0
  281. package/dist/pos_stock/index.js +2 -0
  282. package/dist/pos_stock/module.d.ts +10798 -3276
  283. package/dist/product/.kosh/bindings.js +1 -1
  284. package/dist/product/.kosh/schema.d.ts +202 -1
  285. package/dist/product/i18n/bn.d.ts +2 -0
  286. package/dist/product/i18n/bn.js +175 -0
  287. package/dist/product/index.d.ts +1 -0
  288. package/dist/product/index.js +288 -1
  289. package/dist/product/module.d.ts +1725 -0
  290. package/dist/product/module.js +66 -6
  291. package/dist/product/pricelist.d.ts +575 -0
  292. package/dist/product/pricelist.js +257 -0
  293. package/dist/product/variants.d.ts +908 -0
  294. package/dist/product/variants.js +251 -0
  295. package/dist/production/.kosh/bindings.d.ts +22 -0
  296. package/dist/production/.kosh/bindings.js +15 -0
  297. package/dist/production/.kosh/schema.d.ts +2345 -0
  298. package/dist/{mrp → production}/demo.d.ts +1 -1
  299. package/dist/{mrp → production}/demo.js +5 -5
  300. package/dist/production/depth.d.ts +790 -0
  301. package/dist/production/depth.js +262 -0
  302. package/dist/production/i18n/bn.d.ts +2 -0
  303. package/dist/production/i18n/bn.js +350 -0
  304. package/dist/production/index.d.ts +6 -0
  305. package/dist/production/index.js +1256 -0
  306. package/dist/production/module.d.ts +17327 -0
  307. package/dist/production/module.js +1333 -0
  308. package/dist/production/plan.d.ts +921 -0
  309. package/dist/production/plan.js +511 -0
  310. package/dist/production/schedule.d.ts +656 -0
  311. package/dist/production/schedule.js +309 -0
  312. package/dist/production_account/.kosh/bindings.d.ts +22 -0
  313. package/dist/production_account/.kosh/bindings.js +15 -0
  314. package/dist/production_account/.kosh/schema.d.ts +3496 -0
  315. package/dist/production_account/i18n/bn.d.ts +2 -0
  316. package/dist/production_account/i18n/bn.js +93 -0
  317. package/dist/production_account/index.d.ts +4 -0
  318. package/dist/production_account/index.js +246 -0
  319. package/dist/production_account/module.d.ts +1192 -0
  320. package/dist/{mrp_account → production_account}/module.js +72 -18
  321. package/dist/production_account/overhead.d.ts +1195 -0
  322. package/dist/production_account/overhead.js +554 -0
  323. package/dist/project/.kosh/bindings.js +1 -1
  324. package/dist/project/.kosh/schema.d.ts +65 -2
  325. package/dist/project/i18n/bn.d.ts +2 -0
  326. package/dist/project/i18n/bn.js +383 -0
  327. package/dist/project/index.js +123 -0
  328. package/dist/project/module.d.ts +20 -0
  329. package/dist/project/module.js +73 -14
  330. package/dist/project_account/.kosh/bindings.js +1 -1
  331. package/dist/project_account/.kosh/schema.d.ts +530 -8
  332. package/dist/project_account/i18n/bn.d.ts +2 -0
  333. package/dist/project_account/i18n/bn.js +6 -0
  334. package/dist/project_account/index.js +2 -0
  335. package/dist/project_account/module.d.ts +4926 -1452
  336. package/dist/project_portal/.kosh/bindings.js +1 -1
  337. package/dist/project_portal/.kosh/schema.d.ts +65 -2
  338. package/dist/project_portal/i18n/bn.d.ts +2 -0
  339. package/dist/project_portal/i18n/bn.js +6 -0
  340. package/dist/project_portal/index.js +2 -0
  341. package/dist/project_portal/module.d.ts +40 -0
  342. package/dist/project_purchase/.kosh/bindings.js +1 -1
  343. package/dist/project_purchase/.kosh/schema.d.ts +1126 -53
  344. package/dist/project_purchase/i18n/bn.d.ts +2 -0
  345. package/dist/project_purchase/i18n/bn.js +4 -0
  346. package/dist/project_purchase/index.js +2 -0
  347. package/dist/project_purchase/module.d.ts +17912 -4446
  348. package/dist/purchase/.kosh/bindings.js +1 -1
  349. package/dist/purchase/.kosh/schema.d.ts +813 -3
  350. package/dist/purchase/comparison.d.ts +993 -0
  351. package/dist/purchase/comparison.js +415 -0
  352. package/dist/purchase/extras.d.ts +1509 -0
  353. package/dist/purchase/extras.js +445 -0
  354. package/dist/purchase/i18n/bn.d.ts +2 -0
  355. package/dist/purchase/i18n/bn.js +413 -0
  356. package/dist/purchase/index.d.ts +5 -0
  357. package/dist/purchase/index.js +1280 -4
  358. package/dist/purchase/module.d.ts +7139 -398
  359. package/dist/purchase/module.js +295 -11
  360. package/dist/purchase/request.d.ts +696 -0
  361. package/dist/purchase/request.js +446 -0
  362. package/dist/purchase/tender.d.ts +1208 -0
  363. package/dist/purchase/tender.js +507 -0
  364. package/dist/purchase/vendorprice.d.ts +340 -0
  365. package/dist/purchase/vendorprice.js +153 -0
  366. package/dist/purchase_account/.kosh/bindings.js +1 -1
  367. package/dist/purchase_account/.kosh/schema.d.ts +1130 -57
  368. package/dist/purchase_account/i18n/bn.d.ts +2 -0
  369. package/dist/purchase_account/i18n/bn.js +22 -0
  370. package/dist/purchase_account/index.js +19 -0
  371. package/dist/purchase_account/module.d.ts +12276 -2247
  372. package/dist/purchase_account/module.js +241 -9
  373. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  374. package/dist/purchase_portal/.kosh/schema.d.ts +813 -3
  375. package/dist/purchase_portal/i18n/bn.d.ts +2 -0
  376. package/dist/purchase_portal/i18n/bn.js +5 -0
  377. package/dist/purchase_portal/index.js +2 -0
  378. package/dist/purchase_portal/module.d.ts +6849 -353
  379. package/dist/purchase_stock/.kosh/bindings.js +1 -1
  380. package/dist/purchase_stock/.kosh/schema.d.ts +910 -4
  381. package/dist/purchase_stock/i18n/bn.d.ts +2 -0
  382. package/dist/purchase_stock/i18n/bn.js +19 -0
  383. package/dist/purchase_stock/index.d.ts +1 -1
  384. package/dist/purchase_stock/index.js +29 -0
  385. package/dist/purchase_stock/module.d.ts +12410 -1864
  386. package/dist/purchase_stock/module.js +81 -9
  387. package/dist/purchase_stock/scorecard.d.ts +22 -0
  388. package/dist/purchase_stock/scorecard.js +77 -0
  389. package/dist/purchase_stock/web/Scorecard.d.ts +7 -0
  390. package/dist/purchase_stock/web/Scorecard.js +22 -0
  391. package/dist/purchase_stock/web/index.d.ts +11 -0
  392. package/dist/purchase_stock/web/index.js +16 -0
  393. package/dist/purchase_stock/web/messages.d.ts +2 -0
  394. package/dist/purchase_stock/web/messages.js +27 -0
  395. package/dist/purchase_stock_account/.kosh/bindings.js +1 -1
  396. package/dist/purchase_stock_account/.kosh/schema.d.ts +1286 -58
  397. package/dist/purchase_stock_account/i18n/bn.d.ts +2 -0
  398. package/dist/purchase_stock_account/i18n/bn.js +8 -0
  399. package/dist/purchase_stock_account/index.js +18 -0
  400. package/dist/purchase_stock_account/module.d.ts +7 -37995
  401. package/dist/purchase_stock_account/module.js +24 -3
  402. package/dist/recruitment/.kosh/bindings.js +1 -1
  403. package/dist/recruitment/.kosh/schema.d.ts +59 -1
  404. package/dist/recruitment/i18n/bn.d.ts +2 -0
  405. package/dist/recruitment/i18n/bn.js +93 -0
  406. package/dist/recruitment/index.js +64 -0
  407. package/dist/recruitment/module.d.ts +458 -8
  408. package/dist/recruitment/module.js +11 -3
  409. package/dist/reporting/documents.d.ts +5 -0
  410. package/dist/reporting/documents.js +11 -1
  411. package/dist/reporting/index.d.ts +1 -1
  412. package/dist/reporting/index.js +1 -1
  413. package/dist/reporting/web/CalendarView.d.ts +18 -5
  414. package/dist/reporting/web/CalendarView.js +163 -29
  415. package/dist/reporting/web/messages.js +18 -0
  416. package/dist/sale_account/.kosh/bindings.js +1 -1
  417. package/dist/sale_account/.kosh/schema.d.ts +642 -16
  418. package/dist/sale_account/i18n/bn.d.ts +2 -0
  419. package/dist/sale_account/i18n/bn.js +48 -0
  420. package/dist/sale_account/index.d.ts +1 -1
  421. package/dist/sale_account/index.js +102 -4
  422. package/dist/sale_account/module.d.ts +8254 -2212
  423. package/dist/sale_account/module.js +535 -46
  424. package/dist/sale_crm/.kosh/bindings.d.ts +22 -0
  425. package/dist/sale_crm/.kosh/bindings.js +15 -0
  426. package/dist/{mrp → sale_crm}/.kosh/schema.d.ts +389 -567
  427. package/dist/sale_crm/i18n/bn.d.ts +2 -0
  428. package/dist/sale_crm/i18n/bn.js +9 -0
  429. package/dist/sale_crm/index.d.ts +7 -0
  430. package/dist/sale_crm/index.js +104 -0
  431. package/dist/sale_crm/module.d.ts +5609 -0
  432. package/dist/sale_crm/module.js +73 -0
  433. package/dist/sale_portal/.kosh/bindings.js +1 -1
  434. package/dist/sale_portal/.kosh/schema.d.ts +291 -4
  435. package/dist/sale_portal/i18n/bn.d.ts +2 -0
  436. package/dist/sale_portal/i18n/bn.js +7 -0
  437. package/dist/sale_portal/index.js +10 -0
  438. package/dist/sale_portal/module.d.ts +2564 -294
  439. package/dist/sale_project/.kosh/bindings.js +1 -1
  440. package/dist/sale_project/.kosh/schema.d.ts +354 -5
  441. package/dist/sale_project/i18n/bn.d.ts +2 -0
  442. package/dist/sale_project/i18n/bn.js +12 -0
  443. package/dist/sale_project/index.js +2 -0
  444. package/dist/sale_project/module.d.ts +6249 -2402
  445. package/dist/sale_stock/.kosh/bindings.js +1 -1
  446. package/dist/sale_stock/.kosh/schema.d.ts +387 -5
  447. package/dist/sale_stock/i18n/bn.d.ts +2 -0
  448. package/dist/sale_stock/i18n/bn.js +15 -0
  449. package/dist/sale_stock/index.js +2 -0
  450. package/dist/sale_stock/module.d.ts +8057 -1759
  451. package/dist/sale_stock/module.js +20 -3
  452. package/dist/sales/.kosh/bindings.js +1 -1
  453. package/dist/sales/.kosh/schema.d.ts +291 -4
  454. package/dist/sales/i18n/bn.d.ts +2 -0
  455. package/dist/sales/i18n/bn.js +128 -0
  456. package/dist/sales/index.js +120 -5
  457. package/dist/sales/module.d.ts +3567 -633
  458. package/dist/sales/module.js +306 -11
  459. package/dist/server/api/duplicate.d.ts +14 -0
  460. package/dist/server/api/duplicate.js +75 -0
  461. package/dist/server/api/explain.d.ts +70 -0
  462. package/dist/server/api/explain.js +111 -0
  463. package/dist/server/api/metadata.d.ts +2 -0
  464. package/dist/server/api/metadata.js +9 -0
  465. package/dist/server/api/openapi.js +15 -0
  466. package/dist/server/api/rest.d.ts +1 -0
  467. package/dist/server/api/rest.js +76 -12
  468. package/dist/server/api/views.js +1 -1
  469. package/dist/server/apps.d.ts +16 -1
  470. package/dist/server/apps.js +164 -2
  471. package/dist/server/auth/base.js +2 -0
  472. package/dist/server/auth/service.d.ts +8 -0
  473. package/dist/server/auth/service.js +19 -0
  474. package/dist/server/events.d.ts +13 -0
  475. package/dist/server/events.js +22 -0
  476. package/dist/server/i18n.d.ts +27 -0
  477. package/dist/server/i18n.js +143 -0
  478. package/dist/server/index.d.ts +2 -0
  479. package/dist/server/index.js +2 -0
  480. package/dist/server/server.js +6 -0
  481. package/dist/settings/company.d.ts +3 -1
  482. package/dist/settings/company.js +26 -5
  483. package/dist/settings/routes.d.ts +10 -0
  484. package/dist/settings/routes.js +19 -3
  485. package/dist/settings/web/AppsPage.d.ts +3 -2
  486. package/dist/settings/web/AppsPage.js +26 -8
  487. package/dist/settings/web/CompaniesPage.js +91 -6
  488. package/dist/settings/web/messages.js +64 -0
  489. package/dist/sign/.kosh/bindings.js +1 -1
  490. package/dist/sign/.kosh/schema.d.ts +2 -1
  491. package/dist/sign/i18n/bn.d.ts +2 -0
  492. package/dist/sign/i18n/bn.js +97 -0
  493. package/dist/sign/index.d.ts +15 -0
  494. package/dist/sign/index.js +129 -1
  495. package/dist/sign/module.d.ts +40 -0
  496. package/dist/sign/module.js +11 -2
  497. package/dist/stock/.kosh/bindings.js +1 -1
  498. package/dist/stock/.kosh/schema.d.ts +298 -2
  499. package/dist/stock/i18n/bn.d.ts +2 -0
  500. package/dist/stock/i18n/bn.js +311 -0
  501. package/dist/stock/index.d.ts +3 -1
  502. package/dist/stock/index.js +442 -3
  503. package/dist/stock/ledger.d.ts +78 -0
  504. package/dist/stock/ledger.js +582 -0
  505. package/dist/stock/module.d.ts +5280 -2044
  506. package/dist/stock/module.js +222 -23
  507. package/dist/stock/replenish.js +16 -1
  508. package/dist/stock/returns.d.ts +157 -0
  509. package/dist/stock/returns.js +163 -0
  510. package/dist/stock/web/Ledger.d.ts +9 -0
  511. package/dist/stock/web/Ledger.js +108 -0
  512. package/dist/stock/web/Operations.d.ts +8 -0
  513. package/dist/stock/web/Operations.js +33 -0
  514. package/dist/stock/web/ReturnButton.d.ts +7 -0
  515. package/dist/stock/web/ReturnButton.js +62 -0
  516. package/dist/stock/web/index.d.ts +3 -0
  517. package/dist/stock/web/index.js +9 -0
  518. package/dist/stock/web/messages.js +154 -0
  519. package/dist/stock_account/.kosh/bindings.js +1 -1
  520. package/dist/stock_account/.kosh/schema.d.ts +682 -9
  521. package/dist/stock_account/i18n/bn.d.ts +2 -0
  522. package/dist/stock_account/i18n/bn.js +73 -0
  523. package/dist/stock_account/index.d.ts +1 -0
  524. package/dist/stock_account/index.js +131 -3
  525. package/dist/stock_account/landed.d.ts +429 -0
  526. package/dist/stock_account/landed.js +335 -0
  527. package/dist/stock_account/module.d.ts +11247 -3363
  528. package/dist/stock_account/module.js +135 -6
  529. package/dist/stock_account/web/LandedPreview.d.ts +7 -0
  530. package/dist/stock_account/web/LandedPreview.js +23 -0
  531. package/dist/stock_account/web/index.d.ts +11 -0
  532. package/dist/stock_account/web/index.js +16 -0
  533. package/dist/stock_account/web/messages.d.ts +2 -0
  534. package/dist/stock_account/web/messages.js +27 -0
  535. package/dist/todo/i18n/bn.d.ts +2 -0
  536. package/dist/todo/i18n/bn.js +66 -0
  537. package/dist/todo/index.js +59 -0
  538. package/dist/todo/module.d.ts +5 -0
  539. package/dist/todo/module.js +5 -3
  540. package/dist/trade_finance/.kosh/bindings.js +1 -1
  541. package/dist/trade_finance/.kosh/schema.d.ts +2 -1
  542. package/dist/trade_finance/i18n/bn.d.ts +2 -0
  543. package/dist/trade_finance/i18n/bn.js +162 -0
  544. package/dist/trade_finance/index.js +93 -0
  545. package/dist/trade_finance/module.d.ts +26 -0
  546. package/dist/trade_finance/module.js +27 -8
  547. package/dist/trade_finance_account/.kosh/bindings.js +1 -1
  548. package/dist/trade_finance_account/.kosh/schema.d.ts +530 -8
  549. package/dist/trade_finance_account/i18n/bn.d.ts +2 -0
  550. package/dist/trade_finance_account/i18n/bn.js +27 -0
  551. package/dist/trade_finance_account/index.js +2 -0
  552. package/dist/trade_finance_account/module.d.ts +4906 -1432
  553. package/dist/trade_finance_account/module.js +1 -0
  554. package/dist/trade_finance_purchase/.kosh/bindings.js +1 -1
  555. package/dist/trade_finance_purchase/.kosh/schema.d.ts +813 -3
  556. package/dist/trade_finance_purchase/i18n/bn.d.ts +2 -0
  557. package/dist/trade_finance_purchase/i18n/bn.js +6 -0
  558. package/dist/trade_finance_purchase/index.js +2 -0
  559. package/dist/trade_finance_purchase/module.d.ts +6909 -413
  560. package/dist/trade_finance_sale/.kosh/bindings.js +1 -1
  561. package/dist/trade_finance_sale/.kosh/schema.d.ts +291 -4
  562. package/dist/trade_finance_sale/i18n/bn.d.ts +2 -0
  563. package/dist/trade_finance_sale/i18n/bn.js +6 -0
  564. package/dist/trade_finance_sale/index.js +2 -0
  565. package/dist/trade_finance_sale/module.d.ts +2601 -331
  566. package/dist/ui/api.d.ts +2 -0
  567. package/dist/ui/app.d.ts +9 -0
  568. package/dist/ui/app.js +24 -4
  569. package/dist/ui/components/AppIcon.js +2 -2
  570. package/dist/ui/components/Explain.d.ts +69 -0
  571. package/dist/ui/components/Explain.js +62 -0
  572. package/dist/ui/context.d.ts +8 -1
  573. package/dist/ui/context.js +34 -1
  574. package/dist/ui/i18n/messages.d.ts +36 -0
  575. package/dist/ui/i18n/messages.js +72 -0
  576. package/dist/ui/index.d.ts +2 -1
  577. package/dist/ui/index.js +1 -0
  578. package/dist/ui/registry.d.ts +14 -0
  579. package/dist/ui/registry.js +10 -0
  580. package/dist/ui/views/FormView.d.ts +6 -0
  581. package/dist/ui/views/FormView.js +47 -10
  582. package/dist/ui/views/ListView.d.ts +8 -0
  583. package/dist/ui/views/ListView.js +10 -3
  584. package/dist/ui/views/StatsRow.js +2 -1
  585. package/dist/views/extend.js +12 -0
  586. package/dist/views/types.d.ts +13 -2
  587. package/dist/views/validate.js +8 -1
  588. package/dist/website/.kosh/bindings.js +1 -1
  589. package/dist/website/.kosh/schema.d.ts +2 -1
  590. package/dist/website/i18n/bn.d.ts +2 -0
  591. package/dist/website/i18n/bn.js +113 -0
  592. package/dist/website/index.js +36 -0
  593. package/dist/website/module.d.ts +20 -0
  594. package/dist/website/module.js +29 -5
  595. package/dist/website_crm/.kosh/bindings.js +1 -1
  596. package/dist/website_crm/.kosh/schema.d.ts +2 -1
  597. package/dist/website_crm/i18n/bn.d.ts +2 -0
  598. package/dist/website_crm/i18n/bn.js +5 -0
  599. package/dist/website_crm/index.js +2 -0
  600. package/dist/website_crm/module.d.ts +40 -0
  601. package/dist/website_sale/.kosh/bindings.js +1 -1
  602. package/dist/website_sale/.kosh/schema.d.ts +642 -17
  603. package/dist/website_sale/i18n/bn.d.ts +2 -0
  604. package/dist/website_sale/i18n/bn.js +132 -0
  605. package/dist/website_sale/index.js +83 -1
  606. package/dist/website_sale/module.d.ts +18705 -5550
  607. package/dist/website_sale/module.js +32 -10
  608. package/dist/website_sale/shop.js +22 -9
  609. package/dist/website_sale_stock/.kosh/bindings.js +1 -1
  610. package/dist/website_sale_stock/.kosh/schema.d.ts +737 -18
  611. package/dist/website_sale_stock/i18n/bn.d.ts +2 -0
  612. package/dist/website_sale_stock/i18n/bn.js +4 -0
  613. package/dist/website_sale_stock/index.js +2 -0
  614. package/dist/website_sale_stock/module.d.ts +10 -24997
  615. package/dist/website_sale_stock/module.js +7 -3
  616. package/package.json +36 -8
  617. package/dist/mrp/index.d.ts +0 -3
  618. package/dist/mrp/index.js +0 -393
  619. package/dist/mrp/module.js +0 -757
  620. package/dist/mrp_account/index.d.ts +0 -3
  621. package/dist/mrp_account/index.js +0 -22
  622. /package/dist/{mrp → approvals_purchase}/.kosh/bindings.d.ts +0 -0
  623. /package/dist/{mrp_account → indent}/.kosh/bindings.d.ts +0 -0
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { defineModule } from '../modules-sdk/index.js';
8
8
  export { registerAdminApi } from './admin.js';
9
- export { type ApprovalCard, type ApprovalRequestInfo, approvalCards, approvalGuard, approvalState, approvalStats, approveMany, type Decision, decide, decidersOf, groupsOf, inbox, installApprovalPolicies, issueLinks, linkOf, requestApprovals, requestsOf, sweepDeadlines, turnOf, useLink, type Who, whoIs, withdraw, } from './approvals.js';
9
+ export { APPROVER_KEYS, type ApprovalCard, type ApprovalRequestInfo, approvalCards, approvalGuard, approvalMatrix, approvalState, approvalStats, approveMany, type Decision, decide, decidersOf, eventsOf, groupsOf, inbox, installApprovalPolicies, issueLinks, linkOf, MATRIX_PERIODS, type MatrixCard, type MatrixPeriod, notifyBackups, previewPolicy, reassign, requestApprovals, requestsOf, sweepDeadlines, syncLevels, turnOf, useLink, type Who, whoIs, withdraw, } from './approvals.js';
10
10
  export { permissionsShutro } from './models.js';
11
11
  export { registerRoleRoutes } from './roles.js';
12
12
  export { registerApprovalRoutes } from './routes.js';
@@ -12,7 +12,7 @@ import { permissionsShutro } from './models.js';
12
12
  import { registerRoleRoutes } from './roles.js';
13
13
  import { registerApprovalRoutes } from './routes.js';
14
14
  export { registerAdminApi } from './admin.js';
15
- export { approvalCards, approvalGuard, approvalState, approvalStats, approveMany, decide, decidersOf, groupsOf, inbox, installApprovalPolicies, issueLinks, linkOf, requestApprovals, requestsOf, sweepDeadlines, turnOf, useLink, whoIs, withdraw, } from './approvals.js';
15
+ export { APPROVER_KEYS, approvalCards, approvalGuard, approvalMatrix, approvalState, approvalStats, approveMany, decide, decidersOf, eventsOf, groupsOf, inbox, installApprovalPolicies, issueLinks, linkOf, MATRIX_PERIODS, notifyBackups, previewPolicy, reassign, requestApprovals, requestsOf, sweepDeadlines, syncLevels, turnOf, useLink, whoIs, withdraw, } from './approvals.js';
16
16
  export { permissionsShutro } from './models.js';
17
17
  export { registerRoleRoutes } from './roles.js';
18
18
  export { registerApprovalRoutes } from './routes.js';
@@ -29,7 +29,7 @@ export const permissions = defineModule({
29
29
  accessLevels: {
30
30
  auditor: { label: 'Security auditor', groups: ['permissions.auditor'] },
31
31
  },
32
- privateModels: ['approval.request', 'approval.vote', 'approval.link'],
32
+ privateModels: ['approval.request', 'approval.vote', 'approval.link', 'approval.event'],
33
33
  hooks: {
34
34
  defaults: async (env, modules) => {
35
35
  const n = await installApprovalPolicies(env, modules);
@@ -12,7 +12,11 @@ const modelOf = (env, name) => env.model(name);
12
12
  async function readAll(records, keys) {
13
13
  const set = records;
14
14
  // A many-to-many is loaded by the object spec (defaults plus the relation) and read as ids.
15
- const spec = keys.includes('approverUsers') ? { approverUsers: true } : keys;
15
+ const spec = keys.some((k) => k === 'approverUsers' || k === 'companies')
16
+ ? Object.fromEntries(keys
17
+ .filter((k) => ['approverUsers', 'extraUsers', 'backupUsers', 'companies'].includes(k))
18
+ .map((k) => [k, true]))
19
+ : keys;
16
20
  return set.length === 0 ? [] : (await set.load(spec)).read(keys);
17
21
  }
18
22
  /** The installed model a policy names, or `undefined`. */
@@ -35,8 +39,16 @@ const ApprovalPolicy = defineModel('approval.policy', {
35
39
  name: f.char({ required: true }),
36
40
  targetModel: f.char({ required: true, index: true, label: 'Model' }),
37
41
  sequence: f.integer({ default: 10 }),
38
- /** Empty: every company. */
42
+ /**
43
+ * The companies it covers (RFC 0027 §2.3): this company, these companies and every company of this segment
44
+ * together; all three empty: every company.
45
+ */
39
46
  company: f.many2one('res.company', { ondelete: 'cascade' }),
47
+ companies: f.many2many('res.company', {
48
+ relation: 'approval_policy_company_rel',
49
+ label: 'Companies',
50
+ }),
51
+ segment: f.many2one('res.segment', { ondelete: 'restrict', label: 'Segment' }),
40
52
  /** A transition of the record run when every level approved (as the system, attributed in the audit log). */
41
53
  onApproved: f.char(),
42
54
  /** A transition run when a level rejects. */
@@ -47,9 +59,57 @@ const ApprovalPolicy = defineModel('approval.policy', {
47
59
  onWithdrawn: f.char({ label: 'On withdrawn' }),
48
60
  /** A rejection or a sending back must say why. */
49
61
  reasonRequired: f.boolean({ default: true, label: 'Reason required' }),
62
+ /**
63
+ * The records the policy is for, as a Shutro domain on the model (RFC 0025 step 4: a request category's policy
64
+ * covers that category's requests); empty: every record.
65
+ */
66
+ condition: f.json({ label: 'For records where' }),
67
+ /**
68
+ * When a level finds nobody to decide — no person, and no role to stand in (RFC 0027 §2.4): refuse the submission
69
+ * and say why, let administrators decide, or skip the level (recorded as skipped).
70
+ */
71
+ whenNobody: f.selection({ refuse: 'Refuse the submission', admins: 'Administrators decide', skip: 'Skip the level' }, { required: true, default: 'refuse', label: 'When nobody is found' }),
72
+ /**
73
+ * A level whose approvers include someone who already approved this round is approved for them, marked as
74
+ * such (RFC 0027 §2.2); a level needing more approvals still waits for the others.
75
+ */
76
+ skipDuplicates: f.boolean({ default: false, label: 'Skip approvers who already approved' }),
77
+ /** When a level's approvers change, its waiting requests follow: not, adding the new people, or replacing. */
78
+ syncPending: f.selection({ off: 'No', add: 'Add the new approvers', replace: 'Replace the approvers' }, { required: true, default: 'add', label: 'Waiting requests follow changes' }),
79
+ /** Email the people added to a waiting request whose turn it is. */
80
+ notifyOnSync: f.boolean({ default: true, label: 'Tell the people added' }),
50
81
  levels: f.one2many('approval.level', 'policy'),
51
82
  },
52
83
  constraints: {
84
+ validCondition: {
85
+ depends: ['condition', 'targetModel'],
86
+ check: async (records) => {
87
+ const { env, ids } = records;
88
+ return env.asSuperuser({ reason: 'validate approval policy condition' }, async (su) => {
89
+ const failing = [];
90
+ for (const row of await readAll(modelOf(su, 'approval.policy').browse(ids), [
91
+ 'condition',
92
+ 'targetModel',
93
+ ])) {
94
+ const condition = row['condition'];
95
+ if (condition === null || condition === undefined)
96
+ continue;
97
+ const target = row['targetModel'];
98
+ const ok = typeof condition === 'object' &&
99
+ !Array.isArray(condition) &&
100
+ installed(su, target) &&
101
+ (await modelOf(su, String(target))
102
+ .search({ where: condition, limit: 1 })
103
+ .then(() => true)
104
+ .catch(() => false));
105
+ if (!ok)
106
+ failing.push(row['id']);
107
+ }
108
+ return failing;
109
+ });
110
+ },
111
+ message: 'The policy’s condition is not a valid domain on its model.',
112
+ },
53
113
  installedModel: {
54
114
  depends: ['targetModel'],
55
115
  check: async (records) => {
@@ -64,9 +124,60 @@ const ApprovalPolicy = defineModel('approval.policy', {
64
124
  },
65
125
  },
66
126
  });
127
+ /**
128
+ * A business segment (RFC 0027 §2.3): a group of companies — Feed, Poultry, Dairy — that approval policies cover at
129
+ * once; a company belongs to one segment.
130
+ */
131
+ const ResSegment = defineModel('res.segment', {
132
+ label: 'Segment',
133
+ recName: 'name',
134
+ archivable: true,
135
+ order: ['name asc'],
136
+ access: { 'base.user': ['read'], 'approvals.manager': ['read', 'write', 'create', 'unlink'] },
137
+ fields: {
138
+ name: f.char({ required: true, label: 'Name' }),
139
+ code: f.char({ label: 'Code' }),
140
+ /** Its companies; a company is in one segment at most. */
141
+ companies: f.many2many('res.company', {
142
+ relation: 'res_segment_company_rel',
143
+ label: 'Companies',
144
+ }),
145
+ note: f.text({ label: 'Note' }),
146
+ },
147
+ sqlConstraints: { uniqueName: { unique: ['name'], message: 'Another segment has this name.' } },
148
+ constraints: {
149
+ oneSegment: {
150
+ depends: ['companies', 'active'],
151
+ check: async (records) => {
152
+ const { env, ids } = records;
153
+ return env.asSuperuser({ reason: 'validate segments' }, async (su) => {
154
+ const all = await readAll(await modelOf(su, 'res.segment').search({ where: { active: true } }), ['companies']);
155
+ const seen = new Map();
156
+ const failing = new Set();
157
+ for (const row of all)
158
+ for (const c of row['companies'] ?? []) {
159
+ const other = seen.get(c);
160
+ if (other !== undefined) {
161
+ if (ids.includes(row['id']))
162
+ failing.add(row['id']);
163
+ if (ids.includes(other))
164
+ failing.add(other);
165
+ }
166
+ else
167
+ seen.set(c, row['id']);
168
+ }
169
+ return [...failing];
170
+ });
171
+ },
172
+ message: 'A company is in one segment at most.',
173
+ },
174
+ },
175
+ });
67
176
  const ApprovalLevel = defineModel('approval.level', {
68
177
  access: { 'base.user': ['read'], 'approvals.manager': ['read', 'write', 'create', 'unlink'] },
69
178
  order: ['policy asc', 'sequence asc'],
179
+ // Waiting requests follow changes of their level's approvers (RFC 0027 §2.6), after the save commits.
180
+ changeEvents: true,
70
181
  fields: {
71
182
  policy: f.many2one('approval.policy', { required: true, ondelete: 'cascade' }),
72
183
  sequence: f.integer({ default: 10 }),
@@ -81,15 +192,32 @@ const ApprovalLevel = defineModel('approval.level', {
81
192
  role: 'A role',
82
193
  users: 'Named people',
83
194
  manager: 'The requester’s manager',
195
+ department_head: 'The head of the requester’s department',
196
+ department_approvers: 'The approvers of the requester’s department',
197
+ employee: 'A person on the requester’s employee record',
84
198
  field: 'A person on the record',
199
+ record: 'The record decides',
85
200
  }, { required: true, default: 'role', label: 'Approved by' }),
201
+ /** People added to whoever the kind finds (RFC 0027 §2.1). */
202
+ extraUsers: f.many2many('res.users', {
203
+ relation: 'approval_level_extra_user_rel',
204
+ label: 'Also',
205
+ }),
206
+ /** Standing backups: may always decide the level beside its approvers (RFC 0027 §2.2). */
207
+ backupUsers: f.many2many('res.users', {
208
+ relation: 'approval_level_backup_user_rel',
209
+ label: 'Backups',
210
+ }),
86
211
  /** The role (or, for a manager or a field, the role used when there is nobody). */
87
212
  approverRole: f.char({ label: 'Role' }),
88
213
  approverUsers: f.many2many('res.users', {
89
214
  relation: 'approval_level_user_rel',
90
215
  label: 'People',
91
216
  }),
92
- /** A many-to-one field of the record to a user (`user`, `project.user`). */
217
+ /**
218
+ * A many-to-one field of the record to a user (`user`, `project.user`); for "the record decides", the key passed
219
+ * to the model's `approvalUsers(key)` (optional).
220
+ */
93
221
  approverField: f.char({ label: 'Field' }),
94
222
  /** How many decide: any one, all of them, or a number. */
95
223
  quorum: f.selection({ any: 'Any one', all: 'All of them', count: 'A number of them' }, { required: true, default: 'any', label: 'Decided by' }),
@@ -127,7 +255,7 @@ const ApprovalLevel = defineModel('approval.level', {
127
255
  const role = typeof r['approverRole'] === 'string' && r['approverRole'] !== '';
128
256
  const users = Array.isArray(r['approverUsers']) ? r['approverUsers'].length : 0;
129
257
  const kind = r['approverKind'];
130
- return ((kind !== 'users' && !role) ||
258
+ return ((kind === 'role' && !role) ||
131
259
  (kind === 'users' && users === 0) ||
132
260
  (kind === 'field' &&
133
261
  !(typeof r['approverField'] === 'string' && r['approverField'] !== '')) ||
@@ -136,7 +264,7 @@ const ApprovalLevel = defineModel('approval.level', {
136
264
  .map((r) => r['id']);
137
265
  });
138
266
  },
139
- message: 'A level names its approvers: a role (also the fallback of a manager or a field), people, or a field; a quorum by number needs the number.',
267
+ message: 'A level names its approvers: a role, people, or a field; a quorum by number needs the number.',
140
268
  },
141
269
  // A broken condition is refused when saved, not when a user submits a record.
142
270
  validCondition: {
@@ -189,6 +317,11 @@ const ApprovalRequest = defineModel('approval.request', {
189
317
  relation: 'approval_request_user_rel',
190
318
  label: 'Approvers',
191
319
  }),
320
+ /** Backups who may decide it too (from the level). */
321
+ backups: f.many2many('res.users', {
322
+ relation: 'approval_request_backup_rel',
323
+ label: 'Backups',
324
+ }),
192
325
  /** How many approvals it needs. */
193
326
  required: f.integer({ required: true, default: 1, label: 'Approvals needed' }),
194
327
  /** The submission it belongs to (a resubmission after sending back is the next round). */
@@ -200,6 +333,10 @@ const ApprovalRequest = defineModel('approval.request', {
200
333
  /** The role that may also decide since the deadline passed. */
201
334
  escalatedTo: f.char({ label: 'Escalated to' }),
202
335
  escalatedAt: f.datetime({ label: 'Escalated at' }),
336
+ /** When the level's turn came (every lower level approved); the deadline counts from it (RFC 0027 §2.5). */
337
+ startedAt: f.datetime({ label: 'Started' }),
338
+ /** How its approvers were found, written when it was made. */
339
+ explanation: f.text({ label: 'How the approvers were found' }),
203
340
  state: f.selection({
204
341
  pending: 'Pending',
205
342
  approved: 'Approved',
@@ -213,6 +350,35 @@ const ApprovalRequest = defineModel('approval.request', {
213
350
  comment: f.text(),
214
351
  },
215
352
  });
353
+ /**
354
+ * The matrix log (RFC 0027 §2.5): every step of a request — made, its turn, votes, decisions, cancelled, skipped,
355
+ * reminded, escalated — with who and a note. Private like requests: read through the record's approvals.
356
+ */
357
+ const ApprovalEvent = defineModel('approval.event', {
358
+ order: ['at asc', 'id asc'],
359
+ fields: {
360
+ request: f.many2one('approval.request', { required: true, ondelete: 'cascade', index: true }),
361
+ at: f.datetime({ required: true }),
362
+ user: f.many2one('res.users', { ondelete: 'set null' }),
363
+ kind: f.selection({
364
+ created: 'Created',
365
+ started: 'Its turn',
366
+ voted: 'Voted',
367
+ approved: 'Approved',
368
+ rejected: 'Rejected',
369
+ sent_back: 'Sent back',
370
+ cancelled: 'Cancelled',
371
+ skipped: 'Skipped',
372
+ reminded: 'Reminded',
373
+ escalated: 'Escalated',
374
+ auto: 'Approved already',
375
+ reassigned: 'Reassigned',
376
+ synced: 'Synced',
377
+ notified: 'Backups told',
378
+ }, { required: true }),
379
+ note: f.text(),
380
+ },
381
+ });
216
382
  /** One approver's decision on a request (a level decided by several keeps each). */
217
383
  const ApprovalVote = defineModel('approval.vote', {
218
384
  order: ['request asc', 'at asc', 'id asc'],
@@ -226,6 +392,10 @@ const ApprovalVote = defineModel('approval.vote', {
226
392
  onBehalfOf: f.many2one('res.users', { ondelete: 'set null', label: 'For' }),
227
393
  /** Decided from an email link: the address it came from. */
228
394
  ip: f.char({ label: 'From address' }),
395
+ /** Copied from the person's approval at an earlier level (skip approvers who already approved). */
396
+ auto: f.boolean({ default: false, label: 'Approved already' }),
397
+ /** Decided as one of the level's backups. */
398
+ asBackup: f.boolean({ default: false, label: 'As backup' }),
229
399
  },
230
400
  sqlConstraints: {
231
401
  once: { unique: ['request', 'user'], message: 'Each approver decides a request once.' },
@@ -311,9 +481,11 @@ export const permissionsShutro = defineModule({
311
481
  }),
312
482
  ],
313
483
  models: [
484
+ ResSegment,
314
485
  ApprovalPolicy,
315
486
  ApprovalLevel,
316
487
  ApprovalRequest,
488
+ ApprovalEvent,
317
489
  ApprovalVote,
318
490
  ApprovalDelegation,
319
491
  ApprovalLink,
@@ -10,6 +10,9 @@
10
10
  * - `GET /api/v1/approvals/cards?list=inbox|mine|history` the Approvals app's cards (RFC 0025)
11
11
  * - `GET /api/v1/approvals/stats` waiting, overdue, decided this month, the median time
12
12
  * - `POST /api/v1/approvals/bulk` `{ requests, comment? }`: approves each it may
13
+ * - `POST /api/v1/approvals/requests/:id/reassign` `{ users, mode: 'replace' | 'add', reason }` (RFC 0027)
14
+ * - `POST /api/v1/approvals/requests/:id/notify-backups` tells the level's backups it waits (RFC 0027)
15
+ * - `GET /api/v1/approvals/matrix?period=&mode=` the matrix dashboard: cards per policy, direct or delegated
13
16
  */
14
17
  import { type ModuleContext } from '../server/index.js';
15
18
  /** Registers the approval routes. */
@@ -10,9 +10,12 @@
10
10
  * - `GET /api/v1/approvals/cards?list=inbox|mine|history` the Approvals app's cards (RFC 0025)
11
11
  * - `GET /api/v1/approvals/stats` waiting, overdue, decided this month, the median time
12
12
  * - `POST /api/v1/approvals/bulk` `{ requests, comment? }`: approves each it may
13
+ * - `POST /api/v1/approvals/requests/:id/reassign` `{ users, mode: 'replace' | 'add', reason }` (RFC 0027)
14
+ * - `POST /api/v1/approvals/requests/:id/notify-backups` tells the level's backups it waits (RFC 0027)
15
+ * - `GET /api/v1/approvals/matrix?period=&mode=` the matrix dashboard: cards per policy, direct or delegated
13
16
  */
14
17
  import { HttpProblem } from '../server/index.js';
15
- import { approvalCards, approvalStats, approveMany, decide, inbox, requestsOf, withdraw, } from './approvals.js';
18
+ import { approvalCards, approvalMatrix, approvalStats, approveMany, decide, eventsOf, inbox, MATRIX_PERIODS, notifyBackups, reassign, requestsOf, withdraw, } from './approvals.js';
16
19
  function positive(value, what) {
17
20
  const id = Number(value);
18
21
  if (!Number.isSafeInteger(id) || id <= 0)
@@ -33,7 +36,12 @@ export function registerApprovalRoutes({ app }) {
33
36
  app.get('/api/v1/approvals/:model/:id', async (request) => {
34
37
  const { model, id } = request.params;
35
38
  const recordId = positive(id, 'id');
36
- return request.env(async (env) => ({ requests: await requestsOf(env, model, recordId) }), {
39
+ return request.env(async (env) => ({
40
+ requests: await requestsOf(env, model, recordId),
41
+ events: await eventsOf(env, model, recordId),
42
+ // Approvals managers reassign waiting requests (RFC 0027 §2.6).
43
+ manage: env.administrator || env.groups.has('approvals.manager'),
44
+ }), {
37
45
  write: true, // reads through audited system scopes
38
46
  });
39
47
  });
@@ -47,6 +55,32 @@ export function registerApprovalRoutes({ app }) {
47
55
  const text = comment(request);
48
56
  return request.env(async (env) => decide(env, id, decision, text));
49
57
  });
58
+ // RFC 0027 §2.7: the matrix dashboard.
59
+ app.get('/api/v1/approvals/matrix', async (request) => {
60
+ const q = (request.query ?? {});
61
+ const period = (q.period ?? '3m');
62
+ if (!MATRIX_PERIODS.includes(period))
63
+ throw new HttpProblem(400, 'invalid-query', 'Invalid query', `period is one of ${MATRIX_PERIODS.join(', ')}.`);
64
+ const mode = q.mode === 'delegated' ? 'delegated' : 'direct';
65
+ return request.env(async (env) => approvalMatrix(env, period, mode), { write: true });
66
+ });
67
+ // RFC 0027: reassigning a waiting request; telling its backups.
68
+ app.post('/api/v1/approvals/requests/:id/reassign', async (request) => {
69
+ const id = positive(request.params.id, 'id');
70
+ const body = (request.body ?? {});
71
+ const users = Array.isArray(body.users) ? body.users.map((u) => positive(u, 'users')) : [];
72
+ if (body.mode !== undefined && body.mode !== 'replace' && body.mode !== 'add')
73
+ throw new HttpProblem(400, 'invalid-body', 'Invalid body', "mode is 'replace' or 'add'.");
74
+ if (typeof body.reason !== 'string' || body.reason.length > 2000)
75
+ throw new HttpProblem(400, 'invalid-body', 'Invalid body', 'reason is a string (at most 2000).');
76
+ const mode = body.mode === 'add' ? 'add' : 'replace';
77
+ const reason = body.reason;
78
+ return request.env(async (env) => reassign(env, id, users, mode, reason));
79
+ });
80
+ app.post('/api/v1/approvals/requests/:id/notify-backups', async (request) => {
81
+ const id = positive(request.params.id, 'id');
82
+ return request.env(async (env) => ({ notified: await notifyBackups(env, id) }));
83
+ });
50
84
  // The Approvals app (RFC 0025): its lists, its header, approving many at once.
51
85
  app.get('/api/v1/approvals/cards', async (request) => {
52
86
  const list = String(request.query?.list ?? 'inbox');
@@ -10,6 +10,8 @@ import { ApiError, Badge, Button, Textarea } from '../../ui/index.js';
10
10
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
11
11
  import { useId, useState } from 'react';
12
12
  import { useTranslation } from 'react-i18next';
13
+ /** Whole hours between two instants (to now when `to` is null). */
14
+ const hoursBetween = (from, to) => Math.max(0, Math.round(((to === null ? Date.now() : Date.parse(to)) - Date.parse(from)) / 3_600_000));
13
15
  const TONE = {
14
16
  pending: 'warning',
15
17
  approved: 'success',
@@ -24,9 +26,11 @@ const DOT = {
24
26
  sent_back: 'border-danger bg-danger/40',
25
27
  cancelled: 'border-muted-foreground bg-muted',
26
28
  };
27
- function Round({ requests }) {
28
- const { t } = useTranslation('approvalPanel');
29
- return (_jsx("ol", { className: "grid gap-3", children: requests.map((r) => (_jsxs("li", { className: "flex gap-3 text-sm", children: [_jsx("span", { "aria-hidden": "true", className: `mt-1 size-3 shrink-0 rounded-full border-2 ${DOT[r.state]}` }), _jsxs("span", { className: "grid flex-1 gap-0.5", children: [_jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: r.levelName }), _jsx(Badge, { tone: TONE[r.state], children: t(`state.${r.state}`) }), r.required > 1 ? (_jsx("span", { className: "text-xs text-muted-foreground", children: t('votes', { n: r.approvals, of: r.required }) })) : null] }), r.approverRole === '' ? null : (_jsx("span", { className: "text-xs text-muted-foreground", children: r.approverRole })), r.comment === null ? null : _jsxs("span", { className: "text-xs", children: ["\u201C", r.comment, "\u201D"] })] })] }, r.id))) }));
29
+ function Round({ requests, events = [], }) {
30
+ const { t, i18n } = useTranslation('approvalPanel');
31
+ return (_jsx("ol", { className: "grid gap-3", children: requests.map((r) => (_jsxs("li", { className: "flex gap-3 text-sm", children: [_jsx("span", { "aria-hidden": "true", className: `mt-1 size-3 shrink-0 rounded-full border-2 ${DOT[r.state]}` }), _jsxs("span", { className: "grid flex-1 gap-0.5", children: [_jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: r.levelName }), _jsx(Badge, { tone: TONE[r.state], children: t(`state.${r.state}`) }), r.required > 1 ? (_jsx("span", { className: "text-xs text-muted-foreground", children: t('votes', { n: r.approvals, of: r.required }) })) : null] }), r.approverRole === '' ? null : (_jsx("span", { className: "text-xs text-muted-foreground", children: r.approverRole })), r.comment === null ? null : _jsxs("span", { className: "text-xs", children: ["\u201C", r.comment, "\u201D"] }), r.state === 'pending' && r.startedAt !== null ? (_jsx("span", { className: "text-xs text-muted-foreground", children: t('since', { hours: hoursBetween(r.startedAt, null) }) })) : null, r.explanation === null && !events.some((e) => e.request === r.id) ? null : (_jsxs("details", { className: "text-xs text-muted-foreground", children: [_jsx("summary", { className: "cursor-pointer", children: t('how') }), r.explanation === null ? null : _jsx("p", { className: "mt-1", children: r.explanation }), _jsx("ul", { className: "mt-1 grid gap-0.5", "aria-label": t('log'), children: events
32
+ .filter((e) => e.request === r.id)
33
+ .map((e) => (_jsxs("li", { children: [_jsx("time", { dateTime: e.at, children: new Date(e.at).toLocaleString(i18n.language) }), ' ', "\u00B7 ", t(`event.${e.kind}`), e.note === null || e.kind === 'created' ? '' : ` — ${e.note}`] }, e.id))) })] }))] })] }, r.id))) }));
30
34
  }
31
35
  export function ApprovalPanel({ record, model, api, version, reload, }) {
32
36
  const { t } = useTranslation('approvalPanel');
@@ -54,6 +58,32 @@ export function ApprovalPanel({ record, model, api, version, reload, }) {
54
58
  mutationFn: ({ request, decision, }) => api.call('POST', `/api/v1/approvals/requests/${request}/${decision}`, comment.trim() === '' ? {} : { comment: comment.trim() }),
55
59
  onSuccess: done,
56
60
  });
61
+ const [reassigning, setReassigning] = useState(false);
62
+ const [chosen, setChosen] = useState([]);
63
+ const [mode, setMode] = useState('replace');
64
+ const [reason, setReason] = useState('');
65
+ const people = useQuery({
66
+ queryKey: ['approvals', 'people'],
67
+ queryFn: () => api.call('GET', '/api/v1/models/res.users?fields=id,name&limit=200&order=name'),
68
+ enabled: reassigning,
69
+ });
70
+ const reassign = useMutation({
71
+ mutationFn: (request) => api.call('POST', `/api/v1/approvals/requests/${request}/reassign`, {
72
+ users: chosen,
73
+ mode,
74
+ reason: reason.trim(),
75
+ }),
76
+ onSuccess: async () => {
77
+ setReassigning(false);
78
+ setChosen([]);
79
+ setReason('');
80
+ await done();
81
+ },
82
+ });
83
+ const tell = useMutation({
84
+ mutationFn: (request) => api.call('POST', `/api/v1/approvals/requests/${request}/notify-backups`, {}),
85
+ onSuccess: done,
86
+ });
57
87
  const withdraw = useMutation({
58
88
  mutationFn: () => api.call('POST', `/api/v1/approvals/${model}/${recordId}/withdraw`),
59
89
  onSuccess: done,
@@ -69,6 +99,14 @@ export function ApprovalPanel({ record, model, api, version, reload, }) {
69
99
  const mine = new Set((inbox.data?.requests ?? []).map((r) => r.id));
70
100
  const actionable = current.find((r) => mine.has(r.id));
71
101
  const asked = current.some((r) => r.state === 'pending' && r.requestedBy === me.data?.id);
72
- const error = decide.error ?? withdraw.error;
73
- return (_jsxs("section", { "aria-labelledby": `${id}-title`, className: "rounded-lg border bg-surface p-4", children: [_jsxs("h2", { id: `${id}-title`, className: "mb-3 flex items-center gap-2 text-sm font-semibold", children: [t('title'), last > 1 ? _jsx(Badge, { tone: "neutral", children: t('round', { n: last }) }) : null] }), current.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('withdrawn') })) : (_jsx(Round, { requests: current })), earlier.length === 0 ? null : (_jsxs("details", { className: "mt-3 text-sm", children: [_jsx("summary", { className: "cursor-pointer text-muted-foreground", children: t('earlier', { count: earlier.length }) }), _jsx("div", { className: "mt-2", children: _jsx(Round, { requests: earlier }) })] })), actionable === undefined ? null : (_jsxs("div", { className: "mt-4 grid gap-2", children: [_jsx("label", { htmlFor: `${id}-comment`, className: "text-sm font-medium", children: t('yourDecision', { level: actionable.levelName }) }), _jsx(Textarea, { id: `${id}-comment`, placeholder: t('commentHint'), value: comment, onChange: (e) => setComment(e.target.value) }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "primary", disabled: decide.isPending, onClick: () => decide.mutate({ request: actionable.id, decision: 'approve' }), children: t('approve') }), _jsx(Button, { disabled: decide.isPending || comment.trim() === '', onClick: () => decide.mutate({ request: actionable.id, decision: 'send-back' }), children: t('sendBack') }), _jsx(Button, { variant: "danger", disabled: decide.isPending || comment.trim() === '', onClick: () => decide.mutate({ request: actionable.id, decision: 'reject' }), children: t('reject') })] })] })), asked ? (_jsx("div", { className: "mt-3", children: _jsx(Button, { variant: "ghost", disabled: withdraw.isPending, onClick: () => withdraw.mutate(), children: t('withdraw') }) })) : null, error === null ? null : (_jsx("p", { role: "alert", className: "mt-2 text-sm text-danger", children: error instanceof ApiError ? error.message : String(error) }))] }));
102
+ const error = decide.error ?? withdraw.error ?? reassign.error ?? tell.error;
103
+ const manage = requests.data?.manage === true;
104
+ // The waiting request whose turn it is (the first, when levels decide side by side).
105
+ const waiting = current.find((r) => r.state === 'pending' && r.startedAt !== null);
106
+ const mayTell = waiting !== undefined &&
107
+ waiting.backups.length > 0 &&
108
+ (manage || waiting.approvers.includes(me.data?.id ?? -1));
109
+ return (_jsxs("section", { "aria-labelledby": `${id}-title`, className: "rounded-lg border bg-surface p-4", children: [_jsxs("h2", { id: `${id}-title`, className: "mb-3 flex items-center gap-2 text-sm font-semibold", children: [t('title'), last > 1 ? _jsx(Badge, { tone: "neutral", children: t('round', { n: last }) }) : null] }), current.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('withdrawn') })) : (_jsx(Round, { requests: current, events: requests.data?.events ?? [] })), earlier.length === 0 ? null : (_jsxs("details", { className: "mt-3 text-sm", children: [_jsx("summary", { className: "cursor-pointer text-muted-foreground", children: t('earlier', { count: earlier.length }) }), _jsx("div", { className: "mt-2", children: _jsx(Round, { requests: earlier }) })] })), actionable === undefined ? null : (_jsxs("div", { className: "mt-4 grid gap-2", children: [_jsx("label", { htmlFor: `${id}-comment`, className: "text-sm font-medium", children: t('yourDecision', { level: actionable.levelName }) }), _jsx(Textarea, { id: `${id}-comment`, placeholder: t('commentHint'), value: comment, onChange: (e) => setComment(e.target.value) }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "primary", disabled: decide.isPending, onClick: () => decide.mutate({ request: actionable.id, decision: 'approve' }), children: t('approve') }), _jsx(Button, { disabled: decide.isPending || comment.trim() === '', onClick: () => decide.mutate({ request: actionable.id, decision: 'send-back' }), children: t('sendBack') }), _jsx(Button, { variant: "danger", disabled: decide.isPending || comment.trim() === '', onClick: () => decide.mutate({ request: actionable.id, decision: 'reject' }), children: t('reject') })] })] })), waiting !== undefined && (manage || mayTell) ? (_jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [manage ? (_jsx(Button, { variant: "ghost", onClick: () => setReassigning((v) => !v), children: t('reassign') })) : null, mayTell ? (_jsx(Button, { variant: "ghost", disabled: tell.isPending, onClick: () => tell.mutate(waiting.id), children: t('tellBackups') })) : null] })) : null, reassigning && waiting !== undefined ? (_jsxs("fieldset", { className: "mt-3 grid gap-2 rounded-md border p-3", children: [_jsx("legend", { className: "px-1 text-sm font-medium", children: t('reassignLevel', { level: waiting.levelName }) }), _jsx("label", { htmlFor: `${id}-people`, className: "text-sm", children: t('newApprovers') }), _jsx("select", { id: `${id}-people`, multiple: true, className: "min-h-24 rounded-md border bg-surface p-1 text-sm", value: chosen.map(String), onChange: (e) => setChosen([...e.target.selectedOptions].map((o) => Number(o.value))), children: (people.data?.items ?? [])
110
+ .filter((u) => u.id !== waiting.requestedBy)
111
+ .map((u) => (_jsx("option", { value: u.id, children: u.name }, u.id))) }), _jsx("div", { className: "flex flex-wrap gap-3 text-sm", children: ['replace', 'add'].map((m) => (_jsxs("label", { className: "flex items-center gap-1", children: [_jsx("input", { type: "radio", name: `${id}-mode`, checked: mode === m, onChange: () => setMode(m) }), t(`mode.${m}`)] }, m))) }), _jsx("label", { htmlFor: `${id}-reason`, className: "text-sm", children: t('reason') }), _jsx(Textarea, { id: `${id}-reason`, value: reason, onChange: (e) => setReason(e.target.value) }), _jsx("div", { children: _jsx(Button, { variant: "primary", disabled: reassign.isPending || chosen.length === 0 || reason.trim() === '', onClick: () => reassign.mutate(waiting.id), children: t('reassignConfirm') }) })] })) : null, asked ? (_jsx("div", { className: "mt-3", children: _jsx(Button, { variant: "ghost", disabled: withdraw.isPending, onClick: () => withdraw.mutate(), children: t('withdraw') }) })) : null, error === null ? null : (_jsx("p", { role: "alert", className: "mt-2 text-sm text-danger", children: error instanceof ApiError ? error.message : String(error) }))] }));
74
112
  }
@@ -12,6 +12,32 @@ export const messages = {
12
12
  sendBack: 'Send back for changes',
13
13
  reject: 'Reject',
14
14
  withdraw: 'Withdraw my request',
15
+ how: 'How the approvers were found',
16
+ reassign: 'Reassign',
17
+ reassignLevel: 'Reassign {{level}}',
18
+ newApprovers: 'Who decides instead',
19
+ mode: { replace: 'Replace the approvers', add: 'Add to them' },
20
+ reason: 'Why (required)',
21
+ reassignConfirm: 'Reassign',
22
+ tellBackups: 'Tell the backups',
23
+ log: 'Log',
24
+ since: 'Its turn for {{hours}} h',
25
+ event: {
26
+ created: 'Created',
27
+ started: 'Its turn',
28
+ voted: 'Voted',
29
+ approved: 'Approved',
30
+ rejected: 'Rejected',
31
+ sent_back: 'Sent back',
32
+ cancelled: 'Cancelled',
33
+ skipped: 'Skipped',
34
+ reminded: 'Reminded',
35
+ escalated: 'Escalated',
36
+ auto: 'Approved already',
37
+ reassigned: 'Reassigned',
38
+ synced: 'Synced',
39
+ notified: 'Backups told',
40
+ },
15
41
  state: {
16
42
  pending: 'Waiting',
17
43
  approved: 'Approved',
@@ -32,6 +58,32 @@ export const messages = {
32
58
  sendBack: 'সংশোধনের জন্য ফেরত',
33
59
  reject: 'প্রত্যাখ্যান',
34
60
  withdraw: 'আমার অনুরোধ প্রত্যাহার',
61
+ how: 'অনুমোদনকারী কীভাবে নির্ধারিত হলেন',
62
+ reassign: 'অন্যকে দিন',
63
+ reassignLevel: '{{level}} অন্যকে দিন',
64
+ newApprovers: 'এর বদলে কে সিদ্ধান্ত নেবেন',
65
+ mode: { replace: 'অনুমোদনকারী বদলে দিন', add: 'তাঁদের সঙ্গে যোগ করুন' },
66
+ reason: 'কারণ (আবশ্যক)',
67
+ reassignConfirm: 'অন্যকে দিন',
68
+ tellBackups: 'বিকল্পদের জানান',
69
+ log: 'লগ',
70
+ since: '{{hours}} ঘণ্টা ধরে এর পালা',
71
+ event: {
72
+ created: 'তৈরি',
73
+ started: 'এর পালা',
74
+ voted: 'ভোট',
75
+ approved: 'অনুমোদিত',
76
+ rejected: 'প্রত্যাখ্যাত',
77
+ sent_back: 'ফেরত পাঠানো',
78
+ cancelled: 'বাতিল',
79
+ skipped: 'বাদ দেওয়া',
80
+ reminded: 'মনে করানো',
81
+ escalated: 'ঊর্ধ্বতনে পাঠানো',
82
+ auto: 'আগেই অনুমোদিত',
83
+ reassigned: 'অন্যকে দেওয়া',
84
+ synced: 'মিলিয়ে নেওয়া',
85
+ notified: 'বিকল্পদের জানানো',
86
+ },
35
87
  state: {
36
88
  pending: 'অপেক্ষায়',
37
89
  approved: 'অনুমোদিত',
@@ -1,4 +1,4 @@
1
- // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: d1428de5985e3529
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: ebc3fbac95760b14
2
2
  // Bindings of module `portal`: import the definition API from here to get types scoped to what it can see.
3
3
  export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
4
4
  import { definitions } from '../../server/orm.js';
@@ -1,4 +1,4 @@
1
- // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: d1428de5985e3529
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: ebc3fbac95760b14
2
2
  // Schema view of module `portal` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
3
3
  import type { F } from '../../server/orm.js';
4
4
 
@@ -508,6 +508,7 @@ export interface ResPartner {
508
508
  readonly parent: F.Many2one<'res.partner'>;
509
509
  readonly contacts: F.One2many<'res.partner', { readonly required: true }>;
510
510
  readonly email: F.Char;
511
+ readonly lang: F.Selection<'en' | 'bn'>;
511
512
  readonly phone: F.Char;
512
513
  readonly mobile: F.Char;
513
514
  readonly website: F.Char;
@@ -12,6 +12,9 @@ export interface PortalDocument {
12
12
  readonly model: ApiModel;
13
13
  /** The lines' model, when the document has lines. */
14
14
  readonly lineModel: ApiModel | null;
15
+ /** The optional items' model and the field that points back at the document, when it has options. */
16
+ readonly optionModel: ApiModel | null;
17
+ readonly optionParent: string | null;
15
18
  }
16
19
  /** The documents of the loaded modules, by key. @throws {ServerConfigError} for a declaration that cannot work */
17
20
  export declare function portalDocuments(registry: KoshRegistry): ReadonlyMap<string, PortalDocument>;
@@ -50,12 +50,32 @@ export function portalDocuments(registry) {
50
50
  if (!lineModel.byKey.has(field))
51
51
  fail(key, `${lines?.target} has no field '${field}'.`, 'Declare existing line fields.');
52
52
  }
53
+ let optionModel = null;
54
+ let optionParent = null;
55
+ if (spec.options !== undefined) {
56
+ const options = m.byKey.get(spec.options.field);
57
+ if (options?.kind !== 'one2many' || options.target === null)
58
+ fail(key, `'${spec.options.field}' is not a one2many.`, 'Name the options field.');
59
+ optionModel = models.get(options?.target ?? '') ?? null;
60
+ if (optionModel === null)
61
+ fail(key, `the options' model ${options?.target} is not served.`, 'Use a served model.');
62
+ const om = optionModel;
63
+ for (const field of spec.options.fields)
64
+ if (!om.byKey.has(field))
65
+ fail(key, `${om.name} has no field '${field}'.`, 'Declare existing option fields.');
66
+ if (!om.transitions.includes(spec.options.add))
67
+ fail(key, `${om.name} has no transition '${spec.options.add}'.`, 'Name the transition that adds an option.');
68
+ optionParent =
69
+ om.fields.find((f) => f.kind === 'many2one' && f.target === spec.model)?.key ?? null;
70
+ if (optionParent === null)
71
+ fail(key, `${om.name} has no many2one to ${spec.model}.`, 'Options point at their document.');
72
+ }
53
73
  for (const action of spec.actions ?? [])
54
74
  if (!m.transitions.includes(action.transition))
55
75
  fail(key, `${spec.model} has no transition '${action.transition}' (action '${action.key}').`, `Transitions: ${m.transitions.join(', ') || 'none'}.`);
56
76
  if (spec.pdf !== undefined && !templates.has(spec.pdf))
57
77
  fail(key, `no document template '${spec.pdf}'.`, 'Name a template a module ships (module.key).');
58
- out.set(key, { spec, model: m, lineModel });
78
+ out.set(key, { spec, model: m, lineModel, optionModel, optionParent });
59
79
  }
60
80
  return out;
61
81
  }
@@ -0,0 +1,2 @@
1
+ /** Bangla for the module's labels, keyed by their English text (RFC 0023 §3.1; `pnpm i18n:extract`). */
2
+ export declare const bn: Readonly<Record<string, string>>;