shutro-erp 0.3.4 → 0.3.6

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 (618) hide show
  1. package/dist/account_asset/.kosh/bindings.d.ts +22 -0
  2. package/dist/account_asset/.kosh/bindings.js +15 -0
  3. package/dist/account_asset/.kosh/schema.d.ts +1755 -0
  4. package/dist/account_asset/board.d.ts +61 -0
  5. package/dist/account_asset/board.js +86 -0
  6. package/dist/account_asset/books.d.ts +42 -0
  7. package/dist/account_asset/books.js +398 -0
  8. package/dist/account_asset/demo.d.ts +7 -0
  9. package/dist/account_asset/demo.js +72 -0
  10. package/dist/account_asset/index.d.ts +9 -0
  11. package/dist/account_asset/index.js +335 -0
  12. package/dist/account_asset/module.d.ts +7622 -0
  13. package/dist/account_asset/module.js +230 -0
  14. package/dist/account_budget/.kosh/bindings.d.ts +22 -0
  15. package/dist/account_budget/.kosh/bindings.js +15 -0
  16. package/dist/account_budget/.kosh/schema.d.ts +1767 -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/index.d.ts +4 -0
  22. package/dist/account_budget/index.js +223 -0
  23. package/dist/account_budget/module.d.ts +13863 -0
  24. package/dist/account_budget/module.js +167 -0
  25. package/dist/account_portal/.kosh/bindings.js +1 -1
  26. package/dist/account_portal/.kosh/schema.d.ts +176 -6
  27. package/dist/account_portal/module.d.ts +505 -6
  28. package/dist/accounting/.kosh/bindings.js +1 -1
  29. package/dist/accounting/.kosh/schema.d.ts +176 -6
  30. package/dist/accounting/index.d.ts +1 -1
  31. package/dist/accounting/index.js +344 -4
  32. package/dist/accounting/module.d.ts +513 -5
  33. package/dist/accounting/module.js +55 -3
  34. package/dist/approvals/.kosh/bindings.d.ts +22 -0
  35. package/dist/approvals/.kosh/bindings.js +15 -0
  36. package/dist/approvals/.kosh/schema.d.ts +611 -0
  37. package/dist/approvals/index.d.ts +18 -0
  38. package/dist/approvals/index.js +302 -0
  39. package/dist/approvals/mails.d.ts +25 -0
  40. package/dist/approvals/mails.js +82 -0
  41. package/dist/approvals/module.d.ts +1 -0
  42. package/dist/approvals/module.js +12 -0
  43. package/dist/approvals/web/History.d.ts +4 -0
  44. package/dist/approvals/web/History.js +13 -0
  45. package/dist/approvals/web/Inbox.d.ts +9 -0
  46. package/dist/approvals/web/Inbox.js +114 -0
  47. package/dist/approvals/web/Mine.d.ts +7 -0
  48. package/dist/approvals/web/Mine.js +27 -0
  49. package/dist/approvals/web/index.d.ts +13 -0
  50. package/dist/approvals/web/index.js +22 -0
  51. package/dist/approvals/web/messages.d.ts +2 -0
  52. package/dist/approvals/web/messages.js +119 -0
  53. package/dist/approvals/web/shared.d.ts +59 -0
  54. package/dist/approvals/web/shared.js +46 -0
  55. package/dist/attendance/.kosh/bindings.d.ts +22 -0
  56. package/dist/attendance/.kosh/bindings.js +15 -0
  57. package/dist/attendance/.kosh/schema.d.ts +1048 -0
  58. package/dist/attendance/days.d.ts +96 -0
  59. package/dist/attendance/days.js +430 -0
  60. package/dist/attendance/demo.d.ts +7 -0
  61. package/dist/attendance/demo.js +58 -0
  62. package/dist/attendance/index.d.ts +4 -0
  63. package/dist/attendance/index.js +513 -0
  64. package/dist/attendance/module.d.ts +3969 -0
  65. package/dist/attendance/module.js +412 -0
  66. package/dist/attendance/routes.d.ts +16 -0
  67. package/dist/attendance/routes.js +266 -0
  68. package/dist/attendance/web/Check.d.ts +8 -0
  69. package/dist/attendance/web/Check.js +40 -0
  70. package/dist/attendance/web/Kiosk.d.ts +8 -0
  71. package/dist/attendance/web/Kiosk.js +62 -0
  72. package/dist/attendance/web/PinButton.d.ts +7 -0
  73. package/dist/attendance/web/PinButton.js +29 -0
  74. package/dist/attendance/web/index.d.ts +13 -0
  75. package/dist/attendance/web/index.js +22 -0
  76. package/dist/attendance/web/messages.d.ts +2 -0
  77. package/dist/attendance/web/messages.js +61 -0
  78. package/dist/calendar/.kosh/bindings.d.ts +22 -0
  79. package/dist/calendar/.kosh/bindings.js +15 -0
  80. package/dist/calendar/.kosh/schema.d.ts +795 -0
  81. package/dist/calendar/demo.d.ts +6 -0
  82. package/dist/calendar/demo.js +24 -0
  83. package/dist/calendar/index.d.ts +3 -0
  84. package/dist/calendar/index.js +291 -0
  85. package/dist/calendar/module.d.ts +1705 -0
  86. package/dist/calendar/module.js +180 -0
  87. package/dist/cli/init.js +80 -0
  88. package/dist/cli/templates.d.ts +1 -1
  89. package/dist/cli/templates.js +226 -0
  90. package/dist/contacts/.kosh/bindings.js +1 -1
  91. package/dist/contacts/.kosh/schema.d.ts +78 -3
  92. package/dist/contacts/demo.js +30 -1
  93. package/dist/contacts/index.d.ts +1 -1
  94. package/dist/contacts/index.js +155 -9
  95. package/dist/contacts/module.d.ts +338 -2
  96. package/dist/contacts/module.js +93 -5
  97. package/dist/crm/.kosh/bindings.d.ts +22 -0
  98. package/dist/crm/.kosh/bindings.js +15 -0
  99. package/dist/crm/.kosh/schema.d.ts +800 -0
  100. package/dist/crm/demo.d.ts +6 -0
  101. package/dist/crm/demo.js +45 -0
  102. package/dist/crm/index.d.ts +3 -0
  103. package/dist/crm/index.js +361 -0
  104. package/dist/crm/module.d.ts +1845 -0
  105. package/dist/crm/module.js +208 -0
  106. package/dist/documents/.kosh/bindings.d.ts +22 -0
  107. package/dist/documents/.kosh/bindings.js +15 -0
  108. package/dist/documents/.kosh/schema.d.ts +862 -0
  109. package/dist/documents/demo.d.ts +6 -0
  110. package/dist/documents/demo.js +22 -0
  111. package/dist/documents/documents.d.ts +98 -0
  112. package/dist/documents/documents.js +318 -0
  113. package/dist/documents/index.d.ts +30 -0
  114. package/dist/documents/index.js +442 -0
  115. package/dist/documents/module.d.ts +2781 -0
  116. package/dist/documents/module.js +214 -0
  117. package/dist/documents/web/Versions.d.ts +7 -0
  118. package/dist/documents/web/Versions.js +45 -0
  119. package/dist/documents/web/index.d.ts +11 -0
  120. package/dist/documents/web/index.js +16 -0
  121. package/dist/documents/web/messages.d.ts +2 -0
  122. package/dist/documents/web/messages.js +21 -0
  123. package/dist/event/.kosh/bindings.d.ts +22 -0
  124. package/dist/event/.kosh/bindings.js +15 -0
  125. package/dist/event/.kosh/schema.d.ts +843 -0
  126. package/dist/event/demo.d.ts +6 -0
  127. package/dist/event/demo.js +65 -0
  128. package/dist/event/index.d.ts +14 -0
  129. package/dist/event/index.js +333 -0
  130. package/dist/event/mails.d.ts +15 -0
  131. package/dist/event/mails.js +112 -0
  132. package/dist/event/module.d.ts +2800 -0
  133. package/dist/event/module.js +323 -0
  134. package/dist/event/web/CheckInBox.d.ts +9 -0
  135. package/dist/event/web/CheckInBox.js +87 -0
  136. package/dist/event/web/index.d.ts +11 -0
  137. package/dist/event/web/index.js +16 -0
  138. package/dist/event/web/messages.d.ts +2 -0
  139. package/dist/event/web/messages.js +23 -0
  140. package/dist/event_account/.kosh/bindings.d.ts +22 -0
  141. package/dist/event_account/.kosh/bindings.js +15 -0
  142. package/dist/event_account/.kosh/schema.d.ts +1758 -0
  143. package/dist/event_account/index.d.ts +3 -0
  144. package/dist/event_account/index.js +28 -0
  145. package/dist/event_account/module.d.ts +7114 -0
  146. package/dist/event_account/module.js +90 -0
  147. package/dist/expense/.kosh/bindings.d.ts +22 -0
  148. package/dist/expense/.kosh/bindings.js +15 -0
  149. package/dist/expense/.kosh/schema.d.ts +1299 -0
  150. package/dist/expense/demo.d.ts +7 -0
  151. package/dist/expense/demo.js +69 -0
  152. package/dist/expense/index.d.ts +3 -0
  153. package/dist/expense/index.js +495 -0
  154. package/dist/expense/module.d.ts +4747 -0
  155. package/dist/expense/module.js +647 -0
  156. package/dist/expense_account/.kosh/bindings.d.ts +22 -0
  157. package/dist/expense_account/.kosh/bindings.js +15 -0
  158. package/dist/expense_account/.kosh/schema.d.ts +1936 -0
  159. package/dist/expense_account/index.d.ts +3 -0
  160. package/dist/expense_account/index.js +74 -0
  161. package/dist/expense_account/module.d.ts +9153 -0
  162. package/dist/expense_account/module.js +318 -0
  163. package/dist/helpdesk/.kosh/bindings.d.ts +22 -0
  164. package/dist/helpdesk/.kosh/bindings.js +15 -0
  165. package/dist/helpdesk/.kosh/schema.d.ts +885 -0
  166. package/dist/helpdesk/demo.d.ts +7 -0
  167. package/dist/helpdesk/demo.js +65 -0
  168. package/dist/helpdesk/hours.d.ts +17 -0
  169. package/dist/helpdesk/hours.js +94 -0
  170. package/dist/helpdesk/index.d.ts +10 -0
  171. package/dist/helpdesk/index.js +461 -0
  172. package/dist/helpdesk/module.d.ts +2940 -0
  173. package/dist/helpdesk/module.js +326 -0
  174. package/dist/helpdesk/routes.d.ts +12 -0
  175. package/dist/helpdesk/routes.js +132 -0
  176. package/dist/helpdesk/sla.d.ts +37 -0
  177. package/dist/helpdesk/sla.js +226 -0
  178. package/dist/hr/.kosh/bindings.d.ts +22 -0
  179. package/dist/hr/.kosh/bindings.js +15 -0
  180. package/dist/hr/.kosh/schema.d.ts +878 -0
  181. package/dist/hr/demo.d.ts +6 -0
  182. package/dist/hr/demo.js +60 -0
  183. package/dist/hr/index.d.ts +3 -0
  184. package/dist/hr/index.js +483 -0
  185. package/dist/hr/module.d.ts +2611 -0
  186. package/dist/hr/module.js +350 -0
  187. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  188. package/dist/l10n_bd/.kosh/schema.d.ts +176 -6
  189. package/dist/l10n_bd/module.d.ts +505 -6
  190. package/dist/l10n_bd_payroll/.kosh/bindings.d.ts +22 -0
  191. package/dist/l10n_bd_payroll/.kosh/bindings.js +15 -0
  192. package/dist/l10n_bd_payroll/.kosh/schema.d.ts +2312 -0
  193. package/dist/l10n_bd_payroll/data.d.ts +47 -0
  194. package/dist/l10n_bd_payroll/data.js +347 -0
  195. package/dist/l10n_bd_payroll/index.d.ts +4 -0
  196. package/dist/l10n_bd_payroll/index.js +24 -0
  197. package/dist/l10n_bd_payroll/module.d.ts +12932 -0
  198. package/dist/l10n_bd_payroll/module.js +86 -0
  199. package/dist/l10n_in/.kosh/bindings.js +1 -1
  200. package/dist/l10n_in/.kosh/schema.d.ts +176 -6
  201. package/dist/l10n_in/module.d.ts +527 -7
  202. package/dist/mail/inbound/handle.js +6 -1
  203. package/dist/maintenance/.kosh/bindings.d.ts +22 -0
  204. package/dist/maintenance/.kosh/bindings.js +15 -0
  205. package/dist/maintenance/.kosh/schema.d.ts +911 -0
  206. package/dist/maintenance/demo.d.ts +6 -0
  207. package/dist/maintenance/demo.js +60 -0
  208. package/dist/maintenance/index.d.ts +3 -0
  209. package/dist/maintenance/index.js +422 -0
  210. package/dist/maintenance/module.d.ts +3545 -0
  211. package/dist/maintenance/module.js +446 -0
  212. package/dist/maintenance_stock/.kosh/bindings.d.ts +22 -0
  213. package/dist/maintenance_stock/.kosh/bindings.js +15 -0
  214. package/dist/maintenance_stock/.kosh/schema.d.ts +1446 -0
  215. package/dist/maintenance_stock/index.d.ts +9 -0
  216. package/dist/maintenance_stock/index.js +52 -0
  217. package/dist/maintenance_stock/module.d.ts +7675 -0
  218. package/dist/maintenance_stock/module.js +192 -0
  219. package/dist/modules-sdk/index.d.ts +1 -1
  220. package/dist/modules-sdk/manifest.d.ts +109 -2
  221. package/dist/modules-sdk/manifest.js +35 -1
  222. package/dist/mrp/.kosh/bindings.d.ts +22 -0
  223. package/dist/mrp/.kosh/bindings.js +15 -0
  224. package/dist/mrp/.kosh/schema.d.ts +1453 -0
  225. package/dist/mrp/demo.d.ts +7 -0
  226. package/dist/mrp/demo.js +42 -0
  227. package/dist/mrp/index.d.ts +3 -0
  228. package/dist/mrp/index.js +393 -0
  229. package/dist/mrp/module.d.ts +9335 -0
  230. package/dist/mrp/module.js +757 -0
  231. package/dist/mrp_account/.kosh/bindings.d.ts +22 -0
  232. package/dist/mrp_account/.kosh/bindings.js +15 -0
  233. package/dist/mrp_account/.kosh/schema.d.ts +2299 -0
  234. package/dist/mrp_account/index.d.ts +3 -0
  235. package/dist/mrp_account/index.js +22 -0
  236. package/dist/mrp_account/module.d.ts +23151 -0
  237. package/dist/mrp_account/module.js +123 -0
  238. package/dist/payment/.kosh/bindings.js +1 -1
  239. package/dist/payment/.kosh/schema.d.ts +178 -7
  240. package/dist/payment/index.d.ts +41 -0
  241. package/dist/payment/index.js +131 -74
  242. package/dist/payment/module.d.ts +569 -12
  243. package/dist/payment/module.js +19 -3
  244. package/dist/payment/settle.js +10 -1
  245. package/dist/payroll/.kosh/bindings.d.ts +22 -0
  246. package/dist/payroll/.kosh/bindings.js +15 -0
  247. package/dist/payroll/.kosh/schema.d.ts +1390 -0
  248. package/dist/payroll/compute.d.ts +35 -0
  249. package/dist/payroll/compute.js +398 -0
  250. package/dist/payroll/demo.d.ts +55 -0
  251. package/dist/payroll/demo.js +121 -0
  252. package/dist/payroll/formula.d.ts +62 -0
  253. package/dist/payroll/formula.js +320 -0
  254. package/dist/payroll/index.d.ts +230 -0
  255. package/dist/payroll/index.js +713 -0
  256. package/dist/payroll/module.d.ts +9390 -0
  257. package/dist/payroll/module.js +606 -0
  258. package/dist/payroll/routes.d.ts +10 -0
  259. package/dist/payroll/routes.js +112 -0
  260. package/dist/payroll_account/.kosh/bindings.d.ts +22 -0
  261. package/dist/payroll_account/.kosh/bindings.js +15 -0
  262. package/dist/payroll_account/.kosh/schema.d.ts +2308 -0
  263. package/dist/payroll_account/index.d.ts +3 -0
  264. package/dist/payroll_account/index.js +40 -0
  265. package/dist/payroll_account/module.d.ts +12396 -0
  266. package/dist/payroll_account/module.js +256 -0
  267. package/dist/permissions/approvals.d.ts +131 -9
  268. package/dist/permissions/approvals.js +662 -57
  269. package/dist/permissions/index.d.ts +1 -1
  270. package/dist/permissions/index.js +2 -2
  271. package/dist/permissions/models.js +191 -12
  272. package/dist/permissions/routes.d.ts +6 -1
  273. package/dist/permissions/routes.js +37 -4
  274. package/dist/permissions/web/ApprovalPanel.d.ts +5 -4
  275. package/dist/permissions/web/ApprovalPanel.js +39 -13
  276. package/dist/permissions/web/index.d.ts +6 -4
  277. package/dist/permissions/web/index.js +13 -4
  278. package/dist/permissions/web/messages.d.ts +2 -0
  279. package/dist/permissions/web/messages.js +43 -0
  280. package/dist/portal/.kosh/bindings.js +1 -1
  281. package/dist/portal/.kosh/schema.d.ts +78 -3
  282. package/dist/portal/index.js +1 -1
  283. package/dist/portal/module.d.ts +166 -2
  284. package/dist/pos/.kosh/bindings.d.ts +22 -0
  285. package/dist/pos/.kosh/bindings.js +15 -0
  286. package/dist/pos/.kosh/schema.d.ts +1809 -0
  287. package/dist/pos/accounting.d.ts +10 -0
  288. package/dist/pos/accounting.js +149 -0
  289. package/dist/pos/demo.d.ts +6 -0
  290. package/dist/pos/demo.js +90 -0
  291. package/dist/pos/index.d.ts +9 -0
  292. package/dist/pos/index.js +353 -0
  293. package/dist/pos/module.d.ts +9783 -0
  294. package/dist/pos/module.js +423 -0
  295. package/dist/pos/routes.d.ts +39 -0
  296. package/dist/pos/routes.js +368 -0
  297. package/dist/pos/web/Register.d.ts +9 -0
  298. package/dist/pos/web/Register.js +252 -0
  299. package/dist/pos/web/index.d.ts +11 -0
  300. package/dist/pos/web/index.js +16 -0
  301. package/dist/pos/web/messages.d.ts +2 -0
  302. package/dist/pos/web/messages.js +87 -0
  303. package/dist/pos_stock/.kosh/bindings.d.ts +22 -0
  304. package/dist/pos_stock/.kosh/bindings.js +15 -0
  305. package/dist/pos_stock/.kosh/schema.d.ts +2208 -0
  306. package/dist/pos_stock/index.d.ts +9 -0
  307. package/dist/pos_stock/index.js +35 -0
  308. package/dist/pos_stock/module.d.ts +12803 -0
  309. package/dist/pos_stock/module.js +115 -0
  310. package/dist/product/.kosh/bindings.js +1 -1
  311. package/dist/product/.kosh/schema.d.ts +52 -1
  312. package/dist/product/index.js +94 -4
  313. package/dist/product/module.d.ts +45 -1
  314. package/dist/product/module.js +11 -3
  315. package/dist/project/.kosh/bindings.js +1 -1
  316. package/dist/project/.kosh/schema.d.ts +725 -3
  317. package/dist/project/index.d.ts +3 -1
  318. package/dist/project/index.js +1045 -18
  319. package/dist/project/module.d.ts +10168 -458
  320. package/dist/project/module.js +1578 -14
  321. package/dist/project/routes.d.ts +44 -0
  322. package/dist/project/routes.js +314 -0
  323. package/dist/project/schedule.d.ts +72 -0
  324. package/dist/project/schedule.js +192 -0
  325. package/dist/project/web/Refresh.d.ts +8 -0
  326. package/dist/project/web/Refresh.js +29 -0
  327. package/dist/project/web/Schedule.d.ts +9 -0
  328. package/dist/project/web/Schedule.js +93 -0
  329. package/dist/project/web/Structure.d.ts +8 -0
  330. package/dist/project/web/Structure.js +86 -0
  331. package/dist/project/web/Workload.d.ts +8 -0
  332. package/dist/project/web/Workload.js +43 -0
  333. package/dist/project/web/index.d.ts +14 -0
  334. package/dist/project/web/index.js +25 -0
  335. package/dist/project/web/messages.d.ts +2 -0
  336. package/dist/project/web/messages.js +85 -0
  337. package/dist/project_account/.kosh/bindings.d.ts +22 -0
  338. package/dist/project_account/.kosh/bindings.js +15 -0
  339. package/dist/project_account/.kosh/schema.d.ts +2241 -0
  340. package/dist/project_account/index.d.ts +3 -0
  341. package/dist/project_account/index.js +27 -0
  342. package/dist/project_account/module.d.ts +11539 -0
  343. package/dist/project_account/module.js +76 -0
  344. package/dist/project_portal/.kosh/bindings.js +1 -1
  345. package/dist/project_portal/.kosh/schema.d.ts +725 -3
  346. package/dist/project_portal/module.d.ts +5137 -119
  347. package/dist/project_purchase/.kosh/bindings.d.ts +22 -0
  348. package/dist/project_purchase/.kosh/bindings.js +15 -0
  349. package/dist/project_purchase/.kosh/schema.d.ts +2320 -0
  350. package/dist/project_purchase/index.d.ts +9 -0
  351. package/dist/project_purchase/index.js +22 -0
  352. package/dist/project_purchase/module.d.ts +25147 -0
  353. package/dist/project_purchase/module.js +56 -0
  354. package/dist/purchase/.kosh/bindings.js +1 -1
  355. package/dist/purchase/.kosh/schema.d.ts +150 -6
  356. package/dist/purchase/index.js +163 -3
  357. package/dist/purchase/module.d.ts +228 -5
  358. package/dist/purchase/module.js +30 -3
  359. package/dist/purchase_account/.kosh/bindings.js +1 -1
  360. package/dist/purchase_account/.kosh/schema.d.ts +180 -8
  361. package/dist/purchase_account/index.js +27 -1
  362. package/dist/purchase_account/module.d.ts +591 -30
  363. package/dist/purchase_account/module.js +13 -2
  364. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  365. package/dist/purchase_portal/.kosh/schema.d.ts +150 -6
  366. package/dist/purchase_portal/module.d.ts +372 -6
  367. package/dist/purchase_stock/.kosh/bindings.d.ts +22 -0
  368. package/dist/purchase_stock/.kosh/bindings.js +15 -0
  369. package/dist/purchase_stock/.kosh/schema.d.ts +1479 -0
  370. package/dist/purchase_stock/index.d.ts +7 -0
  371. package/dist/purchase_stock/index.js +98 -0
  372. package/dist/purchase_stock/module.d.ts +7535 -0
  373. package/dist/purchase_stock/module.js +380 -0
  374. package/dist/purchase_stock_account/.kosh/bindings.d.ts +22 -0
  375. package/dist/purchase_stock_account/.kosh/bindings.js +15 -0
  376. package/dist/purchase_stock_account/.kosh/schema.d.ts +2155 -0
  377. package/dist/purchase_stock_account/index.d.ts +3 -0
  378. package/dist/purchase_stock_account/index.js +24 -0
  379. package/dist/purchase_stock_account/module.d.ts +38015 -0
  380. package/dist/purchase_stock_account/module.js +168 -0
  381. package/dist/recruitment/.kosh/bindings.d.ts +22 -0
  382. package/dist/recruitment/.kosh/bindings.js +15 -0
  383. package/dist/recruitment/.kosh/schema.d.ts +991 -0
  384. package/dist/recruitment/demo.d.ts +6 -0
  385. package/dist/recruitment/demo.js +45 -0
  386. package/dist/recruitment/index.d.ts +3 -0
  387. package/dist/recruitment/index.js +385 -0
  388. package/dist/recruitment/module.d.ts +3249 -0
  389. package/dist/recruitment/module.js +194 -0
  390. package/dist/recruitment/web/HireButton.d.ts +9 -0
  391. package/dist/recruitment/web/HireButton.js +32 -0
  392. package/dist/recruitment/web/index.d.ts +11 -0
  393. package/dist/recruitment/web/index.js +16 -0
  394. package/dist/recruitment/web/messages.d.ts +2 -0
  395. package/dist/recruitment/web/messages.js +19 -0
  396. package/dist/reporting/documents.d.ts +5 -0
  397. package/dist/reporting/documents.js +28 -1
  398. package/dist/reporting/index.d.ts +1 -1
  399. package/dist/reporting/index.js +4 -2
  400. package/dist/reporting/models.js +9 -0
  401. package/dist/reporting/overviews.d.ts +16 -0
  402. package/dist/reporting/overviews.js +56 -0
  403. package/dist/reporting/routes-documents.js +1 -1
  404. package/dist/reporting/routes.js +138 -80
  405. package/dist/reporting/web/DashboardView.d.ts +11 -0
  406. package/dist/reporting/web/DashboardView.js +2 -2
  407. package/dist/reporting/web/DesignerView.js +5 -0
  408. package/dist/reporting/web/Overview.d.ts +9 -0
  409. package/dist/reporting/web/Overview.js +53 -0
  410. package/dist/reporting/web/index.d.ts +1 -0
  411. package/dist/reporting/web/index.js +4 -1
  412. package/dist/reporting/web/messages.js +9 -0
  413. package/dist/sale_account/.kosh/bindings.js +1 -1
  414. package/dist/sale_account/.kosh/schema.d.ts +181 -6
  415. package/dist/sale_account/index.js +22 -1
  416. package/dist/sale_account/module.d.ts +639 -38
  417. package/dist/sale_portal/.kosh/bindings.js +1 -1
  418. package/dist/sale_portal/.kosh/schema.d.ts +59 -3
  419. package/dist/sale_portal/module.d.ts +435 -5
  420. package/dist/sale_project/.kosh/bindings.js +1 -1
  421. package/dist/sale_project/.kosh/schema.d.ts +732 -3
  422. package/dist/sale_project/module.d.ts +5349 -208
  423. package/dist/sale_project/module.js +17 -1
  424. package/dist/sale_stock/.kosh/bindings.d.ts +22 -0
  425. package/dist/sale_stock/.kosh/bindings.js +15 -0
  426. package/dist/sale_stock/.kosh/schema.d.ts +1303 -0
  427. package/dist/sale_stock/index.d.ts +7 -0
  428. package/dist/sale_stock/index.js +84 -0
  429. package/dist/sale_stock/module.d.ts +7514 -0
  430. package/dist/sale_stock/module.js +146 -0
  431. package/dist/sales/.kosh/bindings.js +1 -1
  432. package/dist/sales/.kosh/schema.d.ts +59 -3
  433. package/dist/sales/index.js +195 -4
  434. package/dist/sales/module.d.ts +356 -3
  435. package/dist/sales/module.js +78 -3
  436. package/dist/server/api/metadata.d.ts +5 -0
  437. package/dist/server/api/metadata.js +5 -0
  438. package/dist/server/api/rest.js +64 -1
  439. package/dist/server/api/views.js +1 -0
  440. package/dist/server/app-settings.d.ts +7 -0
  441. package/dist/server/app-settings.js +10 -0
  442. package/dist/server/auth/base.js +9 -0
  443. package/dist/server/index.d.ts +1 -0
  444. package/dist/server/index.js +1 -0
  445. package/dist/settings/app-settings.d.ts +2 -0
  446. package/dist/settings/app-settings.js +518 -0
  447. package/dist/settings/index.d.ts +1 -0
  448. package/dist/settings/index.js +15 -0
  449. package/dist/settings/people.js +29 -5
  450. package/dist/settings/web/AppSettingsPage.d.ts +11 -0
  451. package/dist/settings/web/AppSettingsPage.js +210 -0
  452. package/dist/settings/web/OverviewPage.js +16 -3
  453. package/dist/settings/web/PeoplePage.js +20 -6
  454. package/dist/settings/web/index.d.ts +1 -0
  455. package/dist/settings/web/index.js +3 -0
  456. package/dist/settings/web/messages.js +118 -2
  457. package/dist/sign/.kosh/bindings.d.ts +22 -0
  458. package/dist/sign/.kosh/bindings.js +15 -0
  459. package/dist/sign/.kosh/schema.d.ts +990 -0
  460. package/dist/sign/index.d.ts +14 -0
  461. package/dist/sign/index.js +350 -0
  462. package/dist/sign/module.d.ts +4478 -0
  463. package/dist/sign/module.js +158 -0
  464. package/dist/sign/sign.d.ts +82 -0
  465. package/dist/sign/sign.js +474 -0
  466. package/dist/stock/.kosh/bindings.d.ts +22 -0
  467. package/dist/stock/.kosh/bindings.js +15 -0
  468. package/dist/stock/.kosh/schema.d.ts +1222 -0
  469. package/dist/stock/demo.d.ts +8 -0
  470. package/dist/stock/demo.js +82 -0
  471. package/dist/stock/index.d.ts +4 -0
  472. package/dist/stock/index.js +903 -0
  473. package/dist/stock/module.d.ts +7856 -0
  474. package/dist/stock/module.js +1880 -0
  475. package/dist/stock/replenish.d.ts +38 -0
  476. package/dist/stock/replenish.js +176 -0
  477. package/dist/stock/web/Replenishment.d.ts +8 -0
  478. package/dist/stock/web/Replenishment.js +84 -0
  479. package/dist/stock/web/ScanBox.d.ts +8 -0
  480. package/dist/stock/web/ScanBox.js +90 -0
  481. package/dist/stock/web/index.d.ts +13 -0
  482. package/dist/stock/web/index.js +20 -0
  483. package/dist/stock/web/messages.d.ts +2 -0
  484. package/dist/stock/web/messages.js +77 -0
  485. package/dist/stock_account/.kosh/bindings.d.ts +22 -0
  486. package/dist/stock_account/.kosh/bindings.js +15 -0
  487. package/dist/stock_account/.kosh/schema.d.ts +2068 -0
  488. package/dist/stock_account/index.d.ts +3 -0
  489. package/dist/stock_account/index.js +140 -0
  490. package/dist/stock_account/module.d.ts +11523 -0
  491. package/dist/stock_account/module.js +339 -0
  492. package/dist/storage/attachments.d.ts +5 -0
  493. package/dist/storage/attachments.js +12 -1
  494. package/dist/storage/models.js +3 -0
  495. package/dist/storage/routes.js +13 -2
  496. package/dist/todo/.kosh/bindings.d.ts +22 -0
  497. package/dist/todo/.kosh/bindings.js +15 -0
  498. package/dist/todo/.kosh/schema.d.ts +732 -0
  499. package/dist/todo/demo.d.ts +6 -0
  500. package/dist/todo/demo.js +35 -0
  501. package/dist/todo/index.d.ts +3 -0
  502. package/dist/todo/index.js +187 -0
  503. package/dist/todo/module.d.ts +1180 -0
  504. package/dist/todo/module.js +130 -0
  505. package/dist/trade_finance/.kosh/bindings.d.ts +22 -0
  506. package/dist/trade_finance/.kosh/bindings.js +15 -0
  507. package/dist/trade_finance/.kosh/schema.d.ts +884 -0
  508. package/dist/trade_finance/demo.d.ts +6 -0
  509. package/dist/trade_finance/demo.js +63 -0
  510. package/dist/trade_finance/index.d.ts +3 -0
  511. package/dist/trade_finance/index.js +415 -0
  512. package/dist/trade_finance/module.d.ts +3779 -0
  513. package/dist/trade_finance/module.js +507 -0
  514. package/dist/trade_finance_account/.kosh/bindings.d.ts +22 -0
  515. package/dist/trade_finance_account/.kosh/bindings.js +15 -0
  516. package/dist/trade_finance_account/.kosh/schema.d.ts +1830 -0
  517. package/dist/trade_finance_account/index.d.ts +3 -0
  518. package/dist/trade_finance_account/index.js +93 -0
  519. package/dist/trade_finance_account/module.d.ts +8074 -0
  520. package/dist/trade_finance_account/module.js +245 -0
  521. package/dist/trade_finance_purchase/.kosh/bindings.d.ts +22 -0
  522. package/dist/trade_finance_purchase/.kosh/bindings.js +15 -0
  523. package/dist/trade_finance_purchase/.kosh/schema.d.ts +1246 -0
  524. package/dist/trade_finance_purchase/index.d.ts +8 -0
  525. package/dist/trade_finance_purchase/index.js +50 -0
  526. package/dist/trade_finance_purchase/module.d.ts +4257 -0
  527. package/dist/trade_finance_purchase/module.js +44 -0
  528. package/dist/trade_finance_sale/.kosh/bindings.d.ts +22 -0
  529. package/dist/trade_finance_sale/.kosh/bindings.js +15 -0
  530. package/dist/trade_finance_sale/.kosh/schema.d.ts +1072 -0
  531. package/dist/trade_finance_sale/index.d.ts +8 -0
  532. package/dist/trade_finance_sale/index.js +50 -0
  533. package/dist/trade_finance_sale/module.d.ts +4281 -0
  534. package/dist/trade_finance_sale/module.js +44 -0
  535. package/dist/ui/api.d.ts +2 -1
  536. package/dist/ui/api.js +2 -1
  537. package/dist/ui/app.js +26 -13
  538. package/dist/ui/components/AppIcon.js +30 -1
  539. package/dist/ui/components/primitives.d.ts +2 -0
  540. package/dist/ui/components/primitives.js +7 -1
  541. package/dist/ui/i18n/messages.d.ts +27 -0
  542. package/dist/ui/i18n/messages.js +46 -0
  543. package/dist/ui/index.d.ts +1 -0
  544. package/dist/ui/index.js +1 -0
  545. package/dist/ui/offline/queue.d.ts +5 -1
  546. package/dist/ui/offline/queue.js +2 -0
  547. package/dist/ui/registry.d.ts +5 -0
  548. package/dist/ui/shell/Home.js +55 -4
  549. package/dist/ui/views/FormView.js +102 -19
  550. package/dist/ui/views/KanbanView.d.ts +2 -0
  551. package/dist/ui/views/KanbanView.js +40 -3
  552. package/dist/ui/views/ListView.js +110 -11
  553. package/dist/ui/views/StatsRow.d.ts +14 -0
  554. package/dist/ui/views/StatsRow.js +88 -0
  555. package/dist/ui/views/widgets.d.ts +2 -1
  556. package/dist/ui/views/widgets.js +200 -1
  557. package/dist/views/builders.d.ts +19 -2
  558. package/dist/views/builders.js +21 -1
  559. package/dist/views/condition.d.ts +6 -0
  560. package/dist/views/condition.js +20 -0
  561. package/dist/views/documents.d.ts +8 -0
  562. package/dist/views/extend.d.ts +2 -2
  563. package/dist/views/extend.js +11 -1
  564. package/dist/views/index.d.ts +2 -2
  565. package/dist/views/index.js +2 -2
  566. package/dist/views/types.d.ts +52 -5
  567. package/dist/views/validate.js +39 -1
  568. package/dist/website/.kosh/bindings.d.ts +22 -0
  569. package/dist/website/.kosh/bindings.js +15 -0
  570. package/dist/website/.kosh/schema.d.ts +881 -0
  571. package/dist/website/demo.d.ts +6 -0
  572. package/dist/website/demo.js +115 -0
  573. package/dist/website/index.d.ts +9 -0
  574. package/dist/website/index.js +251 -0
  575. package/dist/website/module.d.ts +2987 -0
  576. package/dist/website/module.js +282 -0
  577. package/dist/website/render.d.ts +45 -0
  578. package/dist/website/render.js +102 -0
  579. package/dist/website/routes.d.ts +12 -0
  580. package/dist/website/routes.js +246 -0
  581. package/dist/website/web/PagePreview.d.ts +7 -0
  582. package/dist/website/web/PagePreview.js +33 -0
  583. package/dist/website/web/index.d.ts +11 -0
  584. package/dist/website/web/index.js +16 -0
  585. package/dist/website/web/messages.d.ts +2 -0
  586. package/dist/website/web/messages.js +15 -0
  587. package/dist/website_crm/.kosh/bindings.d.ts +22 -0
  588. package/dist/website_crm/.kosh/bindings.js +15 -0
  589. package/dist/website_crm/.kosh/schema.d.ts +967 -0
  590. package/dist/website_crm/index.d.ts +3 -0
  591. package/dist/website_crm/index.js +39 -0
  592. package/dist/website_crm/module.d.ts +2956 -0
  593. package/dist/website_crm/module.js +44 -0
  594. package/dist/website_sale/.kosh/bindings.d.ts +22 -0
  595. package/dist/website_sale/.kosh/bindings.js +15 -0
  596. package/dist/website_sale/.kosh/schema.d.ts +2284 -0
  597. package/dist/website_sale/couriers.d.ts +50 -0
  598. package/dist/website_sale/couriers.js +59 -0
  599. package/dist/website_sale/demo.d.ts +7 -0
  600. package/dist/website_sale/demo.js +90 -0
  601. package/dist/website_sale/growth.d.ts +31 -0
  602. package/dist/website_sale/growth.js +163 -0
  603. package/dist/website_sale/index.d.ts +49 -0
  604. package/dist/website_sale/index.js +538 -0
  605. package/dist/website_sale/module.d.ts +22027 -0
  606. package/dist/website_sale/module.js +397 -0
  607. package/dist/website_sale/shipments.d.ts +16 -0
  608. package/dist/website_sale/shipments.js +113 -0
  609. package/dist/website_sale/shop.d.ts +81 -0
  610. package/dist/website_sale/shop.js +649 -0
  611. package/dist/website_sale_stock/.kosh/bindings.d.ts +22 -0
  612. package/dist/website_sale_stock/.kosh/bindings.js +15 -0
  613. package/dist/website_sale_stock/.kosh/schema.d.ts +2681 -0
  614. package/dist/website_sale_stock/index.d.ts +9 -0
  615. package/dist/website_sale_stock/index.js +20 -0
  616. package/dist/website_sale_stock/module.d.ts +24998 -0
  617. package/dist/website_sale_stock/module.js +28 -0
  618. package/package.json +200 -2
