shutro-erp 0.3.3 → 0.3.5

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 (412) hide show
  1. package/dist/account_portal/.kosh/bindings.js +1 -1
  2. package/dist/account_portal/.kosh/schema.d.ts +181 -3
  3. package/dist/account_portal/module.d.ts +359 -6
  4. package/dist/accounting/.kosh/bindings.js +1 -1
  5. package/dist/accounting/.kosh/schema.d.ts +181 -3
  6. package/dist/accounting/index.js +214 -2
  7. package/dist/accounting/module.d.ts +217 -5
  8. package/dist/accounting/module.js +23 -2
  9. package/dist/calendar/.kosh/bindings.d.ts +22 -0
  10. package/dist/calendar/.kosh/bindings.js +15 -0
  11. package/dist/calendar/.kosh/schema.d.ts +795 -0
  12. package/dist/calendar/demo.d.ts +6 -0
  13. package/dist/calendar/demo.js +24 -0
  14. package/dist/calendar/index.d.ts +3 -0
  15. package/dist/calendar/index.js +291 -0
  16. package/dist/calendar/module.d.ts +1705 -0
  17. package/dist/calendar/module.js +180 -0
  18. package/dist/cli/init.js +48 -0
  19. package/dist/cli/templates.d.ts +1 -1
  20. package/dist/cli/templates.js +137 -0
  21. package/dist/contacts/.kosh/bindings.js +1 -1
  22. package/dist/contacts/.kosh/schema.d.ts +205 -3
  23. package/dist/contacts/demo.js +30 -1
  24. package/dist/contacts/index.d.ts +1 -1
  25. package/dist/contacts/index.js +155 -9
  26. package/dist/contacts/module.d.ts +338 -2
  27. package/dist/contacts/module.js +82 -5
  28. package/dist/crm/.kosh/bindings.d.ts +22 -0
  29. package/dist/crm/.kosh/bindings.js +15 -0
  30. package/dist/crm/.kosh/schema.d.ts +799 -0
  31. package/dist/crm/demo.d.ts +6 -0
  32. package/dist/crm/demo.js +45 -0
  33. package/dist/crm/index.d.ts +3 -0
  34. package/dist/crm/index.js +361 -0
  35. package/dist/crm/module.d.ts +1843 -0
  36. package/dist/crm/module.js +207 -0
  37. package/dist/event/.kosh/bindings.d.ts +22 -0
  38. package/dist/event/.kosh/bindings.js +15 -0
  39. package/dist/event/.kosh/schema.d.ts +843 -0
  40. package/dist/event/demo.d.ts +6 -0
  41. package/dist/event/demo.js +65 -0
  42. package/dist/event/index.d.ts +14 -0
  43. package/dist/event/index.js +333 -0
  44. package/dist/event/mails.d.ts +15 -0
  45. package/dist/event/mails.js +112 -0
  46. package/dist/event/module.d.ts +2800 -0
  47. package/dist/event/module.js +323 -0
  48. package/dist/event/web/CheckInBox.d.ts +9 -0
  49. package/dist/event/web/CheckInBox.js +87 -0
  50. package/dist/event/web/index.d.ts +11 -0
  51. package/dist/event/web/index.js +16 -0
  52. package/dist/event/web/messages.d.ts +2 -0
  53. package/dist/event/web/messages.js +23 -0
  54. package/dist/event_account/.kosh/bindings.d.ts +22 -0
  55. package/dist/event_account/.kosh/bindings.js +15 -0
  56. package/dist/event_account/.kosh/schema.d.ts +1639 -0
  57. package/dist/event_account/index.d.ts +3 -0
  58. package/dist/event_account/index.js +28 -0
  59. package/dist/event_account/module.d.ts +6968 -0
  60. package/dist/event_account/module.js +90 -0
  61. package/dist/hr/.kosh/bindings.d.ts +22 -0
  62. package/dist/hr/.kosh/bindings.js +15 -0
  63. package/dist/hr/.kosh/schema.d.ts +878 -0
  64. package/dist/hr/demo.d.ts +6 -0
  65. package/dist/hr/demo.js +60 -0
  66. package/dist/hr/index.d.ts +3 -0
  67. package/dist/hr/index.js +483 -0
  68. package/dist/hr/module.d.ts +2611 -0
  69. package/dist/hr/module.js +350 -0
  70. package/dist/integrations/index.js +12 -0
  71. package/dist/integrations/web/OAuthAppsPage.d.ts +9 -0
  72. package/dist/integrations/web/OAuthAppsPage.js +79 -0
  73. package/dist/integrations/web/index.d.ts +1 -0
  74. package/dist/integrations/web/index.js +3 -0
  75. package/dist/integrations/web/messages.js +58 -0
  76. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  77. package/dist/l10n_bd/.kosh/schema.d.ts +181 -3
  78. package/dist/l10n_bd/module.d.ts +359 -6
  79. package/dist/l10n_in/.kosh/bindings.js +1 -1
  80. package/dist/l10n_in/.kosh/schema.d.ts +181 -3
  81. package/dist/l10n_in/module.d.ts +380 -7
  82. package/dist/maintenance/.kosh/bindings.d.ts +22 -0
  83. package/dist/maintenance/.kosh/bindings.js +15 -0
  84. package/dist/maintenance/.kosh/schema.d.ts +911 -0
  85. package/dist/maintenance/demo.d.ts +6 -0
  86. package/dist/maintenance/demo.js +60 -0
  87. package/dist/maintenance/index.d.ts +3 -0
  88. package/dist/maintenance/index.js +422 -0
  89. package/dist/maintenance/module.d.ts +3545 -0
  90. package/dist/maintenance/module.js +446 -0
  91. package/dist/maintenance_stock/.kosh/bindings.d.ts +22 -0
  92. package/dist/maintenance_stock/.kosh/bindings.js +15 -0
  93. package/dist/maintenance_stock/.kosh/schema.d.ts +1371 -0
  94. package/dist/maintenance_stock/index.d.ts +9 -0
  95. package/dist/maintenance_stock/index.js +52 -0
  96. package/dist/maintenance_stock/module.d.ts +6953 -0
  97. package/dist/maintenance_stock/module.js +192 -0
  98. package/dist/messaging/web/index.js +1 -1
  99. package/dist/modules-sdk/index.d.ts +1 -1
  100. package/dist/modules-sdk/manifest.d.ts +70 -0
  101. package/dist/modules-sdk/manifest.js +35 -1
  102. package/dist/mrp/.kosh/bindings.d.ts +22 -0
  103. package/dist/mrp/.kosh/bindings.js +15 -0
  104. package/dist/mrp/.kosh/schema.d.ts +1378 -0
  105. package/dist/mrp/demo.d.ts +7 -0
  106. package/dist/mrp/demo.js +42 -0
  107. package/dist/mrp/index.d.ts +3 -0
  108. package/dist/mrp/index.js +392 -0
  109. package/dist/mrp/module.d.ts +8608 -0
  110. package/dist/mrp/module.js +695 -0
  111. package/dist/mrp_account/.kosh/bindings.d.ts +22 -0
  112. package/dist/mrp_account/.kosh/bindings.js +15 -0
  113. package/dist/mrp_account/.kosh/schema.d.ts +2106 -0
  114. package/dist/mrp_account/index.d.ts +3 -0
  115. package/dist/mrp_account/index.js +22 -0
  116. package/dist/mrp_account/module.d.ts +20840 -0
  117. package/dist/mrp_account/module.js +123 -0
  118. package/dist/notify/web/index.js +1 -1
  119. package/dist/payment/.kosh/bindings.js +1 -1
  120. package/dist/payment/.kosh/schema.d.ts +181 -3
  121. package/dist/payment/module.d.ts +359 -6
  122. package/dist/portal/.kosh/bindings.js +1 -1
  123. package/dist/portal/.kosh/schema.d.ts +205 -3
  124. package/dist/portal/module.d.ts +166 -2
  125. package/dist/pos/.kosh/bindings.d.ts +22 -0
  126. package/dist/pos/.kosh/bindings.js +15 -0
  127. package/dist/pos/.kosh/schema.d.ts +1690 -0
  128. package/dist/pos/accounting.d.ts +10 -0
  129. package/dist/pos/accounting.js +149 -0
  130. package/dist/pos/demo.d.ts +6 -0
  131. package/dist/pos/demo.js +90 -0
  132. package/dist/pos/index.d.ts +9 -0
  133. package/dist/pos/index.js +349 -0
  134. package/dist/pos/module.d.ts +9636 -0
  135. package/dist/pos/module.js +423 -0
  136. package/dist/pos/routes.d.ts +39 -0
  137. package/dist/pos/routes.js +368 -0
  138. package/dist/pos/web/Register.d.ts +9 -0
  139. package/dist/pos/web/Register.js +252 -0
  140. package/dist/pos/web/index.d.ts +11 -0
  141. package/dist/pos/web/index.js +16 -0
  142. package/dist/pos/web/messages.d.ts +2 -0
  143. package/dist/pos/web/messages.js +87 -0
  144. package/dist/pos_stock/.kosh/bindings.d.ts +22 -0
  145. package/dist/pos_stock/.kosh/bindings.js +15 -0
  146. package/dist/pos_stock/.kosh/schema.d.ts +2015 -0
  147. package/dist/pos_stock/index.d.ts +9 -0
  148. package/dist/pos_stock/index.js +35 -0
  149. package/dist/pos_stock/module.d.ts +11935 -0
  150. package/dist/pos_stock/module.js +115 -0
  151. package/dist/product/.kosh/bindings.js +1 -1
  152. package/dist/product/.kosh/schema.d.ts +178 -1
  153. package/dist/product/index.js +81 -4
  154. package/dist/product/module.d.ts +43 -1
  155. package/dist/product/module.js +9 -2
  156. package/dist/project/.kosh/bindings.js +1 -1
  157. package/dist/project/.kosh/schema.d.ts +181 -3
  158. package/dist/project/index.js +147 -7
  159. package/dist/project/module.d.ts +210 -2
  160. package/dist/project/module.js +52 -5
  161. package/dist/project_portal/.kosh/bindings.js +1 -1
  162. package/dist/project_portal/.kosh/schema.d.ts +181 -3
  163. package/dist/project_portal/module.d.ts +353 -4
  164. package/dist/purchase/.kosh/bindings.js +1 -1
  165. package/dist/purchase/.kosh/schema.d.ts +181 -3
  166. package/dist/purchase/index.js +163 -3
  167. package/dist/purchase/module.d.ts +215 -3
  168. package/dist/purchase/module.js +21 -3
  169. package/dist/purchase_account/.kosh/bindings.js +1 -1
  170. package/dist/purchase_account/.kosh/schema.d.ts +182 -3
  171. package/dist/purchase_account/index.js +26 -1
  172. package/dist/purchase_account/module.d.ts +393 -7
  173. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  174. package/dist/purchase_portal/.kosh/schema.d.ts +181 -3
  175. package/dist/purchase_portal/module.d.ts +366 -5
  176. package/dist/purchase_stock/.kosh/bindings.d.ts +22 -0
  177. package/dist/purchase_stock/.kosh/bindings.js +15 -0
  178. package/dist/purchase_stock/.kosh/schema.d.ts +1310 -0
  179. package/dist/purchase_stock/index.d.ts +7 -0
  180. package/dist/purchase_stock/index.js +91 -0
  181. package/dist/purchase_stock/module.d.ts +6763 -0
  182. package/dist/purchase_stock/module.js +144 -0
  183. package/dist/purchase_stock_account/.kosh/bindings.d.ts +22 -0
  184. package/dist/purchase_stock_account/.kosh/bindings.js +15 -0
  185. package/dist/purchase_stock_account/.kosh/schema.d.ts +1959 -0
  186. package/dist/purchase_stock_account/index.d.ts +3 -0
  187. package/dist/purchase_stock_account/index.js +24 -0
  188. package/dist/purchase_stock_account/module.d.ts +35366 -0
  189. package/dist/purchase_stock_account/module.js +162 -0
  190. package/dist/recruitment/.kosh/bindings.d.ts +22 -0
  191. package/dist/recruitment/.kosh/bindings.js +15 -0
  192. package/dist/recruitment/.kosh/schema.d.ts +990 -0
  193. package/dist/recruitment/demo.d.ts +6 -0
  194. package/dist/recruitment/demo.js +45 -0
  195. package/dist/recruitment/index.d.ts +3 -0
  196. package/dist/recruitment/index.js +385 -0
  197. package/dist/recruitment/module.d.ts +3247 -0
  198. package/dist/recruitment/module.js +193 -0
  199. package/dist/recruitment/web/HireButton.d.ts +9 -0
  200. package/dist/recruitment/web/HireButton.js +32 -0
  201. package/dist/recruitment/web/index.d.ts +11 -0
  202. package/dist/recruitment/web/index.js +16 -0
  203. package/dist/recruitment/web/messages.d.ts +2 -0
  204. package/dist/recruitment/web/messages.js +19 -0
  205. package/dist/reporting/documents.d.ts +5 -0
  206. package/dist/reporting/documents.js +28 -1
  207. package/dist/reporting/index.d.ts +1 -1
  208. package/dist/reporting/index.js +4 -2
  209. package/dist/reporting/models.js +9 -0
  210. package/dist/reporting/overviews.d.ts +16 -0
  211. package/dist/reporting/overviews.js +56 -0
  212. package/dist/reporting/routes-documents.js +1 -1
  213. package/dist/reporting/routes.js +138 -80
  214. package/dist/reporting/web/DashboardView.d.ts +11 -0
  215. package/dist/reporting/web/DashboardView.js +2 -2
  216. package/dist/reporting/web/DesignerView.js +5 -0
  217. package/dist/reporting/web/Overview.d.ts +9 -0
  218. package/dist/reporting/web/Overview.js +53 -0
  219. package/dist/reporting/web/index.d.ts +1 -0
  220. package/dist/reporting/web/index.js +4 -1
  221. package/dist/reporting/web/messages.js +9 -0
  222. package/dist/sale_account/.kosh/bindings.js +1 -1
  223. package/dist/sale_account/.kosh/schema.d.ts +186 -3
  224. package/dist/sale_account/index.js +22 -1
  225. package/dist/sale_account/module.d.ts +461 -7
  226. package/dist/sale_portal/.kosh/bindings.js +1 -1
  227. package/dist/sale_portal/.kosh/schema.d.ts +185 -3
  228. package/dist/sale_portal/module.d.ts +434 -5
  229. package/dist/sale_project/.kosh/bindings.js +1 -1
  230. package/dist/sale_project/.kosh/schema.d.ts +187 -3
  231. package/dist/sale_project/module.d.ts +476 -6
  232. package/dist/sale_stock/.kosh/bindings.d.ts +22 -0
  233. package/dist/sale_stock/.kosh/bindings.js +15 -0
  234. package/dist/sale_stock/.kosh/schema.d.ts +1228 -0
  235. package/dist/sale_stock/index.d.ts +7 -0
  236. package/dist/sale_stock/index.js +84 -0
  237. package/dist/sale_stock/module.d.ts +6791 -0
  238. package/dist/sale_stock/module.js +146 -0
  239. package/dist/sales/.kosh/bindings.js +1 -1
  240. package/dist/sales/.kosh/schema.d.ts +185 -3
  241. package/dist/sales/index.js +191 -4
  242. package/dist/sales/module.d.ts +355 -3
  243. package/dist/sales/module.js +78 -3
  244. package/dist/server/api/metadata.d.ts +5 -0
  245. package/dist/server/api/metadata.js +5 -0
  246. package/dist/server/api/rest.js +64 -1
  247. package/dist/server/api/views.js +1 -0
  248. package/dist/server/app-settings.d.ts +7 -0
  249. package/dist/server/app-settings.js +10 -0
  250. package/dist/server/auth/base.js +85 -0
  251. package/dist/server/auth/oauth.d.ts +21 -0
  252. package/dist/server/auth/oauth.js +579 -0
  253. package/dist/server/auth/service.d.ts +4 -0
  254. package/dist/server/auth/service.js +17 -0
  255. package/dist/server/identity.d.ts +2 -0
  256. package/dist/server/images.d.ts +12 -0
  257. package/dist/server/images.js +139 -0
  258. package/dist/server/index.d.ts +2 -0
  259. package/dist/server/index.js +2 -0
  260. package/dist/server/server.d.ts +4 -0
  261. package/dist/server/server.js +9 -1
  262. package/dist/settings/app-settings.d.ts +2 -0
  263. package/dist/settings/app-settings.js +518 -0
  264. package/dist/settings/company.js +3 -1
  265. package/dist/settings/index.d.ts +1 -0
  266. package/dist/settings/index.js +15 -0
  267. package/dist/settings/people.js +32 -6
  268. package/dist/settings/web/AppSettingsPage.d.ts +11 -0
  269. package/dist/settings/web/AppSettingsPage.js +210 -0
  270. package/dist/settings/web/CompaniesPage.js +2 -2
  271. package/dist/settings/web/CompanyPage.js +13 -2
  272. package/dist/settings/web/ConnectedAppsPage.d.ts +7 -0
  273. package/dist/settings/web/ConnectedAppsPage.js +33 -0
  274. package/dist/settings/web/OverviewPage.js +28 -4
  275. package/dist/settings/web/PeoplePage.js +23 -18
  276. package/dist/settings/web/ProfilePage.js +9 -7
  277. package/dist/settings/web/index.d.ts +2 -0
  278. package/dist/settings/web/index.js +8 -0
  279. package/dist/settings/web/messages.js +146 -2
  280. package/dist/stock/.kosh/bindings.d.ts +22 -0
  281. package/dist/stock/.kosh/bindings.js +15 -0
  282. package/dist/stock/.kosh/schema.d.ts +1147 -0
  283. package/dist/stock/demo.d.ts +7 -0
  284. package/dist/stock/demo.js +55 -0
  285. package/dist/stock/index.d.ts +3 -0
  286. package/dist/stock/index.js +674 -0
  287. package/dist/stock/module.d.ts +6330 -0
  288. package/dist/stock/module.js +1325 -0
  289. package/dist/stock/web/ScanBox.d.ts +8 -0
  290. package/dist/stock/web/ScanBox.js +90 -0
  291. package/dist/stock/web/index.d.ts +11 -0
  292. package/dist/stock/web/index.js +16 -0
  293. package/dist/stock/web/messages.d.ts +2 -0
  294. package/dist/stock/web/messages.js +25 -0
  295. package/dist/stock_account/.kosh/bindings.d.ts +22 -0
  296. package/dist/stock_account/.kosh/bindings.js +15 -0
  297. package/dist/stock_account/.kosh/schema.d.ts +1875 -0
  298. package/dist/stock_account/index.d.ts +3 -0
  299. package/dist/stock_account/index.js +130 -0
  300. package/dist/stock_account/module.d.ts +10655 -0
  301. package/dist/stock_account/module.js +339 -0
  302. package/dist/storage/attachments.d.ts +5 -0
  303. package/dist/storage/attachments.js +12 -1
  304. package/dist/storage/models.js +3 -0
  305. package/dist/storage/routes.js +13 -2
  306. package/dist/todo/.kosh/bindings.d.ts +22 -0
  307. package/dist/todo/.kosh/bindings.js +15 -0
  308. package/dist/todo/.kosh/schema.d.ts +732 -0
  309. package/dist/todo/demo.d.ts +6 -0
  310. package/dist/todo/demo.js +35 -0
  311. package/dist/todo/index.d.ts +3 -0
  312. package/dist/todo/index.js +187 -0
  313. package/dist/todo/module.d.ts +1180 -0
  314. package/dist/todo/module.js +130 -0
  315. package/dist/trade_finance/.kosh/bindings.d.ts +22 -0
  316. package/dist/trade_finance/.kosh/bindings.js +15 -0
  317. package/dist/trade_finance/.kosh/schema.d.ts +884 -0
  318. package/dist/trade_finance/demo.d.ts +6 -0
  319. package/dist/trade_finance/demo.js +63 -0
  320. package/dist/trade_finance/index.d.ts +3 -0
  321. package/dist/trade_finance/index.js +413 -0
  322. package/dist/trade_finance/module.d.ts +3779 -0
  323. package/dist/trade_finance/module.js +505 -0
  324. package/dist/trade_finance_account/.kosh/bindings.d.ts +22 -0
  325. package/dist/trade_finance_account/.kosh/bindings.js +15 -0
  326. package/dist/trade_finance_account/.kosh/schema.d.ts +1711 -0
  327. package/dist/trade_finance_account/index.d.ts +3 -0
  328. package/dist/trade_finance_account/index.js +92 -0
  329. package/dist/trade_finance_account/module.d.ts +7928 -0
  330. package/dist/trade_finance_account/module.js +244 -0
  331. package/dist/trade_finance_purchase/.kosh/bindings.d.ts +22 -0
  332. package/dist/trade_finance_purchase/.kosh/bindings.js +15 -0
  333. package/dist/trade_finance_purchase/.kosh/schema.d.ts +1153 -0
  334. package/dist/trade_finance_purchase/index.d.ts +8 -0
  335. package/dist/trade_finance_purchase/index.js +50 -0
  336. package/dist/trade_finance_purchase/module.d.ts +4252 -0
  337. package/dist/trade_finance_purchase/module.js +44 -0
  338. package/dist/trade_finance_sale/.kosh/bindings.d.ts +22 -0
  339. package/dist/trade_finance_sale/.kosh/bindings.js +15 -0
  340. package/dist/trade_finance_sale/.kosh/schema.d.ts +1071 -0
  341. package/dist/trade_finance_sale/index.d.ts +8 -0
  342. package/dist/trade_finance_sale/index.js +50 -0
  343. package/dist/trade_finance_sale/module.d.ts +4280 -0
  344. package/dist/trade_finance_sale/module.js +44 -0
  345. package/dist/ui/api.d.ts +6 -1
  346. package/dist/ui/api.js +2 -1
  347. package/dist/ui/app.js +51 -17
  348. package/dist/ui/components/AppIcon.js +33 -1
  349. package/dist/ui/components/Avatar.d.ts +21 -0
  350. package/dist/ui/components/Avatar.js +71 -0
  351. package/dist/ui/components/primitives.d.ts +2 -0
  352. package/dist/ui/components/primitives.js +7 -1
  353. package/dist/ui/i18n/messages.d.ts +53 -0
  354. package/dist/ui/i18n/messages.js +98 -0
  355. package/dist/ui/index.d.ts +2 -0
  356. package/dist/ui/index.js +2 -0
  357. package/dist/ui/offline/queue.d.ts +5 -1
  358. package/dist/ui/offline/queue.js +2 -0
  359. package/dist/ui/registry.d.ts +12 -1
  360. package/dist/ui/registry.js +10 -1
  361. package/dist/ui/search/ViewTabs.d.ts +14 -0
  362. package/dist/ui/search/ViewTabs.js +42 -0
  363. package/dist/ui/shell/Consent.d.ts +5 -0
  364. package/dist/ui/shell/Consent.js +35 -0
  365. package/dist/ui/shell/Home.js +136 -16
  366. package/dist/ui/shell/Sidebar.js +15 -22
  367. package/dist/ui/views/FormView.js +98 -17
  368. package/dist/ui/views/KanbanView.d.ts +2 -0
  369. package/dist/ui/views/KanbanView.js +32 -3
  370. package/dist/ui/views/ListView.js +155 -31
  371. package/dist/ui/views/StatsRow.d.ts +14 -0
  372. package/dist/ui/views/StatsRow.js +88 -0
  373. package/dist/ui/views/widgets.d.ts +2 -1
  374. package/dist/ui/views/widgets.js +194 -1
  375. package/dist/views/builders.d.ts +19 -2
  376. package/dist/views/builders.js +21 -1
  377. package/dist/views/condition.d.ts +6 -0
  378. package/dist/views/condition.js +20 -0
  379. package/dist/views/documents.d.ts +8 -0
  380. package/dist/views/extend.d.ts +2 -2
  381. package/dist/views/extend.js +11 -1
  382. package/dist/views/index.d.ts +2 -2
  383. package/dist/views/index.js +2 -2
  384. package/dist/views/types.d.ts +52 -5
  385. package/dist/views/validate.js +39 -1
  386. package/dist/website/.kosh/bindings.d.ts +22 -0
  387. package/dist/website/.kosh/bindings.js +15 -0
  388. package/dist/website/.kosh/schema.d.ts +881 -0
  389. package/dist/website/demo.d.ts +6 -0
  390. package/dist/website/demo.js +115 -0
  391. package/dist/website/index.d.ts +9 -0
  392. package/dist/website/index.js +250 -0
  393. package/dist/website/module.d.ts +2987 -0
  394. package/dist/website/module.js +281 -0
  395. package/dist/website/render.d.ts +45 -0
  396. package/dist/website/render.js +102 -0
  397. package/dist/website/routes.d.ts +12 -0
  398. package/dist/website/routes.js +246 -0
  399. package/dist/website/web/PagePreview.d.ts +7 -0
  400. package/dist/website/web/PagePreview.js +33 -0
  401. package/dist/website/web/index.d.ts +11 -0
  402. package/dist/website/web/index.js +16 -0
  403. package/dist/website/web/messages.d.ts +2 -0
  404. package/dist/website/web/messages.js +15 -0
  405. package/dist/website_crm/.kosh/bindings.d.ts +22 -0
  406. package/dist/website_crm/.kosh/bindings.js +15 -0
  407. package/dist/website_crm/.kosh/schema.d.ts +966 -0
  408. package/dist/website_crm/index.d.ts +3 -0
  409. package/dist/website_crm/index.js +39 -0
  410. package/dist/website_crm/module.d.ts +2955 -0
  411. package/dist/website_crm/module.js +44 -0
  412. package/package.json +118 -1
@@ -0,0 +1,518 @@
1
+ import { audit, HttpProblem, publicApiModels, ServerConfigError, } from '../server/index.js';
2
+ const ref = (env, name) => env.model(name);
3
+ const TYPES = new Set(['boolean', 'integer', 'decimal', 'char', 'text', 'selection', 'date']);
4
+ /** Company field kinds a setting may be. */
5
+ const KINDS = {
6
+ boolean: 'boolean',
7
+ integer: 'integer',
8
+ decimal: 'decimal',
9
+ monetary: 'decimal',
10
+ float: 'decimal',
11
+ char: 'char',
12
+ text: 'text',
13
+ selection: 'selection',
14
+ date: 'date',
15
+ };
16
+ export function registerAppSettingsRoutes(context) {
17
+ const { app, registry } = context;
18
+ const modules = registry.loaded.modules;
19
+ const company = publicApiModels(registry).get('res.company');
20
+ const companyFields = new Map((company?.fields ?? []).map((f) => [f.key, f]));
21
+ const descriptors = registry.registry.get('res.company').model.fields;
22
+ const groups = registry.registry.security.groups;
23
+ // ---- the declarations, checked once at start: a wrong one stops the server, like other manifest errors.
24
+ const fail = (module, detail) => new ServerConfigError(`The settings of ${module}: ${detail}`, 'Fix the module’s settings declaration.');
25
+ const fromField = (module, spec, field) => {
26
+ if (field === undefined || !field.stored)
27
+ throw fail(module, `'${spec.key}' is not a stored res.company field (add it with extendModel).`);
28
+ const type = KINDS[field.kind];
29
+ if (type === undefined)
30
+ throw fail(module, `'${spec.key}' is a ${field.kind} field: not a setting type.`);
31
+ const raw = descriptors[spec.key]?.['default'];
32
+ return {
33
+ key: spec.key,
34
+ type,
35
+ label: spec.label ?? field.label,
36
+ help: spec.help ?? null,
37
+ default: spec.default !== undefined
38
+ ? spec.default
39
+ : typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'
40
+ ? raw
41
+ : null,
42
+ min: spec.min ?? null,
43
+ max: spec.max ?? null,
44
+ options: field.options === null ? null : field.options.map(([value, label]) => ({ value, label })),
45
+ visibleWhen: spec.visibleWhen ?? null,
46
+ };
47
+ };
48
+ const ownSetting = (module, spec) => {
49
+ if (spec.type === undefined || !TYPES.has(spec.type) || spec.label === undefined)
50
+ throw fail(module, `'${spec.key}' needs a type and a label (an installation setting).`);
51
+ if (spec.type === 'selection' && spec.options === undefined)
52
+ throw fail(module, `'${spec.key}' is a selection without options.`);
53
+ return {
54
+ key: spec.key,
55
+ type: spec.type,
56
+ label: spec.label,
57
+ help: spec.help ?? null,
58
+ default: spec.default ?? null,
59
+ min: spec.min ?? null,
60
+ max: spec.max ?? null,
61
+ options: spec.options === undefined
62
+ ? null
63
+ : Object.entries(spec.options).map(([value, label]) => ({ value, label })),
64
+ visibleWhen: spec.visibleWhen ?? null,
65
+ };
66
+ };
67
+ const sections = [];
68
+ const seen = new Set();
69
+ for (const m of modules)
70
+ for (const spec of m.settings?.sections ?? []) {
71
+ if (!/^[a-z][a-z0-9_]*$/.test(spec.key))
72
+ throw fail(m.name, `the section key '${spec.key}'.`);
73
+ if (seen.has(`${m.name}.${spec.key}`))
74
+ throw fail(m.name, `two sections '${spec.key}'.`);
75
+ seen.add(`${m.name}.${spec.key}`);
76
+ for (const g of spec.editors ?? [])
77
+ if (!groups.has(g))
78
+ throw fail(m.name, `the editor group '${g}' is not defined.`);
79
+ const keys = new Set();
80
+ const settings = spec.fields.map((f) => {
81
+ if (keys.has(f.key))
82
+ throw fail(m.name, `'${f.key}' twice in '${spec.key}'.`);
83
+ keys.add(f.key);
84
+ return spec.scope === 'company'
85
+ ? fromField(m.name, f, companyFields.get(f.key))
86
+ : ownSetting(m.name, f);
87
+ });
88
+ for (const s of settings)
89
+ if (s.visibleWhen !== null && !keys.has(s.visibleWhen.key))
90
+ throw fail(m.name, `'${s.key}' depends on '${s.visibleWhen.key}', not in its section.`);
91
+ sections.push({ app: m.name, spec, settings });
92
+ }
93
+ /** The groups that open an app: its access levels' groups. */
94
+ const openers = new Map(modules.map((m) => [
95
+ m.name,
96
+ new Set(Object.values(m.accessLevels ?? {}).flatMap((l) => [...l.groups])),
97
+ ]));
98
+ const mayView = (env, module) => env.administrator || [...(openers.get(module) ?? [])].some((g) => env.groups.has(g));
99
+ const mayEdit = (env, section) => env.administrator || (section.spec.editors ?? []).some((g) => env.groups.has(g));
100
+ const label = (module) => {
101
+ const m = modules.find((x) => x.name === module);
102
+ return { label: m?.app?.label ?? module, icon: m?.app?.icon ?? null };
103
+ };
104
+ const storedKey = (section, key) => `${section.app}.${key}`;
105
+ /** The current values of a section: the company's fields, or the installation's records. */
106
+ const valuesOf = async (su, section, companyId) => {
107
+ if (section.spec.scope === 'company') {
108
+ const keys = section.settings.map((s) => s.key);
109
+ const [row] = (await ref(su, 'res.company').browse([companyId]).load(keys)).read(keys);
110
+ return Object.fromEntries(keys.map((k) => [k, normalize(row?.[k])]));
111
+ }
112
+ const found = await ref(su, 'kosh.setting').search({
113
+ where: { key: { in: section.settings.map((s) => storedKey(section, s.key)) } },
114
+ });
115
+ const rows = found.length === 0 ? [] : (await found.load(['key', 'value'])).read(['key', 'value']);
116
+ const stored = new Map(rows.map((r) => [String(r['key']), r['value']]));
117
+ return Object.fromEntries(section.settings.map((s) => [
118
+ s.key,
119
+ stored.has(storedKey(section, s.key))
120
+ ? stored.get(storedKey(section, s.key))
121
+ : s.default,
122
+ ]));
123
+ };
124
+ /** A value checked against its setting. @throws {HttpProblem} 422 */
125
+ const check = (s, value) => {
126
+ const bad = (detail) => new HttpProblem(422, 'invalid-setting', 'Invalid setting', `${s.label}: ${detail}`);
127
+ if (value === null || value === '') {
128
+ if (s.type === 'boolean')
129
+ throw bad('yes or no.');
130
+ return null;
131
+ }
132
+ switch (s.type) {
133
+ case 'boolean':
134
+ if (typeof value !== 'boolean')
135
+ throw bad('yes or no.');
136
+ return value;
137
+ case 'integer': {
138
+ if (!Number.isSafeInteger(value))
139
+ throw bad('a whole number.');
140
+ const n = value;
141
+ if ((s.min !== null && n < s.min) || (s.max !== null && n > s.max))
142
+ throw bad(`from ${s.min ?? '…'} to ${s.max ?? '…'}.`);
143
+ return n;
144
+ }
145
+ case 'decimal': {
146
+ // Money and quantities stay strings (Shutro Decimal), never JS numbers.
147
+ const text = typeof value === 'number' ? String(value) : String(value);
148
+ if (!/^-?\d+(\.\d+)?$/.test(text))
149
+ throw bad('a number.');
150
+ return text;
151
+ }
152
+ case 'selection':
153
+ if (typeof value !== 'string' || !(s.options ?? []).some((o) => o.value === value))
154
+ throw bad('one of the choices.');
155
+ return value;
156
+ case 'date':
157
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value))
158
+ throw bad('a date (YYYY-MM-DD).');
159
+ return value;
160
+ default:
161
+ if (typeof value !== 'string' || value.length > (s.type === 'char' ? 500 : 20_000))
162
+ throw bad('text.');
163
+ return value;
164
+ }
165
+ };
166
+ const sectionOf = (request) => {
167
+ const { app: module, section } = request.params;
168
+ const found = sections.find((s) => s.app === module && s.spec.key === section);
169
+ if (found === undefined)
170
+ throw new HttpProblem(404, 'missing-record', 'Not found', `There are no settings ${module}/${section}.`);
171
+ return found;
172
+ };
173
+ const shape = (section, values, editable) => ({
174
+ key: section.spec.key,
175
+ label: section.spec.label,
176
+ description: section.spec.description ?? null,
177
+ scope: section.spec.scope,
178
+ editable,
179
+ settings: section.settings.map((s) => ({
180
+ ...s,
181
+ value: values[s.key] ?? null,
182
+ changed: (values[s.key] ?? null) !== s.default,
183
+ })),
184
+ });
185
+ app.get('/api/v1/settings/app-settings', async (request) => request.env(async (env) => {
186
+ const apps = [...new Set(sections.map((s) => s.app))].filter((m) => mayView(env, m));
187
+ return {
188
+ administrator: env.administrator,
189
+ apps: apps.map((m) => ({
190
+ app: m,
191
+ ...label(m),
192
+ sections: sections
193
+ .filter((s) => s.app === m)
194
+ .map((s) => ({
195
+ key: s.spec.key,
196
+ label: s.spec.label,
197
+ description: s.spec.description ?? null,
198
+ keywords: [s.spec.keywords ?? '', ...s.settings.map((x) => x.label)].join(' '),
199
+ })),
200
+ })),
201
+ };
202
+ }));
203
+ app.get('/api/v1/settings/app-settings/:app', async (request) => {
204
+ const module = request.params.app ?? '';
205
+ return request.env(async (env) => {
206
+ const mine = sections.filter((s) => s.app === module);
207
+ if (mine.length === 0 || !mayView(env, module))
208
+ throw new HttpProblem(404, 'missing-record', 'Not found', `There are no settings for ${module}.`);
209
+ return env.asSuperuser({ reason: 'app settings' }, async (su) => ({
210
+ app: module,
211
+ ...label(module),
212
+ company: env.companyId,
213
+ sections: await Promise.all(mine.map(async (s) => shape(s, await valuesOf(su, s, env.companyId), mayEdit(env, s)))),
214
+ }));
215
+ }, { write: true });
216
+ });
217
+ /** Writes `changes` to the section's store for `companyId` and audits them with the old and new values. */
218
+ const apply = async (env, su, section, changes, companyId, how, extra, ip) => {
219
+ if (section.spec.scope === 'company')
220
+ await ref(su, 'res.company')
221
+ .browse([companyId])
222
+ .write(Object.fromEntries(Object.entries(changes).map(([k, c]) => [k, c.to])));
223
+ else
224
+ for (const [k, c] of Object.entries(changes)) {
225
+ const key = storedKey(section, k);
226
+ const existing = await ref(su, 'kosh.setting').search({ where: { key } });
227
+ if (existing.length > 0)
228
+ await existing.write({ value: c.to });
229
+ else
230
+ await ref(su, 'kosh.setting').create([{ key, value: c.to }]);
231
+ }
232
+ await audit(env, {
233
+ kind: 'settings.changed',
234
+ summary: `${label(section.app).label} › ${section.spec.label}: ${Object.keys(changes).join(', ')} ${how}.`,
235
+ details: {
236
+ app: section.app,
237
+ section: section.spec.key,
238
+ scope: section.spec.scope,
239
+ changes,
240
+ ...extra,
241
+ },
242
+ company: companyId,
243
+ ip,
244
+ });
245
+ };
246
+ /** Writes `next` (only the keys that changed), audited with the old and new values. */
247
+ const save = (request, section, next) => request.env(async (env) => {
248
+ if (!mayView(env, section.app) || !mayEdit(env, section))
249
+ throw new HttpProblem(403, 'access', 'App settings', `Changing ${section.spec.label} needs ${(section.spec.editors ?? []).length === 0
250
+ ? 'the Administrator role'
251
+ : `one of: ${(section.spec.editors ?? []).join(', ')}`}.`);
252
+ return env.asSuperuser({ reason: 'app settings' }, async (su) => {
253
+ const old = await valuesOf(su, section, env.companyId);
254
+ const wanted = next(old);
255
+ const changes = Object.fromEntries(Object.entries(wanted)
256
+ .filter(([k, v]) => old[k] !== v)
257
+ .map(([k, v]) => [k, { from: old[k] ?? null, to: v }]));
258
+ if (Object.keys(changes).length > 0) {
259
+ await apply(env, su, section, changes, env.companyId, 'changed', {}, request.ip);
260
+ await su.flush();
261
+ }
262
+ return shape(section, { ...old, ...wanted }, true);
263
+ });
264
+ }, { write: true });
265
+ app.patch('/api/v1/settings/app-settings/:app/:section', async (request) => {
266
+ const section = sectionOf(request);
267
+ const body = (request.body ?? {});
268
+ const values = body['values'];
269
+ if (typeof values !== 'object' || values === null || Array.isArray(values))
270
+ throw new HttpProblem(422, 'invalid-setting', 'Invalid setting', 'values is { key: value }.');
271
+ const checked = {};
272
+ for (const [key, value] of Object.entries(values)) {
273
+ const s = section.settings.find((x) => x.key === key);
274
+ if (s === undefined)
275
+ throw new HttpProblem(422, 'invalid-setting', 'Invalid setting', `'${key}' is not in ${section.spec.label}.`);
276
+ checked[key] = check(s, value);
277
+ }
278
+ return save(request, section, () => checked);
279
+ });
280
+ app.post('/api/v1/settings/app-settings/:app/:section/reset', async (request) => {
281
+ const section = sectionOf(request);
282
+ const keys = request.body?.['keys'];
283
+ const chosen = Array.isArray(keys) ? keys.map(String) : section.settings.map((s) => s.key);
284
+ const defaults = {};
285
+ for (const key of chosen) {
286
+ const s = section.settings.find((x) => x.key === key);
287
+ if (s === undefined)
288
+ throw new HttpProblem(422, 'invalid-setting', 'Invalid setting', `'${key}' is not in ${section.spec.label}.`);
289
+ defaults[key] = s.default;
290
+ }
291
+ return save(request, section, () => defaults);
292
+ });
293
+ // ---- step 2: history, copy between companies, export and import.
294
+ const administrators = (env) => {
295
+ if (!env.administrator)
296
+ throw new HttpProblem(403, 'access', 'App settings', 'This needs the Administrator role.');
297
+ };
298
+ /** Whether `companyId` is one of the user's companies. */
299
+ const memberOf = async (su, uid, companyId) => (await ref(su, 'res.users').search({
300
+ where: { id: uid, companies: { any: { id: companyId } } },
301
+ })).length > 0;
302
+ const companyOf = async (su, id) => {
303
+ const [row] = (await ref(su, 'res.company').browse([id]).load(['name'])).read(['name']);
304
+ if (row === undefined)
305
+ throw new HttpProblem(404, 'missing-record', 'Not found', `There is no company ${id}.`);
306
+ return { id, name: String(row['name']) };
307
+ };
308
+ const sectionKey = (s) => `${s.app}.${s.spec.key}`;
309
+ const describeChanges = (s, changes) => ({
310
+ app: s.app,
311
+ appLabel: label(s.app).label,
312
+ section: s.spec.key,
313
+ label: s.spec.label,
314
+ scope: s.spec.scope,
315
+ changes: Object.entries(changes).map(([key, c]) => ({
316
+ key,
317
+ label: s.settings.find((x) => x.key === key)?.label ?? key,
318
+ from: c.from,
319
+ to: c.to,
320
+ })),
321
+ });
322
+ app.get('/api/v1/settings/app-settings/export', async (request) => request.env(async (env) => {
323
+ administrators(env);
324
+ return env.asSuperuser({ reason: 'app settings export' }, async (su) => {
325
+ const installation = {};
326
+ const companySettings = {};
327
+ for (const s of sections)
328
+ (s.spec.scope === 'company' ? companySettings : installation)[sectionKey(s)] =
329
+ await valuesOf(su, s, env.companyId);
330
+ await audit(env, {
331
+ kind: 'settings.exported',
332
+ summary: 'App settings exported.',
333
+ ip: request.ip,
334
+ });
335
+ return {
336
+ kosh: 'app-settings',
337
+ version: 1,
338
+ exportedAt: new Date().toISOString(),
339
+ company: await companyOf(su, env.companyId),
340
+ installation,
341
+ companySettings,
342
+ };
343
+ });
344
+ }, { write: true }));
345
+ app.get('/api/v1/settings/app-settings/:app/:section/history', async (request) => {
346
+ const section = sectionOf(request);
347
+ const key = request.query.key;
348
+ return request.env(async (env) => {
349
+ if (!mayView(env, section.app))
350
+ throw new HttpProblem(404, 'missing-record', 'Not found', 'There are no such settings.');
351
+ return env.asSuperuser({ reason: 'app settings history' }, async (su) => {
352
+ const found = await ref(su, 'audit.event').search({
353
+ where: {
354
+ kind: 'settings.changed',
355
+ ...(section.spec.scope === 'company' ? { company: env.companyId } : {}),
356
+ },
357
+ order: ['id desc'],
358
+ limit: 500,
359
+ });
360
+ const rows = found.length === 0
361
+ ? []
362
+ : (await found.load(['actor', 'createdAt', 'details'])).read([
363
+ 'actor',
364
+ 'createdAt',
365
+ 'details',
366
+ ]);
367
+ const events = rows
368
+ .map((r) => {
369
+ const details = (r['details'] ?? {});
370
+ const actor = r['actor'];
371
+ return {
372
+ actor: typeof actor === 'object' && actor !== null ? actor.id : actor,
373
+ actorName: typeof actor === 'object' && actor !== null ? (actor.name ?? null) : null,
374
+ at: r['createdAt'] instanceof Date
375
+ ? r['createdAt'].toISOString()
376
+ : String(r['createdAt']),
377
+ details,
378
+ };
379
+ })
380
+ .filter((e) => e.details.app === section.app &&
381
+ e.details.section === section.spec.key &&
382
+ (key === undefined || key in (e.details.changes ?? {})))
383
+ .slice(0, 50);
384
+ const unnamed = [
385
+ ...new Set(events
386
+ .filter((e) => e.actor !== null && e.actorName === null)
387
+ .map((e) => e.actor)),
388
+ ];
389
+ const users = ref(su, 'res.users').browse(unnamed);
390
+ const names = new Map(unnamed.length === 0
391
+ ? []
392
+ : (await users.load(['name']))
393
+ .read(['name'])
394
+ .map((r, i) => [users.ids[i], String(r['name'])]));
395
+ return {
396
+ events: events.map((e) => ({
397
+ at: e.at,
398
+ actor: e.actor === null
399
+ ? null
400
+ : { id: e.actor, name: e.actorName ?? names.get(e.actor) ?? String(e.actor) },
401
+ how: e.details.copiedFrom !== undefined
402
+ ? 'copied'
403
+ : e.details.imported === true
404
+ ? 'imported'
405
+ : 'changed',
406
+ changes: Object.entries(e.details.changes ?? {})
407
+ .filter(([k]) => key === undefined || k === key)
408
+ .map(([k, c]) => ({
409
+ key: k,
410
+ label: section.settings.find((x) => x.key === k)?.label ?? k,
411
+ from: c.from,
412
+ to: c.to,
413
+ })),
414
+ })),
415
+ };
416
+ });
417
+ }, { write: true });
418
+ });
419
+ app.post('/api/v1/settings/app-settings/copy', async (request) => {
420
+ const body = (request.body ?? {});
421
+ const from = Number(body['from']);
422
+ const to = Number(body['to']);
423
+ const preview = body['preview'] === true;
424
+ const chosen = Array.isArray(body['sections']) ? body['sections'].map(String) : null;
425
+ if (!Number.isInteger(from) || !Number.isInteger(to) || from === to)
426
+ throw new HttpProblem(422, 'invalid-copy', 'Copy settings', 'Give two different companies.');
427
+ return request.env(async (env) => {
428
+ administrators(env);
429
+ return env.asSuperuser({ reason: 'app settings copy' }, async (su) => {
430
+ for (const c of [from, to])
431
+ if (!(await memberOf(su, env.uid, c)))
432
+ throw new HttpProblem(403, 'access', 'Copy settings', `Company ${c} is not one of yours.`);
433
+ const source = await companyOf(su, from);
434
+ const target = await companyOf(su, to);
435
+ const picked = sections.filter((s) => s.spec.scope === 'company' && (chosen === null || chosen.includes(sectionKey(s))));
436
+ const out = [];
437
+ for (const s of picked) {
438
+ const a = await valuesOf(su, s, from);
439
+ const b = await valuesOf(su, s, to);
440
+ const changes = Object.fromEntries(s.settings
441
+ .filter((x) => (a[x.key] ?? null) !== (b[x.key] ?? null))
442
+ .map((x) => [x.key, { from: b[x.key] ?? null, to: a[x.key] ?? null }]));
443
+ if (Object.keys(changes).length === 0)
444
+ continue;
445
+ out.push(describeChanges(s, changes));
446
+ if (!preview)
447
+ await apply(env, su, s, changes, to, `copied from ${source.name}`, { copiedFrom: from }, request.ip);
448
+ }
449
+ if (!preview)
450
+ await su.flush();
451
+ return { preview, from: source, to: target, sections: out };
452
+ });
453
+ }, { write: true });
454
+ });
455
+ app.post('/api/v1/settings/app-settings/import', async (request) => {
456
+ const body = (request.body ?? {});
457
+ const file = body['file'];
458
+ const preview = body['preview'] === true;
459
+ if (typeof file !== 'object' || file === null || file['kosh'] !== 'app-settings')
460
+ throw new HttpProblem(422, 'invalid-file', 'Import settings', 'Not an app settings export.');
461
+ const given = [
462
+ ['installation', file['installation']],
463
+ ['company', file['companySettings']],
464
+ ];
465
+ return request.env(async (env) => {
466
+ administrators(env);
467
+ return env.asSuperuser({ reason: 'app settings import' }, async (su) => {
468
+ const skipped = [];
469
+ const plan = [];
470
+ for (const [scope, part] of given) {
471
+ if (typeof part !== 'object' || part === null)
472
+ continue;
473
+ for (const [k, values] of Object.entries(part)) {
474
+ const s = sections.find((x) => sectionKey(x) === k && x.spec.scope === scope);
475
+ if (s === undefined || typeof values !== 'object' || values === null) {
476
+ skipped.push(k);
477
+ continue;
478
+ }
479
+ const current = await valuesOf(su, s, env.companyId);
480
+ const changes = {};
481
+ for (const [key, value] of Object.entries(values)) {
482
+ const setting = s.settings.find((x) => x.key === key);
483
+ if (setting === undefined) {
484
+ skipped.push(`${k}.${key}`);
485
+ continue;
486
+ }
487
+ const checked = check(setting, value);
488
+ if (checked !== (current[key] ?? null))
489
+ changes[key] = { from: current[key] ?? null, to: checked };
490
+ }
491
+ if (Object.keys(changes).length > 0)
492
+ plan.push({ section: s, changes });
493
+ }
494
+ }
495
+ if (!preview) {
496
+ for (const { section, changes } of plan)
497
+ await apply(env, su, section, changes, env.companyId, 'imported', { imported: true }, request.ip);
498
+ await su.flush();
499
+ }
500
+ return {
501
+ preview,
502
+ skipped,
503
+ sections: plan.map(({ section, changes }) => describeChanges(section, changes)),
504
+ };
505
+ });
506
+ }, { write: true });
507
+ });
508
+ }
509
+ /** Database values as settings carry them: dates as YYYY-MM-DD, decimals as strings. */
510
+ function normalize(value) {
511
+ if (value === undefined || value === null)
512
+ return null;
513
+ if (value instanceof Date)
514
+ return value.toISOString().slice(0, 10);
515
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
516
+ return value;
517
+ return String(value);
518
+ }
@@ -10,7 +10,7 @@
10
10
  * - `GET /api/v1/settings/companies` every company with its currency and people, the currencies