@@ -0,0 +1,302 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * `shutro-erp/approvals` (RFC 0025): the **Approvals** app on the engine of `shutro-erp/permissions`. Everyone who decides or
4
+ * asks has it: the inbox (cards with the record, who asked, how long it waits, every level as a step; decided one by
5
+ * one or many at once, from the keyboard too), my requests (withdrawn while they wait), what I decided, and *Away*
6
+ * (who decides for me while I am away); for Approvals managers, the policies — levels, who decides each and how many,
7
+ * the deadline and who it escalates to — edited on screen. Approvers get each request by email with a link of their
8
+ * own that opens a confirmation page; reminders at half a deadline and escalations once late, hourly.
9
+ *
10
+ * - `GET /api/v1/public/approvals/:token` the confirmation page of an email link
11
+ * - `POST /api/v1/public/approvals/:token` `{ decision: approve | reject | send_back, comment? }`, once
12
+ * - `POST /api/v1/approvals/deadlines/run` reminders and escalations now (Approvals managers; hourly too)
13
+ */
14
+ import { randomBytes } from 'node:crypto';
15
+ import { mailerFromEnv } from '../mail/index.js';
16
+ import { defineModule } from '../modules-sdk/index.js';
17
+ import { decide, groupsOf, linkOf, permissions, sweepDeadlines, useLink, } from '../permissions/index.js';
18
+ import { digest, HttpProblem } from '../server/index.js';
19
+ import { form, list, search } from '../views/index.js';
20
+ import { configureMails, mailDeciders, mailTurn } from './mails.js';
21
+ import { approvalsShutro } from './module.js';
22
+ export { mailDeciders, mailTurn } from './mails.js';
23
+ export { approvalsShutro } from './module.js';
24
+ const escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
25
+ const DECISIONS = new Set(['approve', 'reject', 'send_back']);
26
+ /** The subscribers that email a record's approvers when its approvals are asked and when a level is approved. */
27
+ export function approvalMailWorkers(context) {
28
+ const turn = (name, topic) => context.shutro.subscribe(name, topic, async (event, env) => {
29
+ const p = (event.payload ?? {});
30
+ const model = String(p['model'] ?? '');
31
+ const id = Number(p['id']);
32
+ if (model === '' || !Number.isSafeInteger(id))
33
+ return;
34
+ if (topic === 'approval.decided' && (p['final'] === true || p['state'] !== 'approved'))
35
+ return;
36
+ await env.asSuperuser({ reason: 'approval emails' }, (su) => mailTurn(su, model, id));
37
+ }, { pollMs: 5000 });
38
+ return [
39
+ turn('approvals.asked', 'approval.requested'),
40
+ turn('approvals.next', 'approval.decided'),
41
+ ];
42
+ }
43
+ /** Reminders and escalations, then their emails (as the system). */
44
+ async function runDeadlines(su) {
45
+ const done = await sweepDeadlines(su);
46
+ const reminded = done.filter((d) => d.kind === 'reminded').map((d) => d.request);
47
+ const escalated = done.filter((d) => d.kind === 'escalated').map((d) => d.request);
48
+ await mailDeciders(su, reminded, 'reminded');
49
+ await mailDeciders(su, escalated, 'escalated');
50
+ return { reminded: reminded.length, escalated: escalated.length };
51
+ }
52
+ function registerApprovalAppRoutes(context) {
53
+ const { app, shutro } = context;
54
+ const tokenOf = (params) => String(params['token'] ?? '');
55
+ const linkFor = (token) => shutro.bootstrap({ reason: 'approval link' }, (raw) => raw.asSuperuser({ reason: 'approval link' }, async (su) => {
56
+ const link = await linkOf(su.withFlags({ checkCompany: false }), digest(token));
57
+ if (link === null)
58
+ return null;
59
+ const [record] = await (await su
60
+ .withFlags({ checkCompany: false })
61
+ .model(link.request.targetModel).search({ where: { id: link.request.targetId }, limit: 1 }))
62
+ .load(['displayName'])
63
+ .then((r) => r.read(['displayName']))
64
+ .catch(() => []);
65
+ return { ...link, name: String(record?.['displayName'] ?? `#${link.request.targetId}`) };
66
+ }));
67
+ app.post('/api/v1/approvals/deadlines/run', async (request) => request.env(async (raw) => {
68
+ const env = raw;
69
+ if (!env.administrator && !env.groups.has('approvals.manager'))
70
+ throw new HttpProblem(403, 'access', 'Approvals managers', 'This needs the Approvals manager role.');
71
+ return env.asSuperuser({ reason: 'approval deadlines' }, (su) => runDeadlines(su));
72
+ }, { write: true }));
73
+ // The confirmation page: the request, a comment, the three answers; its script (allowed by a nonce) posts.
74
+ app.get('/api/v1/public/approvals/:token', { config: { auth: false } }, async (request, reply) => {
75
+ const link = await linkFor(tokenOf(request.params));
76
+ if (link === null)
77
+ throw new HttpProblem(404, 'not-found', 'Not found', 'This link is used or has expired.');
78
+ const nonce = randomBytes(16).toString('base64');
79
+ return reply
80
+ .type('text/html; charset=utf-8')
81
+ .header('content-security-policy', `default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}'; connect-src 'self'`)
82
+ .header('cache-control', 'no-store')
83
+ .send(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
84
+ <title>${escapeHtml(link.name)}</title><style>body{font:16px system-ui,sans-serif;max-width:32rem;margin:3rem auto;padding:0 1rem;color:#111;background:#fff}
85
+ @media (prefers-color-scheme:dark){body{color:#eee;background:#111}}textarea{width:100%;min-height:5rem;font:inherit}
86
+ button{font:inherit;padding:.6rem 1rem;border-radius:.5rem;border:1px solid #888;cursor:pointer;margin:.5rem .5rem 0 0}.go{background:#16a34a;color:#fff;border-color:#16a34a}.no{background:#dc2626;color:#fff;border-color:#dc2626}</style></head>
87
+ <body><h1>${escapeHtml(link.name)}</h1><p>${escapeHtml(link.request.levelName)}${link.request.required > 1 ? ` — ${link.request.approvals} of ${link.request.required} approvals so far` : ''}</p>
88
+ <label for="c">Comment (needed to reject or send back)</label><textarea id="c" maxlength="2000"></textarea>
89
+ <div><button class="go" data-d="approve">Approve</button><button data-d="send_back">Send back for changes</button><button class="no" data-d="reject">Reject</button></div>
90
+ <p id="out" role="status"></p>
91
+ <script nonce="${nonce}">for(const b of document.querySelectorAll('button[data-d]')){b.addEventListener('click',async()=>{const out=document.getElementById('out');out.textContent='Sending…';
92
+ const r=await fetch(location.pathname,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({decision:b.dataset.d,comment:document.getElementById('c').value})});
93
+ let j={};try{j=await r.json()}catch{}out.textContent=r.ok?'Done — thank you.':(j.detail||'It could not be done.');if(r.ok)for(const x of document.querySelectorAll('button'))x.disabled=true;})}</script></body></html>`);
94
+ });
95
+ // The decision, as the link's person (their rights and the engine's checks), once; audited with the address.
96
+ app.post('/api/v1/public/approvals/:token', { config: { auth: false }, bodyLimit: 8 * 1024 }, async (request) => {
97
+ const token = tokenOf(request.params);
98
+ const body = (request.body ?? {});
99
+ const decision = String(body['decision'] ?? '');
100
+ if (!DECISIONS.has(decision))
101
+ throw new HttpProblem(400, 'invalid-decision', 'Invalid decision', 'approve, reject or send_back.');
102
+ const comment = typeof body['comment'] === 'string' ? body['comment'].slice(0, 2000) : undefined;
103
+ const link = await linkFor(token);
104
+ if (link === null)
105
+ throw new HttpProblem(404, 'not-found', 'Not found', 'This link is used or has expired.');
106
+ const company = link.request.company ??
107
+ (await shutro.bootstrap({ reason: 'approval link: company' }, async (raw) => {
108
+ const [u] = (await raw
109
+ .model('res.users')
110
+ .browse([link.user])
111
+ .load(['company'])).read(['company']);
112
+ return u?.['company'] ?? null;
113
+ }));
114
+ const groups = await shutro.bootstrap({ reason: 'approval link: groups' }, (raw) => groupsOf(raw, link.user, company));
115
+ const decided = await shutro.run({
116
+ user: link.user,
117
+ ...(company === null ? {} : { company, companies: [company] }),
118
+ groups,
119
+ reason: 'approval decided from an email link',
120
+ }, (env) => decide(env, link.request.id, decision, comment, { ip: request.ip }));
121
+ await shutro.bootstrap({ reason: 'approval link used' }, (raw) => raw.asSuperuser({ reason: 'approval link used' }, (su) => useLink(su, link.id)));
122
+ return { state: decided.state };
123
+ });
124
+ if (process.env['KOSH_SCHEDULES'] === 'off')
125
+ return;
126
+ const workers = approvalMailWorkers(context);
127
+ for (const w of workers)
128
+ w.start();
129
+ const run = async () => {
130
+ try {
131
+ const companies = await shutro.bootstrap({ reason: 'approval deadlines' }, async (env) => (await env.model('res.company').search({}))
132
+ .ids);
133
+ for (const company of companies)
134
+ await shutro.bootstrap({ reason: 'approval deadlines', company }, (env) => env.asSuperuser({ reason: 'approval deadlines' }, (su) => runDeadlines(su)));
135
+ }
136
+ catch (error) {
137
+ app.log.error({ err: error }, 'approval deadlines');
138
+ }
139
+ };
140
+ const timer = setInterval(() => void run(), 3_600_000);
141
+ timer.unref();
142
+ app.addHook('onClose', async () => {
143
+ clearInterval(timer);
144
+ for (const w of workers)
145
+ await w.stop();
146
+ });
147
+ }
148
+ /** The Approvals app with explicit options (tests: a memory mailer). */
149
+ export function approvalsModule(options = {}) {
150
+ configureMails({
151
+ mailer: 'mailer' in options ? options.mailer : mailerFromEnv(),
152
+ apiUrl: (options.apiUrl ?? process.env['KOSH_API_URL'] ?? 'http://localhost:3000').replace(/\/$/, ''),
153
+ });
154
+ return defineModule({
155
+ shutro: approvalsShutro,
156
+ kosh: '^0.1.0',
157
+ depends: [permissions],
158
+ app: {
159
+ code: 'approvals',
160
+ label: 'Approvals',
161
+ icon: 'approvals',
162
+ sequence: 2,
163
+ category: 'Productivity',
164
+ summary: 'Everything waiting for your decision, in one place — and what you asked for',
165
+ },
166
+ accessLevels: {
167
+ user: { label: 'Everyone (decide and ask)', groups: ['base.user'] },
168
+ manager: {
169
+ label: 'Manager (policies)',
170
+ groups: ['approvals.manager'],
171
+ implies: ['user'],
172
+ },
173
+ },
174
+ routes: (context) => {
175
+ configureMails({ log: (message, error) => context.app.log.warn({ err: error }, message) });
176
+ registerApprovalAppRoutes(context);
177
+ },
178
+ actions: {
179
+ 'approvals.action_inbox': { type: 'client', page: 'approvals.Inbox', label: 'To approve' },
180
+ 'approvals.action_mine': { type: 'client', page: 'approvals.Mine', label: 'My requests' },
181
+ 'approvals.action_history': {
182
+ type: 'client',
183
+ page: 'approvals.History',
184
+ label: 'What I decided',
185
+ },
186
+ 'approvals.action_away': {
187
+ model: 'approval.delegation',
188
+ label: 'Away',
189
+ views: ['list', 'form'],
190
+ },
191
+ 'approvals.action_policies': {
192
+ model: 'approval.policy',
193
+ label: 'Policies',
194
+ views: ['list', 'form'],
195
+ visibleTo: ['approvals.manager'],
196
+ },
197
+ },
198
+ menus: [
199
+ { id: 'approvals.root', label: 'Approvals', app: 'approvals' },
200
+ {
201
+ id: 'approvals.inbox',
202
+ parent: 'approvals.root',
203
+ label: 'To approve',
204
+ action: 'approvals.action_inbox',
205
+ sequence: 1,
206
+ },
207
+ {
208
+ id: 'approvals.mine',
209
+ parent: 'approvals.root',
210
+ label: 'My requests',
211
+ action: 'approvals.action_mine',
212
+ sequence: 2,
213
+ },
214
+ {
215
+ id: 'approvals.history',
216
+ parent: 'approvals.root',
217
+ label: 'What I decided',
218
+ action: 'approvals.action_history',
219
+ sequence: 3,
220
+ },
221
+ {
222
+ id: 'approvals.away',
223
+ parent: 'approvals.root',
224
+ label: 'Away',
225
+ action: 'approvals.action_away',
226
+ sequence: 4,
227
+ },
228
+ {
229
+ id: 'approvals.configuration',
230
+ parent: 'approvals.root',
231
+ label: 'Configuration',
232
+ sequence: 9,
233
+ visibleTo: ['approvals.manager'],
234
+ },
235
+ {
236
+ id: 'approvals.policies',
237
+ parent: 'approvals.configuration',
238
+ label: 'Policies',
239
+ action: 'approvals.action_policies',
240
+ sequence: 1,
241
+ visibleTo: ['approvals.manager'],
242
+ },
243
+ ],
244
+ views: {
245
+ 'approval.delegation.list': list('approval.delegation', {
246
+ columns: ['user', 'delegate', 'dateFrom', 'dateTo', 'note'],
247
+ }),
248
+ 'approval.delegation.form': form('approval.delegation', (k) => ({
249
+ sheet: [
250
+ k.group('main', ['user', 'delegate', 'dateFrom', 'dateTo', 'note'], {
251
+ description: 'While you are away, the person you choose decides your approvals — in their own name, for you. They never approve their own requests.',
252
+ }),
253
+ ],
254
+ })),
255
+ 'approval.policy.list': list('approval.policy', {
256
+ columns: [
257
+ 'name',
258
+ 'targetModel',
259
+ 'company',
260
+ 'onApproved',
261
+ 'reasonRequired',
262
+ 'active',
263
+ ],
264
+ }),
265
+ 'approval.policy.form': form('approval.policy', (k) => ({
266
+ sheet: [
267
+ k.group('main', ['name', 'targetModel', 'company', 'sequence'], {
268
+ description: 'A record of the model gets one approval per level whose condition it meets; levels with a lower sequence are decided first, those with the same side by side.',
269
+ }),
270
+ k.group('outcomes', ['onApproved', 'onRejected', 'onSentBack', 'onWithdrawn', 'reasonRequired'], {
271
+ label: 'What happens',
272
+ description: 'The transitions of the record run when it is approved, rejected, sent back or withdrawn.',
273
+ }),
274
+ k.lines('levels', [
275
+ { field: 'sequence', id: 'level_sequence' },
276
+ { field: 'name', id: 'level_name' },
277
+ { field: 'approverKind', id: 'level_kind' },
278
+ { field: 'approverRole', id: 'level_role' },
279
+ { field: 'approverUsers', id: 'level_users', widget: 'tags' },
280
+ { field: 'approverField', id: 'level_field' },
281
+ { field: 'quorum', id: 'level_quorum' },
282
+ { field: 'quorumCount', id: 'level_count' },
283
+ { field: 'deadlineHours', id: 'level_deadline' },
284
+ { field: 'escalateRole', id: 'level_escalate' },
285
+ ]),
286
+ ],
287
+ })),
288
+ },
289
+ search: {
290
+ 'approval.delegation': search('approval.delegation', {
291
+ fields: ['user.name', 'delegate.name'],
292
+ groupBy: ['user', 'delegate'],
293
+ }),
294
+ 'approval.policy': search('approval.policy', {
295
+ fields: ['name', 'targetModel'],
296
+ groupBy: ['targetModel'],
297
+ }),
298
+ },
299
+ });
300
+ }
301
+ /** The Approvals app (mail from the environment). */
302
+ export const approvals = approvalsModule();
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Approvals by email (RFC 0025 §3.4): whoever may decide a request now — when it is asked, when its turn comes after
3
+ * a lower level, when it is reminded at half its deadline or escalated once late — gets an email with a link of their
4
+ * own (single use, seven days; only its digest kept) to a page that confirms the decision. A link decides nothing by
5
+ * being opened: mail scanners follow links. Sent after the commit; a failed send is logged.
6
+ */
7
+ import { type Mailer } from '../mail/index.js';
8
+ import { type ApprovalRequestInfo } from '../permissions/index.js';
9
+ import type { Env } from '../server/orm.js';
10
+ declare let config: {
11
+ mailer: Mailer | undefined;
12
+ apiUrl: string;
13
+ log: (m: string, e: unknown) => void;
14
+ };
15
+ export declare function configureMails(next: Partial<typeof config>): void;
16
+ declare const WHY: {
17
+ readonly asked: (who: string, record: string) => string;
18
+ readonly reminded: (_who: string, record: string) => string;
19
+ readonly escalated: (_who: string, record: string) => string;
20
+ };
21
+ /** Emails each person who may decide `requests` now a link of their own; skips those already sent one. */
22
+ export declare function mailDeciders(su: Env, requests: readonly ApprovalRequestInfo[], why: keyof typeof WHY): Promise<number>;
23
+ /** After a record's approvals were asked, or one of its levels approved: the requests whose turn it is now. */
24
+ export declare function mailTurn(su: Env, model: string, id: number): Promise<number>;
25
+ export {};
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Approvals by email (RFC 0025 §3.4): whoever may decide a request now — when it is asked, when its turn comes after
3
+ * a lower level, when it is reminded at half its deadline or escalated once late — gets an email with a link of their
4
+ * own (single use, seven days; only its digest kept) to a page that confirms the decision. A link decides nothing by
5
+ * being opened: mail scanners follow links. Sent after the commit; a failed send is logged.
6
+ */
7
+ import { layout } from '../mail/index.js';
8
+ import { issueLinks, turnOf } from '../permissions/index.js';
9
+ import { digest, randomToken } from '../server/index.js';
10
+ const ref = (env, name) => env.model(name);
11
+ const read = async (rows, keys) => rows.length === 0 ? [] : (await rows.load(keys)).read(keys);
12
+ let config = {
13
+ mailer: undefined,
14
+ apiUrl: 'http://localhost:3000',
15
+ log: () => undefined,
16
+ };
17
+ export function configureMails(next) {
18
+ config = { ...config, ...next };
19
+ }
20
+ const WHY = {
21
+ asked: (who, record) => `${who} asks you to approve ${record}`,
22
+ reminded: (_who, record) => `Reminder: ${record} waits for your approval`,
23
+ escalated: (_who, record) => `${record} is late: you may decide it now`,
24
+ };
25
+ /** Emails each person who may decide `requests` now a link of their own; skips those already sent one. */
26
+ export async function mailDeciders(su, requests, why) {
27
+ const mailer = config.mailer;
28
+ let sent = 0;
29
+ for (const request of requests) {
30
+ if (why === 'asked') {
31
+ const existing = await ref(su, 'approval.link').search({
32
+ where: { request: request.id },
33
+ limit: 1,
34
+ });
35
+ if (existing.length > 0)
36
+ continue;
37
+ }
38
+ const links = await issueLinks(su, request, su.today(), () => randomToken(24), digest);
39
+ if (links.length === 0)
40
+ continue;
41
+ const [record] = await read(await ref(su, request.targetModel)
42
+ .search({ where: { id: request.targetId }, limit: 1 })
43
+ .catch(() => ({ length: 0 })), ['displayName']);
44
+ const name = String(record?.['displayName'] ?? `#${request.targetId}`);
45
+ const [asker] = request.requestedBy === null
46
+ ? []
47
+ : await read(ref(su, 'res.users').browse([request.requestedBy]), ['name']);
48
+ const who = String(asker?.['name'] ?? 'Someone');
49
+ const users = new Map((await read(ref(su, 'res.users').browse(links.map((l) => l.user)), ['email', 'login'])).map((u) => [
50
+ u['id'],
51
+ String(u['email'] ?? (String(u['login'] ?? '').includes('@') ? u['login'] : '')),
52
+ ]));
53
+ for (const link of links) {
54
+ const to = users.get(link.user) ?? '';
55
+ if (to === '' || mailer === undefined)
56
+ continue;
57
+ const subject = WHY[why](who, name);
58
+ const body = layout({
59
+ title: subject,
60
+ paragraphs: [
61
+ `${request.levelName}${request.required > 1 ? ` (${request.approvals} of ${request.required} approvals so far)` : ''}.`,
62
+ 'The link is yours alone and works once; it opens a page where you approve, reject or send it back.',
63
+ ],
64
+ action: {
65
+ label: 'Review and decide',
66
+ url: `${config.apiUrl}/api/v1/public/approvals/${link.token}`,
67
+ },
68
+ });
69
+ su.afterCommit(async () => {
70
+ await mailer
71
+ .send({ to, subject, html: body.html, text: body.text })
72
+ .catch((error) => config.log('approval email not sent', error));
73
+ });
74
+ sent += 1;
75
+ }
76
+ }
77
+ return sent;
78
+ }
79
+ /** After a record's approvals were asked, or one of its levels approved: the requests whose turn it is now. */
80
+ export async function mailTurn(su, model, id) {
81
+ return mailDeciders(su, await turnOf(su, model, id), 'asked');
82
+ }
@@ -0,0 +1 @@
1
+ export declare const approvalsShutro: import("shutro").Module<readonly [], readonly [import("shutro").Module<readonly import("shutro").AnyModel[], readonly import("shutro").ModuleBase[], readonly import("shutro").AnyExtension[]>], readonly []>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The data layer of the Approvals app (RFC 0025): the models are the engine's (`approval.policy`, `approval.level`,
3
+ * `approval.request`, `approval.vote` in `shutro-erp/permissions`); this module adds the app around them.
4
+ */
5
+ import { permissionsShutro } from '../permissions/index.js';
6
+ import { defineModule } from './.kosh/bindings.js';
7
+ export const approvalsShutro = defineModule({
8
+ name: 'approvals',
9
+ version: '0.1.0',
10
+ depends: [permissionsShutro],
11
+ models: [],
12
+ });
@@ -0,0 +1,4 @@
1
+ /** What I decided (RFC 0025 §3.1): each decision with the record, my answer, my comment and when. */
2
+ import { type PageProps } from '../../ui/index.js';
3
+ import type { ReactElement } from 'react';
4
+ export declare function History({ api }: PageProps): ReactElement;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** What I decided (RFC 0025 §3.1): each decision with the record, my answer, my comment and when. */
3
+ import { Badge, EmptyState, useKosh } from '../../ui/index.js';
4
+ import { useQuery } from '@tanstack/react-query';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { cardsQuery, TONE } from './shared.js';
7
+ export function History({ api }) {
8
+ const { t } = useTranslation('approvals');
9
+ const { format } = useKosh();
10
+ const cards = useQuery(cardsQuery(api, 'history'));
11
+ const rows = cards.data ?? [];
12
+ return (_jsxs("div", { className: "grid gap-4 p-4", children: [_jsx("h1", { className: "text-xl font-semibold", children: t('history.title') }), rows.length === 0 ? (_jsx(EmptyState, { title: cards.isLoading ? t('loading') : t('history.empty') })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted", children: _jsxs("tr", { children: [_jsx("th", { className: "px-2 py-1 text-start", children: t('history.record') }), _jsx("th", { className: "px-2 py-1 text-start", children: t('history.level') }), _jsx("th", { className: "px-2 py-1 text-start", children: t('history.answer') }), _jsx("th", { className: "px-2 py-1 text-start", children: t('history.comment') }), _jsx("th", { className: "px-2 py-1 text-start", children: t('history.when') })] }) }), _jsx("tbody", { children: rows.map((c) => (_jsxs("tr", { className: "border-b", children: [_jsxs("td", { className: "px-2 py-1", children: [_jsx("span", { className: "font-medium", children: c.record.name }), ' ', _jsx("span", { className: "text-muted-foreground", children: c.record.label })] }), _jsx("td", { className: "px-2 py-1", children: c.levelName }), _jsx("td", { className: "px-2 py-1", children: _jsx(Badge, { tone: TONE[c.state], children: t(`state.${c.state}`) }) }), _jsx("td", { className: "px-2 py-1", children: c.comment ?? '' }), _jsx("td", { className: "px-2 py-1", children: c.decidedAt === null ? '' : format.datetime(c.decidedAt) })] }, c.id))) })] }) }))] }));
13
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The Approvals inbox (RFC 0025 §3.1): what waits for me, as cards — the record and its app, who asked and how long
3
+ * ago, every level of the round as a step with its votes —, decided one by one (approve, reject or send back, with a
4
+ * comment; a reason is needed for the last two) or many at once. Keyboard: J / K move, A approves, R rejects, X
5
+ * selects. The server decides who may decide what; this page shows and sends.
6
+ */
7
+ import { type PageProps } from '../../ui/index.js';
8
+ import { type ReactElement } from 'react';
9
+ export declare function Inbox({ api }: PageProps): ReactElement;
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The Approvals inbox (RFC 0025 §3.1): what waits for me, as cards — the record and its app, who asked and how long
4
+ * ago, every level of the round as a step with its votes —, decided one by one (approve, reject or send back, with a
5
+ * comment; a reason is needed for the last two) or many at once. Keyboard: J / K move, A approves, R rejects, X
6
+ * selects. The server decides who may decide what; this page shows and sends.
7
+ */
8
+ import { Alert, Badge, Button, Checkbox, EmptyState, Textarea, useKosh, } from '../../ui/index.js';
9
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
10
+ import { useEffect, useId, useState } from 'react';
11
+ import { useTranslation } from 'react-i18next';
12
+ import { ago, cardsQuery, Steps, waitedTone } from './shared.js';
13
+ export function Inbox({ api }) {
14
+ const { t } = useTranslation('approvals');
15
+ const { format } = useKosh();
16
+ const id = useId();
17
+ const client = useQueryClient();
18
+ const cards = useQuery(cardsQuery(api, 'inbox'));
19
+ const stats = useQuery({
20
+ queryKey: ['approvals', 'stats'],
21
+ queryFn: () => api.call('GET', '/api/v1/approvals/stats'),
22
+ });
23
+ const routes = useQuery({ queryKey: ['routes'], queryFn: () => api.routes() });
24
+ const [selected, setSelected] = useState(new Set());
25
+ const [focus, setFocus] = useState(0);
26
+ const [comments, setComments] = useState({});
27
+ const [message, setMessage] = useState(null);
28
+ const rows = cards.data ?? [];
29
+ const refresh = async () => {
30
+ await client.invalidateQueries({ queryKey: ['approvals'] });
31
+ await client.invalidateQueries({ queryKey: ['home', 'approvals'] });
32
+ };
33
+ const decide = useMutation({
34
+ mutationFn: ({ request, decision }) => api.call('POST', `/api/v1/approvals/requests/${request}/${decision}`, {
35
+ comment: comments[request] ?? '',
36
+ }),
37
+ onSuccess: async (_r, v) => {
38
+ setMessage(t(`done.${v.decision}`));
39
+ await refresh();
40
+ },
41
+ onError: (e) => setMessage(e.message),
42
+ });
43
+ const bulk = useMutation({
44
+ mutationFn: () => api.call('POST', '/api/v1/approvals/bulk', { requests: [...selected], decision: 'approve' }),
45
+ onSuccess: async (r) => {
46
+ setMessage(t('bulkDone', { count: r.approved, failed: r.failed.length }));
47
+ setSelected(new Set());
48
+ await refresh();
49
+ },
50
+ onError: (e) => setMessage(e.message),
51
+ });
52
+ // Keyboard: J / K move, A approves, R rejects (with the comment typed), X selects.
53
+ useEffect(() => {
54
+ const onKey = (e) => {
55
+ const target = e.target;
56
+ if (target !== null && ['INPUT', 'TEXTAREA'].includes(target.tagName))
57
+ return;
58
+ const card = rows[focus];
59
+ if (e.key === 'j')
60
+ setFocus((f) => Math.min(f + 1, rows.length - 1));
61
+ else if (e.key === 'k')
62
+ setFocus((f) => Math.max(f - 1, 0));
63
+ else if (card === undefined)
64
+ return;
65
+ else if (e.key === 'a')
66
+ decide.mutate({ request: card.id, decision: 'approve' });
67
+ else if (e.key === 'r' && (comments[card.id] ?? '').trim() !== '')
68
+ decide.mutate({ request: card.id, decision: 'reject' });
69
+ else if (e.key === 'x')
70
+ setSelected((s) => {
71
+ const next = new Set(s);
72
+ if (next.has(card.id))
73
+ next.delete(card.id);
74
+ else
75
+ next.add(card.id);
76
+ return next;
77
+ });
78
+ else
79
+ return;
80
+ e.preventDefault();
81
+ };
82
+ window.addEventListener('keydown', onKey);
83
+ return () => window.removeEventListener('keydown', onKey);
84
+ }, [rows, focus, comments, decide]);
85
+ const link = (c) => {
86
+ const route = routes.data?.[c.record.model];
87
+ return route?.app === undefined ? null : `/${route.app}/a/${route.action}/${c.record.id}`;
88
+ };
89
+ return (_jsxs("div", { className: "grid gap-4 p-4", children: [_jsxs("header", { className: "flex flex-wrap items-center gap-3", children: [_jsx("h1", { className: "text-xl font-semibold", children: t('inbox.title') }), _jsx(Badge, { tone: "neutral", children: rows.length }), _jsx("p", { className: "w-full text-sm text-muted-foreground", children: t('inbox.hint') })] }), stats.data === undefined ? null : (_jsx("dl", { className: "grid grid-cols-2 gap-3 md:grid-cols-4", children: [
90
+ ['waiting', String(stats.data.waiting), 'neutral'],
91
+ [
92
+ 'overdue',
93
+ String(stats.data.overdue),
94
+ stats.data.overdue > 0 ? 'danger' : 'neutral',
95
+ ],
96
+ ['decided', String(stats.data.decidedThisMonth), 'neutral'],
97
+ [
98
+ 'median',
99
+ stats.data.medianHours === null ? '—' : t('hours', { n: stats.data.medianHours }),
100
+ 'neutral',
101
+ ],
102
+ ].map(([key, value, tone]) => (_jsxs("div", { className: "rounded-lg border bg-surface p-3", children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: t(`stats.${key}`) }), _jsx("dd", { className: `text-2xl font-semibold tabular-nums ${tone === 'danger' ? 'text-danger' : ''}`, children: value })] }, key))) })), rows.length === 0 ? null : (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Checkbox, { id: `${id}-all`, checked: selected.size === rows.length && rows.length > 0, onCheckedChange: (c) => setSelected(c === true ? new Set(rows.map((r) => r.id)) : new Set()) }), _jsx("label", { htmlFor: `${id}-all`, className: "text-sm", children: t('inbox.selectAll') }), _jsx(Button, { variant: "primary", disabled: selected.size === 0 || bulk.isPending, onClick: () => bulk.mutate(), children: t('inbox.approveSelected', { count: selected.size }) })] })), message === null ? null : _jsx(Alert, { tone: "info", children: message }), cards.isLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('loading') })) : rows.length === 0 ? (_jsx(EmptyState, { title: t('inbox.empty'), description: t('inbox.emptyHint') })) : (_jsx("ul", { className: "grid gap-3", children: rows.map((c, i) => {
103
+ const href = link(c);
104
+ const comment = comments[c.id] ?? '';
105
+ return (_jsxs("li", { "aria-current": i === focus ? 'true' : undefined, className: `grid gap-3 rounded-lg border bg-surface p-4 ${i === focus ? 'ring-2 ring-accent' : ''}`, children: [_jsxs("div", { className: "flex flex-wrap items-start gap-3", children: [_jsx(Checkbox, { "aria-label": t('inbox.select', { name: c.record.name }), checked: selected.has(c.id), onCheckedChange: (v) => setSelected((s) => {
106
+ const next = new Set(s);
107
+ if (v === true)
108
+ next.add(c.id);
109
+ else
110
+ next.delete(c.id);
111
+ return next;
112
+ }) }), _jsxs("div", { className: "grid min-w-0 flex-1 gap-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [href === null ? (_jsx("span", { className: "font-semibold", children: c.record.name })) : (_jsx("a", { href: href, className: "font-semibold underline-offset-2 hover:underline", children: c.record.name })), _jsx(Badge, { tone: "neutral", children: c.record.label }), _jsx(Badge, { tone: waitedTone(c), children: t('waiting', { since: ago(format.locale, c.waitingSince) }) }), c.dueAt === null ? null : (_jsx(Badge, { tone: Date.parse(c.dueAt) < Date.now() ? 'danger' : 'info', children: t('due', { when: ago(format.locale, c.dueAt) }) })), c.escalatedTo === null ? null : (_jsx(Badge, { tone: "danger", children: t('escalated') }))] }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [t('askedBy', { name: c.requester?.name ?? '—' }), " \u00B7 ", c.levelName, c.required > 1 ? ` · ${t('votes', { n: c.approvals, of: c.required })}` : '', c.round > 1 ? ` · ${t('round', { n: c.round })}` : ''] })] })] }), _jsx(Steps, { steps: c.steps }), _jsx(Textarea, { "aria-label": t('comment', { name: c.record.name }), placeholder: t('commentHint'), value: comment, onChange: (e) => setComments((m) => ({ ...m, [c.id]: e.target.value })) }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "primary", disabled: decide.isPending, onClick: () => decide.mutate({ request: c.id, decision: 'approve' }), children: t('approve') }), _jsx(Button, { disabled: decide.isPending || comment.trim() === '', onClick: () => decide.mutate({ request: c.id, decision: 'send-back' }), children: t('sendBack') }), _jsx(Button, { variant: "danger", disabled: decide.isPending || comment.trim() === '', onClick: () => decide.mutate({ request: c.id, decision: 'reject' }), children: t('reject') })] })] }, c.id));
113
+ }) }))] }));
114
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * My requests (RFC 0025 §3.1): what I asked to be approved, where each stands — every level as a step, since when —,
3
+ * withdrawn while it waits.
4
+ */
5
+ import { type PageProps } from '../../ui/index.js';
6
+ import { type ReactElement } from 'react';
7
+ export declare function Mine({ api }: PageProps): ReactElement;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * My requests (RFC 0025 §3.1): what I asked to be approved, where each stands — every level as a step, since when —,
4
+ * withdrawn while it waits.
5
+ */
6
+ import { Alert, Badge, Button, EmptyState, useKosh } from '../../ui/index.js';
7
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
8
+ import { useState } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { ago, cardsQuery, Steps, TONE } from './shared.js';
11
+ export function Mine({ api }) {
12
+ const { t } = useTranslation('approvals');
13
+ const { format } = useKosh();
14
+ const client = useQueryClient();
15
+ const cards = useQuery(cardsQuery(api, 'mine'));
16
+ const [message, setMessage] = useState(null);
17
+ const withdraw = useMutation({
18
+ mutationFn: ({ model, id }) => api.call('POST', `/api/v1/approvals/${model}/${id}/withdraw`),
19
+ onSuccess: async () => {
20
+ setMessage(t('done.withdrawn'));
21
+ await client.invalidateQueries({ queryKey: ['approvals'] });
22
+ },
23
+ onError: (e) => setMessage(e.message),
24
+ });
25
+ const rows = cards.data ?? [];
26
+ return (_jsxs("div", { className: "grid gap-4 p-4", children: [_jsx("h1", { className: "text-xl font-semibold", children: t('mine.title') }), message === null ? null : _jsx(Alert, { tone: "info", children: message }), rows.length === 0 ? (_jsx(EmptyState, { title: cards.isLoading ? t('loading') : t('mine.empty') })) : (_jsx("ul", { className: "grid gap-3", children: rows.map((c) => (_jsxs("li", { className: "grid gap-2 rounded-lg border bg-surface p-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-semibold", children: c.record.name }), _jsx(Badge, { tone: "neutral", children: c.record.label }), _jsx(Badge, { tone: TONE[c.state], children: t(`state.${c.state}`) }), _jsx("span", { className: "text-sm text-muted-foreground", children: t('asked', { since: ago(format.locale, c.waitingSince) }) })] }), _jsx(Steps, { steps: c.steps }), c.comment === null ? null : (_jsxs("p", { className: "text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: t('saidWhy') }), " \u201C", c.comment, "\u201D"] })), c.state !== 'pending' ? null : (_jsx("div", { children: _jsx(Button, { disabled: withdraw.isPending, onClick: () => withdraw.mutate({ model: c.record.model, id: c.record.id }), children: t('withdraw') }) }))] }, `${c.record.model}:${c.record.id}`))) }))] }));
27
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * The web contribution of `shutro-erp/approvals`: the inbox, my requests and what I decided.
4
+ */
5
+ import { type Registry } from '../../ui/index.js';
6
+ import type { i18n } from 'i18next';
7
+ import { messages } from './messages.js';
8
+ export { History } from './History.js';
9
+ export { Inbox } from './Inbox.js';
10
+ export { Mine } from './Mine.js';
11
+ /** Registers the pages (and the messages in `i18n`, when given). */
12
+ export declare function registerApprovalsWeb(registry: Registry, i18n?: i18n): Registry;
13
+ export { messages as approvalsMessages };