11
11
  * - `POST /api/v1/settings/companies` `{ name, currency }`: a new company (administrators)
12
12
  */
13
- import { audit, HttpProblem, installDefaults, publicApiModels, } from '../server/index.js';
13
+ import { audit, HttpProblem, imageVersions, installDefaults, publicApiModels, } from '../server/index.js';
14
14
  const model = (env, name) => env.model(name);
15
15
  const find = async (env, name, where, keys) => {
16
16
  const set = await model(env, name).search({ where, order: ['id asc'] });
@@ -118,6 +118,7 @@ export function registerCompanyRoutes(context) {
118
118
  app.get('/api/v1/settings/companies', async (request) => guarded(request, async (su, current, env) => {
119
119
  const all = await find(su, 'res.company', {}, ['name', 'currency']);
120
120
  const currencies = await find(su, 'res.currency', {}, ['name']);
121
+ const logos = await imageVersions(su, 'company', all.map((c) => c['id']));
121
122
  const currencyName = new Map(currencies.map((c) => [c['id'], String(c['name'])]));
122
123
  return {
123
124
  canCreate: env.administrator,
@@ -129,6 +130,7 @@ export function registerCompanyRoutes(context) {
129
130
  where: { isService: { ne: true }, companies: { any: { id: c['id'] } } },
130
131
  })).length,
131
132
  current: c['id'] === current,
133
+ logo: logos[String(c['id'])] ?? null,
132
134
  // The person can switch to it: it is one of their companies.
133
135
  member: (await model(su, 'res.users').search({
134
136
  where: { id: env.uid, companies: { any: { id: c['id'] } } },
@@ -1,6 +1,7 @@
1
1
  import { type KoshModule } from '../modules-sdk/index.js';
2
2
  import { orm } from '../server/orm.js';
3
3
  import { type PeopleOptions } from './people.js';
4
+ export { registerAppSettingsRoutes } from './app-settings.js';
4
5
  export { type CompanyDetail, type CompanySettings, registerCompanyRoutes } from './company.js';
5
6
  export { type Assignment, INVITATION_DAYS, type PeopleOptions, registerPeopleRoutes, } from './people.js';
6
7
  export { type AppInfo, type PlanInfo, PROTECTED_APPS, registerAppRoutes } from './routes.js';
@@ -11,10 +11,12 @@ import { mailerFromEnv } from '../mail/index.js';
11
11
  import { defineModule } from '../modules-sdk/index.js';
12
12
  import { base, baseShutro } from '../server/index.js';
13
13
  import { orm } from '../server/orm.js';
14
+ import { registerAppSettingsRoutes } from './app-settings.js';
14
15
  import { registerCompanyRoutes } from './company.js';
15
16
  import { registerPeopleRoutes } from './people.js';
16
17
  import { registerAppRoutes } from './routes.js';
17
18
  import { registerSecurityRoutes } from './security.js';
19
+ export { registerAppSettingsRoutes } from './app-settings.js';
18
20
  export { registerCompanyRoutes } from './company.js';
19
21
  export { INVITATION_DAYS, registerPeopleRoutes, } from './people.js';
20
22
  export { PROTECTED_APPS, registerAppRoutes } from './routes.js';
@@ -76,6 +78,11 @@ export function settingsModule(options = {}) {
76
78
  actions: {
77
79
  'settings.action_overview': { type: 'client', page: 'settings.Overview', label: 'Settings' },
78
80
  'settings.action_apps': { type: 'client', page: 'settings.Apps', label: 'Apps' },
81
+ 'settings.action_app_settings': {
82
+ type: 'client',
83
+ page: 'settings.AppSettings',
84
+ label: 'App settings',
85
+ },
79
86
  'settings.action_company': { type: 'client', page: 'settings.Company', label: 'Company' },
80
87
  'settings.action_companies': {
81
88
  type: 'client',
@@ -164,11 +171,19 @@ export function settingsModule(options = {}) {
164
171
  action: 'settings.action_apps',
165
172
  sequence: 1,
166
173
  },
174
+ {
175
+ id: 'settings.app_settings',
176
+ parent: 'settings.group_apps',
177
+ label: 'App settings',
178
+ action: 'settings.action_app_settings',
179
+ sequence: 2,
180
+ },
167
181
  ],
168
182
  routes: (context) => {
169
183
  registerAppRoutes(context);
170
184
  registerCompanyRoutes(context);
171
185
  registerSecurityRoutes(context);
186
+ registerAppSettingsRoutes(context);
172
187
  registerPeopleRoutes(context, {
173
188
  baseUrl: options.baseUrl ?? process.env['KOSH_BASE_URL'] ?? 'http://localhost:5173',
174
189
  mailer: 'mailer' in options ? options.mailer : mailerFromEnv(),