shutro-erp 0.3.6 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (623) hide show
  1. package/dist/account_asset/.kosh/bindings.js +1 -1
  2. package/dist/account_asset/.kosh/schema.d.ts +530 -8
  3. package/dist/account_asset/i18n/bn.d.ts +2 -0
  4. package/dist/account_asset/i18n/bn.js +97 -0
  5. package/dist/account_asset/index.js +2 -0
  6. package/dist/account_asset/module.d.ts +4899 -1445
  7. package/dist/account_budget/.kosh/bindings.js +1 -1
  8. package/dist/account_budget/.kosh/schema.d.ts +1130 -56
  9. package/dist/account_budget/i18n/bn.d.ts +2 -0
  10. package/dist/account_budget/i18n/bn.js +63 -0
  11. package/dist/account_budget/index.js +14 -0
  12. package/dist/account_budget/module.d.ts +17958 -4532
  13. package/dist/account_budget/module.js +15 -0
  14. package/dist/account_portal/.kosh/bindings.js +1 -1
  15. package/dist/account_portal/.kosh/schema.d.ts +530 -8
  16. package/dist/account_portal/i18n/bn.d.ts +2 -0
  17. package/dist/account_portal/i18n/bn.js +6 -0
  18. package/dist/account_portal/index.js +2 -0
  19. package/dist/account_portal/module.d.ts +5167 -1693
  20. package/dist/accounting/.kosh/bindings.js +1 -1
  21. package/dist/accounting/.kosh/schema.d.ts +530 -8
  22. package/dist/accounting/closing.js +1 -0
  23. package/dist/accounting/fiscal.d.ts +578 -0
  24. package/dist/accounting/fiscal.js +401 -0
  25. package/dist/accounting/followups.d.ts +618 -0
  26. package/dist/accounting/followups.js +420 -0
  27. package/dist/accounting/i18n/bn.d.ts +2 -0
  28. package/dist/accounting/i18n/bn.js +425 -0
  29. package/dist/accounting/index.d.ts +2 -0
  30. package/dist/accounting/index.js +649 -2
  31. package/dist/accounting/module.d.ts +4998 -1451
  32. package/dist/accounting/module.js +124 -18
  33. package/dist/accounting/statements.d.ts +878 -0
  34. package/dist/accounting/statements.js +728 -0
  35. package/dist/accounting/web/BankReconcilePage.d.ts +9 -0
  36. package/dist/accounting/web/BankReconcilePage.js +138 -0
  37. package/dist/accounting/web/FollowupsPage.d.ts +8 -0
  38. package/dist/accounting/web/FollowupsPage.js +54 -0
  39. package/dist/accounting/web/PostingPreview.d.ts +8 -0
  40. package/dist/accounting/web/PostingPreview.js +26 -0
  41. package/dist/accounting/web/index.d.ts +3 -0
  42. package/dist/accounting/web/index.js +9 -0
  43. package/dist/accounting/web/messages.js +140 -0
  44. package/dist/approvals/.kosh/bindings.js +1 -1
  45. package/dist/approvals/.kosh/schema.d.ts +272 -4
  46. package/dist/approvals/demo.d.ts +7 -0
  47. package/dist/approvals/demo.js +41 -0
  48. package/dist/approvals/i18n/bn.d.ts +2 -0
  49. package/dist/approvals/i18n/bn.js +133 -0
  50. package/dist/approvals/index.d.ts +19 -1
  51. package/dist/approvals/index.js +477 -7
  52. package/dist/approvals/mails.d.ts +7 -2
  53. package/dist/approvals/mails.js +8 -3
  54. package/dist/approvals/module.d.ts +2930 -1
  55. package/dist/approvals/module.js +468 -7
  56. package/dist/approvals/web/Config.d.ts +12 -0
  57. package/dist/approvals/web/Config.js +60 -0
  58. package/dist/approvals/web/Matrix.d.ts +9 -0
  59. package/dist/approvals/web/Matrix.js +26 -0
  60. package/dist/approvals/web/index.d.ts +3 -1
  61. package/dist/approvals/web/index.js +8 -1
  62. package/dist/approvals/web/messages.js +92 -0
  63. package/dist/approvals_purchase/.kosh/bindings.js +15 -0
  64. package/dist/approvals_purchase/.kosh/schema.d.ts +1997 -0
  65. package/dist/approvals_purchase/i18n/bn.d.ts +2 -0
  66. package/dist/approvals_purchase/i18n/bn.js +5 -0
  67. package/dist/approvals_purchase/index.d.ts +3 -0
  68. package/dist/approvals_purchase/index.js +47 -0
  69. package/dist/approvals_purchase/module.d.ts +12670 -0
  70. package/dist/approvals_purchase/module.js +84 -0
  71. package/dist/attendance/.kosh/bindings.js +1 -1
  72. package/dist/attendance/.kosh/schema.d.ts +59 -1
  73. package/dist/attendance/i18n/bn.d.ts +2 -0
  74. package/dist/attendance/i18n/bn.js +147 -0
  75. package/dist/attendance/index.js +3 -1
  76. package/dist/attendance/module.d.ts +452 -8
  77. package/dist/automation/engine.js +1 -1
  78. package/dist/calendar/.kosh/bindings.js +1 -1
  79. package/dist/calendar/.kosh/schema.d.ts +2 -1
  80. package/dist/calendar/i18n/bn.d.ts +2 -0
  81. package/dist/calendar/i18n/bn.js +80 -0
  82. package/dist/calendar/index.js +77 -0
  83. package/dist/calendar/module.d.ts +26 -0
  84. package/dist/calendar/module.js +14 -4
  85. package/dist/cli/apps.js +25 -5
  86. package/dist/cli/init.js +14 -4
  87. package/dist/cli/templates.d.ts +1 -1
  88. package/dist/cli/templates.js +34 -5
  89. package/dist/contacts/.kosh/bindings.js +1 -1
  90. package/dist/contacts/.kosh/schema.d.ts +2 -1
  91. package/dist/contacts/i18n/bn.d.ts +2 -0
  92. package/dist/contacts/i18n/bn.js +67 -0
  93. package/dist/contacts/index.js +3 -0
  94. package/dist/contacts/module.d.ts +42 -0
  95. package/dist/contacts/module.js +6 -1
  96. package/dist/crm/.kosh/bindings.js +1 -1
  97. package/dist/crm/.kosh/schema.d.ts +2 -1
  98. package/dist/crm/i18n/bn.d.ts +2 -0
  99. package/dist/crm/i18n/bn.js +80 -0
  100. package/dist/crm/index.js +22 -0
  101. package/dist/crm/module.d.ts +20 -0
  102. package/dist/crm/module.js +4 -2
  103. package/dist/documents/.kosh/bindings.js +1 -1
  104. package/dist/documents/.kosh/schema.d.ts +2 -1
  105. package/dist/documents/i18n/bn.d.ts +2 -0
  106. package/dist/documents/i18n/bn.js +83 -0
  107. package/dist/documents/index.js +128 -1
  108. package/dist/documents/module.d.ts +20 -0
  109. package/dist/documents/module.js +1 -0
  110. package/dist/event/.kosh/bindings.js +1 -1
  111. package/dist/event/.kosh/schema.d.ts +2 -1
  112. package/dist/event/i18n/bn.d.ts +2 -0
  113. package/dist/event/i18n/bn.js +101 -0
  114. package/dist/event/index.js +66 -0
  115. package/dist/event/module.d.ts +20 -0
  116. package/dist/event/module.js +8 -1
  117. package/dist/event_account/.kosh/bindings.js +1 -1
  118. package/dist/event_account/.kosh/schema.d.ts +530 -8
  119. package/dist/event_account/i18n/bn.d.ts +2 -0
  120. package/dist/event_account/i18n/bn.js +5 -0
  121. package/dist/event_account/index.js +2 -0
  122. package/dist/event_account/module.d.ts +4905 -1431
  123. package/dist/expense/.kosh/bindings.js +1 -1
  124. package/dist/expense/.kosh/schema.d.ts +323 -2
  125. package/dist/expense/i18n/bn.d.ts +2 -0
  126. package/dist/expense/i18n/bn.js +155 -0
  127. package/dist/expense/index.js +8 -1
  128. package/dist/expense/module.d.ts +2715 -694
  129. package/dist/expense/module.js +3 -1
  130. package/dist/expense_account/.kosh/bindings.js +1 -1
  131. package/dist/expense_account/.kosh/schema.d.ts +587 -8
  132. package/dist/expense_account/i18n/bn.d.ts +2 -0
  133. package/dist/expense_account/i18n/bn.js +15 -0
  134. package/dist/expense_account/index.js +2 -0
  135. package/dist/expense_account/module.d.ts +8094 -2619
  136. package/dist/helpdesk/.kosh/bindings.js +1 -1
  137. package/dist/helpdesk/.kosh/schema.d.ts +2 -1
  138. package/dist/helpdesk/i18n/bn.d.ts +2 -0
  139. package/dist/helpdesk/i18n/bn.js +127 -0
  140. package/dist/helpdesk/index.js +2 -0
  141. package/dist/helpdesk/module.d.ts +20 -0
  142. package/dist/hr/.kosh/bindings.js +1 -1
  143. package/dist/hr/.kosh/schema.d.ts +59 -1
  144. package/dist/hr/demo.js +11 -1
  145. package/dist/hr/i18n/bn.d.ts +2 -0
  146. package/dist/hr/i18n/bn.js +126 -0
  147. package/dist/hr/index.d.ts +1 -1
  148. package/dist/hr/index.js +158 -12
  149. package/dist/hr/module.d.ts +932 -59
  150. package/dist/hr/module.js +114 -17
  151. package/dist/{mrp → indent}/.kosh/bindings.js +2 -2
  152. package/dist/indent/.kosh/schema.d.ts +2084 -0
  153. package/dist/indent/demo.d.ts +7 -0
  154. package/dist/indent/demo.js +48 -0
  155. package/dist/indent/i18n/bn.d.ts +2 -0
  156. package/dist/indent/i18n/bn.js +143 -0
  157. package/dist/indent/index.d.ts +3 -0
  158. package/dist/indent/index.js +450 -0
  159. package/dist/{mrp → indent}/module.d.ts +7164 -3075
  160. package/dist/indent/module.js +792 -0
  161. package/dist/indent_purchase/.kosh/bindings.d.ts +22 -0
  162. package/dist/indent_purchase/.kosh/bindings.js +15 -0
  163. package/dist/indent_purchase/.kosh/schema.d.ts +2710 -0
  164. package/dist/indent_purchase/i18n/bn.d.ts +2 -0
  165. package/dist/indent_purchase/i18n/bn.js +13 -0
  166. package/dist/indent_purchase/index.d.ts +3 -0
  167. package/dist/indent_purchase/index.js +61 -0
  168. package/dist/indent_purchase/module.d.ts +27950 -0
  169. package/dist/indent_purchase/module.js +163 -0
  170. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  171. package/dist/l10n_bd/.kosh/schema.d.ts +530 -8
  172. package/dist/l10n_bd/i18n/bn.d.ts +2 -0
  173. package/dist/l10n_bd/i18n/bn.js +69 -0
  174. package/dist/l10n_bd/index.js +2 -0
  175. package/dist/l10n_bd/module.d.ts +5167 -1693
  176. package/dist/l10n_bd_payroll/.kosh/bindings.js +1 -1
  177. package/dist/l10n_bd_payroll/.kosh/schema.d.ts +587 -8
  178. package/dist/l10n_bd_payroll/i18n/bn.d.ts +2 -0
  179. package/dist/l10n_bd_payroll/i18n/bn.js +4 -0
  180. package/dist/l10n_bd_payroll/index.js +2 -0
  181. package/dist/l10n_bd_payroll/module.d.ts +5910 -1548
  182. package/dist/l10n_bd_pos/.kosh/bindings.d.ts +22 -0
  183. package/dist/{mrp_account → l10n_bd_pos}/.kosh/bindings.js +2 -2
  184. package/dist/{mrp_account → l10n_bd_pos}/.kosh/schema.d.ts +693 -651
  185. package/dist/l10n_bd_pos/i18n/bn.d.ts +2 -0
  186. package/dist/l10n_bd_pos/i18n/bn.js +32 -0
  187. package/dist/l10n_bd_pos/index.d.ts +3 -0
  188. package/dist/l10n_bd_pos/index.js +111 -0
  189. package/dist/{mrp_account → l10n_bd_pos}/module.d.ts +7402 -7647
  190. package/dist/l10n_bd_pos/module.js +43 -0
  191. package/dist/l10n_in/.kosh/bindings.js +1 -1
  192. package/dist/l10n_in/.kosh/schema.d.ts +530 -8
  193. package/dist/l10n_in/i18n/bn.d.ts +2 -0
  194. package/dist/l10n_in/i18n/bn.js +95 -0
  195. package/dist/l10n_in/index.js +2 -0
  196. package/dist/l10n_in/module.d.ts +6703 -1652
  197. package/dist/l10n_in/module.js +25 -5
  198. package/dist/l10n_in/web/GstSettings.js +2 -2
  199. package/dist/mail/inbound/handle.js +2 -4
  200. package/dist/mail/inbound/models.js +5 -3
  201. package/dist/mail/index.d.ts +12 -1
  202. package/dist/mail/index.js +139 -2
  203. package/dist/mail/send.d.ts +56 -0
  204. package/dist/mail/send.js +394 -0
  205. package/dist/mail/web/SendButton.d.ts +9 -0
  206. package/dist/mail/web/SendButton.js +71 -0
  207. package/dist/mail/web/SendSelected.d.ts +8 -0
  208. package/dist/mail/web/SendSelected.js +33 -0
  209. package/dist/mail/web/index.d.ts +2 -0
  210. package/dist/mail/web/index.js +6 -0
  211. package/dist/mail/web/messages.js +36 -0
  212. package/dist/maintenance/.kosh/bindings.js +1 -1
  213. package/dist/maintenance/.kosh/schema.d.ts +2 -1
  214. package/dist/maintenance/i18n/bn.d.ts +2 -0
  215. package/dist/maintenance/i18n/bn.js +132 -0
  216. package/dist/maintenance/index.js +85 -0
  217. package/dist/maintenance/module.d.ts +25 -0
  218. package/dist/maintenance/module.js +27 -9
  219. package/dist/maintenance_stock/.kosh/bindings.js +1 -1
  220. package/dist/maintenance_stock/.kosh/schema.d.ts +298 -2
  221. package/dist/maintenance_stock/i18n/bn.d.ts +2 -0
  222. package/dist/maintenance_stock/i18n/bn.js +21 -0
  223. package/dist/maintenance_stock/index.js +2 -0
  224. package/dist/maintenance_stock/module.d.ts +5302 -1256
  225. package/dist/maintenance_stock/module.js +1 -0
  226. package/dist/modules-sdk/apps.d.ts +10 -0
  227. package/dist/modules-sdk/apps.js +32 -0
  228. package/dist/modules-sdk/index.d.ts +1 -1
  229. package/dist/modules-sdk/manifest.d.ts +68 -2
  230. package/dist/payment/.kosh/bindings.js +1 -1
  231. package/dist/payment/.kosh/schema.d.ts +530 -8
  232. package/dist/payment/i18n/bn.d.ts +2 -0
  233. package/dist/payment/i18n/bn.js +59 -0
  234. package/dist/payment/index.js +85 -0
  235. package/dist/payment/module.d.ts +5134 -1660
  236. package/dist/payment/module.js +17 -3
  237. package/dist/payment/settle.js +1 -0
  238. package/dist/payroll/.kosh/bindings.js +1 -1
  239. package/dist/payroll/.kosh/schema.d.ts +59 -1
  240. package/dist/payroll/i18n/bn.d.ts +2 -0
  241. package/dist/payroll/i18n/bn.js +206 -0
  242. package/dist/payroll/index.js +2 -0
  243. package/dist/payroll/module.d.ts +936 -48
  244. package/dist/payroll_account/.kosh/bindings.js +1 -1
  245. package/dist/payroll_account/.kosh/schema.d.ts +587 -8
  246. package/dist/payroll_account/i18n/bn.d.ts +2 -0
  247. package/dist/payroll_account/i18n/bn.js +10 -0
  248. package/dist/payroll_account/index.js +2 -0
  249. package/dist/payroll_account/module.d.ts +7534 -3192
  250. package/dist/permissions/approvals.d.ts +97 -4
  251. package/dist/permissions/approvals.js +780 -31
  252. package/dist/permissions/index.d.ts +1 -1
  253. package/dist/permissions/index.js +2 -2
  254. package/dist/permissions/models.js +177 -5
  255. package/dist/permissions/routes.d.ts +3 -0
  256. package/dist/permissions/routes.js +36 -2
  257. package/dist/permissions/web/ApprovalPanel.js +43 -5
  258. package/dist/permissions/web/messages.js +52 -0
  259. package/dist/portal/.kosh/bindings.js +1 -1
  260. package/dist/portal/.kosh/schema.d.ts +2 -1
  261. package/dist/portal/documents.d.ts +3 -0
  262. package/dist/portal/documents.js +21 -1
  263. package/dist/portal/i18n/bn.d.ts +2 -0
  264. package/dist/portal/i18n/bn.js +33 -0
  265. package/dist/portal/index.js +2 -0
  266. package/dist/portal/module.d.ts +20 -0
  267. package/dist/portal/routes-documents.js +76 -0
  268. package/dist/pos/.kosh/bindings.js +1 -1
  269. package/dist/pos/.kosh/schema.d.ts +531 -8
  270. package/dist/pos/i18n/bn.d.ts +2 -0
  271. package/dist/pos/i18n/bn.js +133 -0
  272. package/dist/pos/index.js +83 -1
  273. package/dist/pos/module.d.ts +6888 -1777
  274. package/dist/pos/module.js +49 -5
  275. package/dist/pos/routes.js +80 -7
  276. package/dist/pos/web/Register.js +2 -5
  277. package/dist/pos_stock/.kosh/bindings.js +1 -1
  278. package/dist/pos_stock/.kosh/schema.d.ts +626 -9
  279. package/dist/pos_stock/i18n/bn.d.ts +2 -0
  280. package/dist/pos_stock/i18n/bn.js +6 -0
  281. package/dist/pos_stock/index.js +2 -0
  282. package/dist/pos_stock/module.d.ts +10798 -3276
  283. package/dist/product/.kosh/bindings.js +1 -1
  284. package/dist/product/.kosh/schema.d.ts +202 -1
  285. package/dist/product/i18n/bn.d.ts +2 -0
  286. package/dist/product/i18n/bn.js +175 -0
  287. package/dist/product/index.d.ts +1 -0
  288. package/dist/product/index.js +288 -1
  289. package/dist/product/module.d.ts +1725 -0
  290. package/dist/product/module.js +66 -6
  291. package/dist/product/pricelist.d.ts +575 -0
  292. package/dist/product/pricelist.js +257 -0
  293. package/dist/product/variants.d.ts +908 -0
  294. package/dist/product/variants.js +251 -0
  295. package/dist/production/.kosh/bindings.d.ts +22 -0
  296. package/dist/production/.kosh/bindings.js +15 -0
  297. package/dist/production/.kosh/schema.d.ts +2345 -0
  298. package/dist/{mrp → production}/demo.d.ts +1 -1
  299. package/dist/{mrp → production}/demo.js +5 -5
  300. package/dist/production/depth.d.ts +790 -0
  301. package/dist/production/depth.js +262 -0
  302. package/dist/production/i18n/bn.d.ts +2 -0
  303. package/dist/production/i18n/bn.js +350 -0
  304. package/dist/production/index.d.ts +6 -0
  305. package/dist/production/index.js +1256 -0
  306. package/dist/production/module.d.ts +17327 -0
  307. package/dist/production/module.js +1333 -0
  308. package/dist/production/plan.d.ts +921 -0
  309. package/dist/production/plan.js +511 -0
  310. package/dist/production/schedule.d.ts +656 -0
  311. package/dist/production/schedule.js +309 -0
  312. package/dist/production_account/.kosh/bindings.d.ts +22 -0
  313. package/dist/production_account/.kosh/bindings.js +15 -0
  314. package/dist/production_account/.kosh/schema.d.ts +3496 -0
  315. package/dist/production_account/i18n/bn.d.ts +2 -0
  316. package/dist/production_account/i18n/bn.js +93 -0
  317. package/dist/production_account/index.d.ts +4 -0
  318. package/dist/production_account/index.js +246 -0
  319. package/dist/production_account/module.d.ts +1192 -0
  320. package/dist/{mrp_account → production_account}/module.js +72 -18
  321. package/dist/production_account/overhead.d.ts +1195 -0
  322. package/dist/production_account/overhead.js +554 -0
  323. package/dist/project/.kosh/bindings.js +1 -1
  324. package/dist/project/.kosh/schema.d.ts +65 -2
  325. package/dist/project/i18n/bn.d.ts +2 -0
  326. package/dist/project/i18n/bn.js +383 -0
  327. package/dist/project/index.js +123 -0
  328. package/dist/project/module.d.ts +20 -0
  329. package/dist/project/module.js +73 -14
  330. package/dist/project_account/.kosh/bindings.js +1 -1
  331. package/dist/project_account/.kosh/schema.d.ts +530 -8
  332. package/dist/project_account/i18n/bn.d.ts +2 -0
  333. package/dist/project_account/i18n/bn.js +6 -0
  334. package/dist/project_account/index.js +2 -0
  335. package/dist/project_account/module.d.ts +4926 -1452
  336. package/dist/project_portal/.kosh/bindings.js +1 -1
  337. package/dist/project_portal/.kosh/schema.d.ts +65 -2
  338. package/dist/project_portal/i18n/bn.d.ts +2 -0
  339. package/dist/project_portal/i18n/bn.js +6 -0
  340. package/dist/project_portal/index.js +2 -0
  341. package/dist/project_portal/module.d.ts +40 -0
  342. package/dist/project_purchase/.kosh/bindings.js +1 -1
  343. package/dist/project_purchase/.kosh/schema.d.ts +1126 -53
  344. package/dist/project_purchase/i18n/bn.d.ts +2 -0
  345. package/dist/project_purchase/i18n/bn.js +4 -0
  346. package/dist/project_purchase/index.js +2 -0
  347. package/dist/project_purchase/module.d.ts +17912 -4446
  348. package/dist/purchase/.kosh/bindings.js +1 -1
  349. package/dist/purchase/.kosh/schema.d.ts +813 -3
  350. package/dist/purchase/comparison.d.ts +993 -0
  351. package/dist/purchase/comparison.js +415 -0
  352. package/dist/purchase/extras.d.ts +1509 -0
  353. package/dist/purchase/extras.js +445 -0
  354. package/dist/purchase/i18n/bn.d.ts +2 -0
  355. package/dist/purchase/i18n/bn.js +413 -0
  356. package/dist/purchase/index.d.ts +5 -0
  357. package/dist/purchase/index.js +1280 -4
  358. package/dist/purchase/module.d.ts +7139 -398
  359. package/dist/purchase/module.js +295 -11
  360. package/dist/purchase/request.d.ts +696 -0
  361. package/dist/purchase/request.js +446 -0
  362. package/dist/purchase/tender.d.ts +1208 -0
  363. package/dist/purchase/tender.js +507 -0
  364. package/dist/purchase/vendorprice.d.ts +340 -0
  365. package/dist/purchase/vendorprice.js +153 -0
  366. package/dist/purchase_account/.kosh/bindings.js +1 -1
  367. package/dist/purchase_account/.kosh/schema.d.ts +1130 -57
  368. package/dist/purchase_account/i18n/bn.d.ts +2 -0
  369. package/dist/purchase_account/i18n/bn.js +22 -0
  370. package/dist/purchase_account/index.js +19 -0
  371. package/dist/purchase_account/module.d.ts +12276 -2247
  372. package/dist/purchase_account/module.js +241 -9
  373. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  374. package/dist/purchase_portal/.kosh/schema.d.ts +813 -3
  375. package/dist/purchase_portal/i18n/bn.d.ts +2 -0
  376. package/dist/purchase_portal/i18n/bn.js +5 -0
  377. package/dist/purchase_portal/index.js +2 -0
  378. package/dist/purchase_portal/module.d.ts +6849 -353
  379. package/dist/purchase_stock/.kosh/bindings.js +1 -1
  380. package/dist/purchase_stock/.kosh/schema.d.ts +910 -4
  381. package/dist/purchase_stock/i18n/bn.d.ts +2 -0
  382. package/dist/purchase_stock/i18n/bn.js +19 -0
  383. package/dist/purchase_stock/index.d.ts +1 -1
  384. package/dist/purchase_stock/index.js +29 -0
  385. package/dist/purchase_stock/module.d.ts +12410 -1864
  386. package/dist/purchase_stock/module.js +81 -9
  387. package/dist/purchase_stock/scorecard.d.ts +22 -0
  388. package/dist/purchase_stock/scorecard.js +77 -0
  389. package/dist/purchase_stock/web/Scorecard.d.ts +7 -0
  390. package/dist/purchase_stock/web/Scorecard.js +22 -0
  391. package/dist/purchase_stock/web/index.d.ts +11 -0
  392. package/dist/purchase_stock/web/index.js +16 -0
  393. package/dist/purchase_stock/web/messages.d.ts +2 -0
  394. package/dist/purchase_stock/web/messages.js +27 -0
  395. package/dist/purchase_stock_account/.kosh/bindings.js +1 -1
  396. package/dist/purchase_stock_account/.kosh/schema.d.ts +1286 -58
  397. package/dist/purchase_stock_account/i18n/bn.d.ts +2 -0
  398. package/dist/purchase_stock_account/i18n/bn.js +8 -0
  399. package/dist/purchase_stock_account/index.js +18 -0
  400. package/dist/purchase_stock_account/module.d.ts +7 -37995
  401. package/dist/purchase_stock_account/module.js +24 -3
  402. package/dist/recruitment/.kosh/bindings.js +1 -1
  403. package/dist/recruitment/.kosh/schema.d.ts +59 -1
  404. package/dist/recruitment/i18n/bn.d.ts +2 -0
  405. package/dist/recruitment/i18n/bn.js +93 -0
  406. package/dist/recruitment/index.js +64 -0
  407. package/dist/recruitment/module.d.ts +458 -8
  408. package/dist/recruitment/module.js +11 -3
  409. package/dist/reporting/documents.d.ts +5 -0
  410. package/dist/reporting/documents.js +11 -1
  411. package/dist/reporting/index.d.ts +1 -1
  412. package/dist/reporting/index.js +1 -1
  413. package/dist/reporting/web/CalendarView.d.ts +18 -5
  414. package/dist/reporting/web/CalendarView.js +163 -29
  415. package/dist/reporting/web/messages.js +18 -0
  416. package/dist/sale_account/.kosh/bindings.js +1 -1
  417. package/dist/sale_account/.kosh/schema.d.ts +642 -16
  418. package/dist/sale_account/i18n/bn.d.ts +2 -0
  419. package/dist/sale_account/i18n/bn.js +48 -0
  420. package/dist/sale_account/index.d.ts +1 -1
  421. package/dist/sale_account/index.js +102 -4
  422. package/dist/sale_account/module.d.ts +8254 -2212
  423. package/dist/sale_account/module.js +535 -46
  424. package/dist/sale_crm/.kosh/bindings.d.ts +22 -0
  425. package/dist/sale_crm/.kosh/bindings.js +15 -0
  426. package/dist/{mrp → sale_crm}/.kosh/schema.d.ts +389 -567
  427. package/dist/sale_crm/i18n/bn.d.ts +2 -0
  428. package/dist/sale_crm/i18n/bn.js +9 -0
  429. package/dist/sale_crm/index.d.ts +7 -0
  430. package/dist/sale_crm/index.js +104 -0
  431. package/dist/sale_crm/module.d.ts +5609 -0
  432. package/dist/sale_crm/module.js +73 -0
  433. package/dist/sale_portal/.kosh/bindings.js +1 -1
  434. package/dist/sale_portal/.kosh/schema.d.ts +291 -4
  435. package/dist/sale_portal/i18n/bn.d.ts +2 -0
  436. package/dist/sale_portal/i18n/bn.js +7 -0
  437. package/dist/sale_portal/index.js +10 -0
  438. package/dist/sale_portal/module.d.ts +2564 -294
  439. package/dist/sale_project/.kosh/bindings.js +1 -1
  440. package/dist/sale_project/.kosh/schema.d.ts +354 -5
  441. package/dist/sale_project/i18n/bn.d.ts +2 -0
  442. package/dist/sale_project/i18n/bn.js +12 -0
  443. package/dist/sale_project/index.js +2 -0
  444. package/dist/sale_project/module.d.ts +6249 -2402
  445. package/dist/sale_stock/.kosh/bindings.js +1 -1
  446. package/dist/sale_stock/.kosh/schema.d.ts +387 -5
  447. package/dist/sale_stock/i18n/bn.d.ts +2 -0
  448. package/dist/sale_stock/i18n/bn.js +15 -0
  449. package/dist/sale_stock/index.js +2 -0
  450. package/dist/sale_stock/module.d.ts +8057 -1759
  451. package/dist/sale_stock/module.js +20 -3
  452. package/dist/sales/.kosh/bindings.js +1 -1
  453. package/dist/sales/.kosh/schema.d.ts +291 -4
  454. package/dist/sales/i18n/bn.d.ts +2 -0
  455. package/dist/sales/i18n/bn.js +128 -0
  456. package/dist/sales/index.js +120 -5
  457. package/dist/sales/module.d.ts +3567 -633
  458. package/dist/sales/module.js +306 -11
  459. package/dist/server/api/duplicate.d.ts +14 -0
  460. package/dist/server/api/duplicate.js +75 -0
  461. package/dist/server/api/explain.d.ts +70 -0
  462. package/dist/server/api/explain.js +111 -0
  463. package/dist/server/api/metadata.d.ts +2 -0
  464. package/dist/server/api/metadata.js +9 -0
  465. package/dist/server/api/openapi.js +15 -0
  466. package/dist/server/api/rest.d.ts +1 -0
  467. package/dist/server/api/rest.js +76 -12
  468. package/dist/server/api/views.js +1 -1
  469. package/dist/server/apps.d.ts +16 -1
  470. package/dist/server/apps.js +164 -2
  471. package/dist/server/auth/base.js +2 -0
  472. package/dist/server/auth/service.d.ts +8 -0
  473. package/dist/server/auth/service.js +19 -0
  474. package/dist/server/events.d.ts +13 -0
  475. package/dist/server/events.js +22 -0
  476. package/dist/server/i18n.d.ts +27 -0
  477. package/dist/server/i18n.js +143 -0
  478. package/dist/server/index.d.ts +2 -0
  479. package/dist/server/index.js +2 -0
  480. package/dist/server/server.js +6 -0
  481. package/dist/settings/company.d.ts +3 -1
  482. package/dist/settings/company.js +26 -5
  483. package/dist/settings/routes.d.ts +10 -0
  484. package/dist/settings/routes.js +19 -3
  485. package/dist/settings/web/AppsPage.d.ts +3 -2
  486. package/dist/settings/web/AppsPage.js +26 -8
  487. package/dist/settings/web/CompaniesPage.js +91 -6
  488. package/dist/settings/web/messages.js +64 -0
  489. package/dist/sign/.kosh/bindings.js +1 -1
  490. package/dist/sign/.kosh/schema.d.ts +2 -1
  491. package/dist/sign/i18n/bn.d.ts +2 -0
  492. package/dist/sign/i18n/bn.js +97 -0
  493. package/dist/sign/index.d.ts +15 -0
  494. package/dist/sign/index.js +129 -1
  495. package/dist/sign/module.d.ts +40 -0
  496. package/dist/sign/module.js +11 -2
  497. package/dist/stock/.kosh/bindings.js +1 -1
  498. package/dist/stock/.kosh/schema.d.ts +298 -2
  499. package/dist/stock/i18n/bn.d.ts +2 -0
  500. package/dist/stock/i18n/bn.js +311 -0
  501. package/dist/stock/index.d.ts +3 -1
  502. package/dist/stock/index.js +442 -3
  503. package/dist/stock/ledger.d.ts +78 -0
  504. package/dist/stock/ledger.js +582 -0
  505. package/dist/stock/module.d.ts +5280 -2044
  506. package/dist/stock/module.js +222 -23
  507. package/dist/stock/replenish.js +16 -1
  508. package/dist/stock/returns.d.ts +157 -0
  509. package/dist/stock/returns.js +163 -0
  510. package/dist/stock/web/Ledger.d.ts +9 -0
  511. package/dist/stock/web/Ledger.js +108 -0
  512. package/dist/stock/web/Operations.d.ts +8 -0
  513. package/dist/stock/web/Operations.js +33 -0
  514. package/dist/stock/web/ReturnButton.d.ts +7 -0
  515. package/dist/stock/web/ReturnButton.js +62 -0
  516. package/dist/stock/web/index.d.ts +3 -0
  517. package/dist/stock/web/index.js +9 -0
  518. package/dist/stock/web/messages.js +154 -0
  519. package/dist/stock_account/.kosh/bindings.js +1 -1
  520. package/dist/stock_account/.kosh/schema.d.ts +682 -9
  521. package/dist/stock_account/i18n/bn.d.ts +2 -0
  522. package/dist/stock_account/i18n/bn.js +73 -0
  523. package/dist/stock_account/index.d.ts +1 -0
  524. package/dist/stock_account/index.js +131 -3
  525. package/dist/stock_account/landed.d.ts +429 -0
  526. package/dist/stock_account/landed.js +335 -0
  527. package/dist/stock_account/module.d.ts +11247 -3363
  528. package/dist/stock_account/module.js +135 -6
  529. package/dist/stock_account/web/LandedPreview.d.ts +7 -0
  530. package/dist/stock_account/web/LandedPreview.js +23 -0
  531. package/dist/stock_account/web/index.d.ts +11 -0
  532. package/dist/stock_account/web/index.js +16 -0
  533. package/dist/stock_account/web/messages.d.ts +2 -0
  534. package/dist/stock_account/web/messages.js +27 -0
  535. package/dist/todo/i18n/bn.d.ts +2 -0
  536. package/dist/todo/i18n/bn.js +66 -0
  537. package/dist/todo/index.js +59 -0
  538. package/dist/todo/module.d.ts +5 -0
  539. package/dist/todo/module.js +5 -3
  540. package/dist/trade_finance/.kosh/bindings.js +1 -1
  541. package/dist/trade_finance/.kosh/schema.d.ts +2 -1
  542. package/dist/trade_finance/i18n/bn.d.ts +2 -0
  543. package/dist/trade_finance/i18n/bn.js +162 -0
  544. package/dist/trade_finance/index.js +93 -0
  545. package/dist/trade_finance/module.d.ts +26 -0
  546. package/dist/trade_finance/module.js +27 -8
  547. package/dist/trade_finance_account/.kosh/bindings.js +1 -1
  548. package/dist/trade_finance_account/.kosh/schema.d.ts +530 -8
  549. package/dist/trade_finance_account/i18n/bn.d.ts +2 -0
  550. package/dist/trade_finance_account/i18n/bn.js +27 -0
  551. package/dist/trade_finance_account/index.js +2 -0
  552. package/dist/trade_finance_account/module.d.ts +4906 -1432
  553. package/dist/trade_finance_account/module.js +1 -0
  554. package/dist/trade_finance_purchase/.kosh/bindings.js +1 -1
  555. package/dist/trade_finance_purchase/.kosh/schema.d.ts +813 -3
  556. package/dist/trade_finance_purchase/i18n/bn.d.ts +2 -0
  557. package/dist/trade_finance_purchase/i18n/bn.js +6 -0
  558. package/dist/trade_finance_purchase/index.js +2 -0
  559. package/dist/trade_finance_purchase/module.d.ts +6909 -413
  560. package/dist/trade_finance_sale/.kosh/bindings.js +1 -1
  561. package/dist/trade_finance_sale/.kosh/schema.d.ts +291 -4
  562. package/dist/trade_finance_sale/i18n/bn.d.ts +2 -0
  563. package/dist/trade_finance_sale/i18n/bn.js +6 -0
  564. package/dist/trade_finance_sale/index.js +2 -0
  565. package/dist/trade_finance_sale/module.d.ts +2601 -331
  566. package/dist/ui/api.d.ts +2 -0
  567. package/dist/ui/app.d.ts +9 -0
  568. package/dist/ui/app.js +24 -4
  569. package/dist/ui/components/AppIcon.js +2 -2
  570. package/dist/ui/components/Explain.d.ts +69 -0
  571. package/dist/ui/components/Explain.js +62 -0
  572. package/dist/ui/context.d.ts +8 -1
  573. package/dist/ui/context.js +34 -1
  574. package/dist/ui/i18n/messages.d.ts +36 -0
  575. package/dist/ui/i18n/messages.js +72 -0
  576. package/dist/ui/index.d.ts +2 -1
  577. package/dist/ui/index.js +1 -0
  578. package/dist/ui/registry.d.ts +14 -0
  579. package/dist/ui/registry.js +10 -0
  580. package/dist/ui/views/FormView.d.ts +6 -0
  581. package/dist/ui/views/FormView.js +47 -10
  582. package/dist/ui/views/ListView.d.ts +8 -0
  583. package/dist/ui/views/ListView.js +10 -3
  584. package/dist/ui/views/StatsRow.js +2 -1
  585. package/dist/views/extend.js +12 -0
  586. package/dist/views/types.d.ts +13 -2
  587. package/dist/views/validate.js +8 -1
  588. package/dist/website/.kosh/bindings.js +1 -1
  589. package/dist/website/.kosh/schema.d.ts +2 -1
  590. package/dist/website/i18n/bn.d.ts +2 -0
  591. package/dist/website/i18n/bn.js +113 -0
  592. package/dist/website/index.js +36 -0
  593. package/dist/website/module.d.ts +20 -0
  594. package/dist/website/module.js +29 -5
  595. package/dist/website_crm/.kosh/bindings.js +1 -1
  596. package/dist/website_crm/.kosh/schema.d.ts +2 -1
  597. package/dist/website_crm/i18n/bn.d.ts +2 -0
  598. package/dist/website_crm/i18n/bn.js +5 -0
  599. package/dist/website_crm/index.js +2 -0
  600. package/dist/website_crm/module.d.ts +40 -0
  601. package/dist/website_sale/.kosh/bindings.js +1 -1
  602. package/dist/website_sale/.kosh/schema.d.ts +642 -17
  603. package/dist/website_sale/i18n/bn.d.ts +2 -0
  604. package/dist/website_sale/i18n/bn.js +132 -0
  605. package/dist/website_sale/index.js +83 -1
  606. package/dist/website_sale/module.d.ts +18705 -5550
  607. package/dist/website_sale/module.js +32 -10
  608. package/dist/website_sale/shop.js +22 -9
  609. package/dist/website_sale_stock/.kosh/bindings.js +1 -1
  610. package/dist/website_sale_stock/.kosh/schema.d.ts +737 -18
  611. package/dist/website_sale_stock/i18n/bn.d.ts +2 -0
  612. package/dist/website_sale_stock/i18n/bn.js +4 -0
  613. package/dist/website_sale_stock/index.js +2 -0
  614. package/dist/website_sale_stock/module.d.ts +10 -24997
  615. package/dist/website_sale_stock/module.js +7 -3
  616. package/package.json +36 -8
  617. package/dist/mrp/index.d.ts +0 -3
  618. package/dist/mrp/index.js +0 -393
  619. package/dist/mrp/module.js +0 -757
  620. package/dist/mrp_account/index.d.ts +0 -3
  621. package/dist/mrp_account/index.js +0 -22
  622. /package/dist/{mrp → approvals_purchase}/.kosh/bindings.d.ts +0 -0
  623. /package/dist/{mrp_account → indent}/.kosh/bindings.d.ts +0 -0
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Sending documents (RFC 0029 §3.3). Modules declare email templates (`mailTemplates`) and the documents sent from
3
+ * their form (`sendable`: the template and the partner field). The Send dialog asks for the composition — the
4
+ * partner's address, the template's subject and body filled in the partner's language, the PDF named — and sends
5
+ * it, edited or not: the PDF is rendered and stored on the record, the message posted on its chatter with it, and
6
+ * the email queued in `mail.outgoing` and sent by an outbox subscriber, so a failed send never undoes the record.
7
+ */
8
+ import { chatterContext, postMessage } from '../messaging/index.js';
9
+ import { documentFields, fillText, loadDocumentData, renderDocument, } from '../reporting/index.js';
10
+ import { HttpProblem, publicApiModels } from '../server/index.js';
11
+ import { orm } from '../server/orm.js';
12
+ import { openAttachment, saveAttachment, storageOf } from '../storage/index.js';
13
+ import { escapeHtml, layout } from './index.js';
14
+ const { defineModel, f } = orm;
15
+ /** An email template (RFC 0029 §3.3): installed from manifests by key, edited by Email managers. */
16
+ const MailTemplate = defineModel('mail.template', {
17
+ label: 'Email template',
18
+ archivable: true,
19
+ order: ['targetModel asc', 'name asc'],
20
+ access: {
21
+ 'base.user': ['read'],
22
+ 'mail.manager': ['read', 'write', 'create', 'unlink'],
23
+ },
24
+ fields: {
25
+ name: f.char({ required: true, translate: true, label: 'Template' }),
26
+ targetModel: f.char({ required: true, index: true, label: 'Document' }),
27
+ /** `module.key` of the manifest template it came from, so reinstalls do not duplicate it. */
28
+ key: f.char({ index: 'unique' }),
29
+ subject: f.char({ required: true, translate: true, size: 998, label: 'Subject' }),
30
+ body: f.text({
31
+ required: true,
32
+ translate: true,
33
+ label: 'Body',
34
+ help: 'Plain text with {{field}} placeholders (name, partner.name, amountTotal, company.name); a blank line starts a paragraph.',
35
+ }),
36
+ document: f.char({
37
+ label: 'Attach the document',
38
+ help: 'The document template sent as a PDF, as module.key (accounting.invoice); empty: no attachment.',
39
+ }),
40
+ },
41
+ });
42
+ /** An email sent from a record (RFC 0029 §3.3): queued with the record, sent by the outbox subscriber. */
43
+ const MailOutgoing = defineModel('mail.outgoing', {
44
+ label: 'Sent email',
45
+ order: ['id desc'],
46
+ access: { 'mail.manager': ['read'] },
47
+ indexes: [{ fields: ['resModel', 'resId'] }, { fields: ['state'] }],
48
+ fields: {
49
+ resModel: f.char({ required: true, label: 'Document' }),
50
+ resId: f.integer({ required: true, label: 'Record' }),
51
+ to: f.text({ required: true, label: 'To' }),
52
+ cc: f.text({ label: 'Cc' }),
53
+ subject: f.char({ required: true, size: 998, label: 'Subject' }),
54
+ text: f.text({ label: 'Text' }),
55
+ html: f.text({ label: 'HTML' }),
56
+ attachments: f.many2many('storage.attachment', {
57
+ relation: 'mail_outgoing_attachment_rel',
58
+ label: 'Attachments',
59
+ }),
60
+ state: f.selection({ queued: 'Queued', sent: 'Sent', failed: 'Failed' }, {
61
+ required: true,
62
+ default: 'queued',
63
+ label: 'Status',
64
+ help: 'Queued until the mail server took it; failed with the reason when it refused.',
65
+ }),
66
+ error: f.text({ label: 'Error' }),
67
+ providerId: f.char({ size: 998, label: 'Message id' }),
68
+ sentAt: f.datetime({ label: 'Sent' }),
69
+ author: f.many2one('res.users', { ondelete: 'set null', label: 'Sent by' }),
70
+ message: f.many2one('mail.message', { ondelete: 'set null', label: 'Chatter message' }),
71
+ },
72
+ });
73
+ /** The models of sending documents, added to the mail module's. */
74
+ export const SEND_MODELS = [MailTemplate, MailOutgoing];
75
+ /** The sendable documents of the loaded modules, by model. */
76
+ export function sendablesOf(modules) {
77
+ const out = new Map();
78
+ for (const m of modules)
79
+ for (const s of m.sendable ?? [])
80
+ out.set(s.model, s);
81
+ return out;
82
+ }
83
+ /** Installs the manifests' email templates that do not exist yet (by `module.key`). */
84
+ export async function installMailTemplates(env, modules) {
85
+ let created = 0;
86
+ const model = env.withFlags({ activeTest: false }).model('mail.template');
87
+ for (const m of modules)
88
+ for (const spec of m.mailTemplates ?? []) {
89
+ const key = `${m.name}.${spec.key}`;
90
+ if ((await model.search({ where: { key } })).length > 0)
91
+ continue;
92
+ const translations = Object.entries(spec.translations ?? {});
93
+ await model.create([
94
+ {
95
+ name: spec.name,
96
+ targetModel: spec.model,
97
+ key,
98
+ subject: {
99
+ en_US: spec.subject,
100
+ ...Object.fromEntries(translations.map(([l, t]) => [l, t.subject])),
101
+ },
102
+ body: {
103
+ en_US: spec.body,
104
+ ...Object.fromEntries(translations.map(([l, t]) => [l, t.body])),
105
+ },
106
+ ...(spec.document === undefined ? {} : { document: spec.document }),
107
+ },
108
+ ]);
109
+ created++;
110
+ }
111
+ return created;
112
+ }
113
+ const LANGS = { en: 'en_US', bn: 'bn_BD' };
114
+ const EMAIL = /^[^\s@<>,;]+@[^\s@<>,;]+\.[^\s@<>,;]+$/;
115
+ /** A list of addresses from the body: each checked, at most 20. */
116
+ function addresses(value, what) {
117
+ const list = Array.isArray(value)
118
+ ? value
119
+ : typeof value === 'string'
120
+ ? value.split(/[,;]/)
121
+ : value === undefined || value === null
122
+ ? []
123
+ : null;
124
+ if (list === null)
125
+ throw bad(`${what} is a list of email addresses.`);
126
+ const out = list.map((a) => String(a).trim()).filter((a) => a !== '');
127
+ if (out.length > 20)
128
+ throw bad(`At most 20 addresses in ${what}.`);
129
+ const wrong = out.find((a) => !EMAIL.test(a));
130
+ if (wrong !== undefined)
131
+ throw bad(`${wrong} is not an email address.`);
132
+ return out;
133
+ }
134
+ const bad = (detail) => new HttpProblem(400, 'invalid-body', 'Invalid email', detail);
135
+ const ref = (env, name) => env.model(name);
136
+ /** The send engine of one server: the sendable documents, the models' metadata, the mailer. */
137
+ export function sendEngine(context, mailer) {
138
+ const apiModels = publicApiModels(context.registry);
139
+ const fields = documentFields(apiModels);
140
+ const modules = context.registry.loaded.modules;
141
+ const sendables = sendablesOf(modules);
142
+ const chatter = chatterContext(context);
143
+ const served = new Set(apiModels.keys());
144
+ const reporting = modules.some((m) => m.name === 'reporting');
145
+ /** The record as the caller, matching the document's `when`; refused otherwise. */
146
+ const sendable = async (env, model, id) => {
147
+ const spec = sendables.get(model);
148
+ if (spec === undefined)
149
+ throw new HttpProblem(404, 'not-sendable', 'Not sent by email', `${model} is not sendable.`);
150
+ const found = await ref(env, model).search({
151
+ where: { id, ...(spec.when ?? {}) },
152
+ limit: 1,
153
+ });
154
+ if (found.length === 0) {
155
+ const exists = (await ref(env, model).search({ where: { id }, limit: 1 })).length > 0;
156
+ if (!exists)
157
+ throw new HttpProblem(404, 'missing-record', 'Not found', 'No such record.');
158
+ throw new HttpProblem(409, 'not-ready', 'Not ready to send', 'This document cannot be sent in its state.');
159
+ }
160
+ return spec;
161
+ };
162
+ const templateOf = async (env, key) => {
163
+ const [row] = (await (await ref(env, 'mail.template').search({ where: { key }, limit: 1 })).load([])).read(['id', 'subject', 'body', 'document']);
164
+ if (row === undefined)
165
+ throw new HttpProblem(409, 'no-template', 'No email template', `The template ${key} is missing or archived: run the apps' defaults, or restore it.`);
166
+ return row;
167
+ };
168
+ /** The partner's name, address and language. */
169
+ const partnerOf = async (env, spec, model, id) => {
170
+ const [record] = (await ref(env, model).browse([id]).load([spec.partner])).read([spec.partner]);
171
+ const value = record?.[spec.partner];
172
+ const partnerId = typeof value === 'number' ? value : (value?.id ?? null);
173
+ if (partnerId === null)
174
+ return null;
175
+ const hasLang = apiModels.get('res.partner')?.fields.some((f) => f.key === 'lang') === true;
176
+ const keys = ['name', 'email', ...(hasLang ? ['lang'] : [])];
177
+ const [p] = (await ref(env, 'res.partner').browse([partnerId]).load(keys)).read(keys);
178
+ return p === undefined
179
+ ? null
180
+ : {
181
+ name: String(p['name'] ?? ''),
182
+ email: p['email'] ?? null,
183
+ lang: p['lang'] ?? null,
184
+ };
185
+ };
186
+ /** The subject and body filled for the record, in `lang`. */
187
+ const fill = async (env, model, id, subject, body) => {
188
+ const layout = {
189
+ paper: 'A4',
190
+ blocks: [{ kind: 'text', id: 'mail', text: `${subject}\n${body}` }],
191
+ };
192
+ const data = await loadDocumentData(env, model, [id], layout, fields);
193
+ const lang = env.lang.replace('_', '-');
194
+ const doc = {
195
+ model,
196
+ record: data.records[0] ?? {},
197
+ company: data.company,
198
+ user: data.user,
199
+ now: new Date(),
200
+ locale: lang,
201
+ tz: env.tz,
202
+ };
203
+ return { subject: fillText(doc, fields, subject), body: fillText(doc, fields, body) };
204
+ };
205
+ /** The partner's language (`bn`, `en`, or a full code like `bn_BD`) as the environment's. */
206
+ const languageOf = (env, lang) => {
207
+ const full = lang === null || lang === '' ? null : (LANGS[lang] ?? lang);
208
+ try {
209
+ return full === null ? env : env.withLang(full);
210
+ }
211
+ catch {
212
+ return env;
213
+ }
214
+ };
215
+ async function compose(env, model, id) {
216
+ const spec = await sendable(env, model, id);
217
+ const partner = await partnerOf(env, spec, model, id);
218
+ const local = languageOf(env, partner?.lang ?? null);
219
+ const template = await templateOf(local, spec.template);
220
+ const filled = await fill(local, model, id, String(template['subject'] ?? ''), String(template['body'] ?? ''));
221
+ const document = template['document'] ?? null;
222
+ // How often it was sent: counted directly (the caller reads the record; the log itself is for Email managers).
223
+ const [sent] = await env.sql `
224
+ SELECT count(*)::int AS count, max(created_at) AS last FROM mail_outgoing
225
+ WHERE res_model = ${model} AND res_id = ${id} AND state <> 'failed'`;
226
+ return {
227
+ to: partner === null ? [] : [{ name: partner.name, email: partner.email }],
228
+ ...filled,
229
+ attachment: document !== null && reporting ? `${await nameOf(env, model, id)}.pdf` : null,
230
+ lang: local.lang,
231
+ sent: sent?.count ?? 0,
232
+ lastSentAt: sent?.last instanceof Date ? sent.last.toISOString() : null,
233
+ };
234
+ }
235
+ const nameOf = async (env, model, id) => {
236
+ const [row] = (await ref(env, model).browse([id]).load(['displayName'])).read(['displayName']);
237
+ return String(row?.['displayName'] ?? `${model}-${id}`).replace(/[\\/:*?"<>|]+/g, '-');
238
+ };
239
+ async function send(env, model, id, input) {
240
+ const spec = await sendable(env, model, id);
241
+ if (mailer() === undefined)
242
+ throw new HttpProblem(409, 'no-mailer', 'No email set up', 'Set SMTP_URL to send documents by email.');
243
+ const to = addresses(input.to, 'to');
244
+ const cc = addresses(input.cc, 'cc');
245
+ if (to.length === 0)
246
+ throw bad('Name at least one recipient.');
247
+ const subject = typeof input.subject === 'string' ? input.subject.trim() : '';
248
+ const body = typeof input.body === 'string' ? input.body.trim() : '';
249
+ if (subject === '' || subject.length > 998)
250
+ throw bad('The subject is 1 to 998 characters.');
251
+ if (body.length > 50_000)
252
+ throw bad('The body is at most 50,000 characters.');
253
+ const template = await templateOf(env, spec.template);
254
+ const documentKey = template['document'] ?? null;
255
+ let attachment = null;
256
+ if (input.attach !== false && documentKey !== null && reporting) {
257
+ const [doc] = (await (await ref(env, 'report.template').search({ where: { key: documentKey }, limit: 1 })).load([])).read(['id', 'name', 'layout', 'scope']);
258
+ const apiModel = apiModels.get(model);
259
+ if (doc !== undefined && apiModel !== undefined) {
260
+ const rendered = await renderDocument(env, apiModel, doc, fields, { ids: [id] }, 'pdf');
261
+ const saved = await saveAttachment(env, storageOf(context.app), served, {
262
+ model,
263
+ id,
264
+ name: `${await nameOf(env, model, id)}.pdf`,
265
+ mime: 'application/pdf',
266
+ data: rendered.content,
267
+ });
268
+ attachment = saved.id;
269
+ }
270
+ }
271
+ const posted = await postMessage(env, chatter, model, id, {
272
+ kind: 'comment',
273
+ text: `${subject}\n\n${body}`,
274
+ attachments: attachment === null ? [] : [attachment],
275
+ });
276
+ const paragraphs = body
277
+ .split(/\n\s*\n/)
278
+ .map((p) => p.trim())
279
+ .filter((p) => p !== '');
280
+ const mail = layout({ title: subject, paragraphs, lang: env.lang.slice(0, 2) });
281
+ const outgoing = await env.asSuperuser({ reason: 'email queued from a record' }, async (su) => {
282
+ const made = await ref(su, 'mail.outgoing').create([
283
+ {
284
+ resModel: model,
285
+ resId: id,
286
+ to: to.join(', '),
287
+ cc: cc.length === 0 ? null : cc.join(', '),
288
+ subject,
289
+ text: body,
290
+ html: mail.html,
291
+ attachments: { set: attachment === null ? [] : [attachment] },
292
+ author: env.uid,
293
+ message: posted.id,
294
+ },
295
+ ]);
296
+ return made.ids[0];
297
+ });
298
+ await env.emit('mail.outgoing.queued', { outgoing, model, id });
299
+ return { outgoing, message: posted.id, attachment };
300
+ }
301
+ /** Sends one queued email, as the system (the outbox subscriber). Sent once: a sent row is left alone. */
302
+ async function deliver(su, outgoing) {
303
+ const [row] = (await ref(su, 'mail.outgoing').browse([outgoing]).load({ attachments: true })).read(['to', 'cc', 'subject', 'text', 'html', 'state', 'attachments']);
304
+ const m = mailer();
305
+ if (row === undefined || row['state'] !== 'queued' || m === undefined)
306
+ return;
307
+ try {
308
+ const files = [];
309
+ for (const a of (row['attachments'] ?? [])) {
310
+ const opened = await openAttachment(su, storageOf(context.app), served, typeof a === 'number' ? a : a.id);
311
+ const chunks = [];
312
+ for await (const chunk of opened.body)
313
+ chunks.push(Buffer.from(chunk));
314
+ files.push({
315
+ filename: opened.info.name,
316
+ content: Buffer.concat(chunks),
317
+ contentType: opened.info.mime,
318
+ });
319
+ }
320
+ const cc = String(row['cc'] ?? '')
321
+ .split(',')
322
+ .map((a) => a.trim())
323
+ .filter((a) => a !== '');
324
+ const result = await m.send({
325
+ to: String(row['to']),
326
+ ...(cc.length === 0 ? {} : { cc }),
327
+ subject: String(row['subject']),
328
+ text: String(row['text'] ?? ''),
329
+ html: String(row['html'] ?? escapeHtml(String(row['text'] ?? ''))),
330
+ ...(files.length === 0 ? {} : { attachments: files }),
331
+ });
332
+ await ref(su, 'mail.outgoing')
333
+ .browse([outgoing])
334
+ .write({ state: 'sent', sentAt: new Date(), providerId: result.id });
335
+ }
336
+ catch (error) {
337
+ await ref(su, 'mail.outgoing')
338
+ .browse([outgoing])
339
+ .write({
340
+ state: 'failed',
341
+ error: error instanceof Error ? error.message : String(error),
342
+ });
343
+ }
344
+ }
345
+ /** The composition sent as it is, to the partner's address; refused (before any write) when it cannot be. */
346
+ async function sendComposed(env, model, id) {
347
+ const c = await compose(env, model, id);
348
+ const to = c.to.map((r) => r.email).filter((e) => e !== null && e !== '');
349
+ if (to.length === 0)
350
+ throw new HttpProblem(409, 'no-address', 'No email address', `${c.to[0]?.name ?? 'The partner'} has no email address.`);
351
+ return send(env, model, id, { to, subject: c.subject, body: c.body });
352
+ }
353
+ async function sendMany(env, model, ids) {
354
+ if (ids.length === 0 || ids.length > 100)
355
+ throw new HttpProblem(400, 'invalid-body', 'Invalid batch', 'Send 1 to 100 records at once.');
356
+ let sent = 0;
357
+ const skipped = [];
358
+ for (const id of ids)
359
+ try {
360
+ await sendComposed(env, model, id);
361
+ sent++;
362
+ }
363
+ catch (error) {
364
+ // Refusals happen before anything is written (the record, its state, its address): the batch goes on.
365
+ if (!(error instanceof HttpProblem) || error.problem.status >= 500)
366
+ throw error;
367
+ skipped.push({ id, reason: error.problem.detail ?? error.problem.title });
368
+ }
369
+ return { sent, skipped };
370
+ }
371
+ async function autoSend(env, model, id) {
372
+ const spec = sendables.get(model);
373
+ if (spec?.autoSend === undefined || mailer() === undefined)
374
+ return false;
375
+ const found = await ref(env, model).search({ where: { id, ...(spec.when ?? {}) }, limit: 1 });
376
+ if (found.length === 0)
377
+ return false;
378
+ const [record] = (await ref(env, model).browse([id]).load([spec.partner])).read([spec.partner]);
379
+ const value = record?.[spec.partner];
380
+ const partner = typeof value === 'number' ? value : (value?.id ?? null);
381
+ if (partner === null)
382
+ return false;
383
+ const flag = spec.autoSend.flag;
384
+ const [p] = (await ref(env, 'res.partner').browse([partner]).load([flag, 'email'])).read([
385
+ flag,
386
+ 'email',
387
+ ]);
388
+ if (p?.[flag] !== true || typeof p['email'] !== 'string' || p['email'] === '')
389
+ return false;
390
+ await sendComposed(env, model, id);
391
+ return true;
392
+ }
393
+ return { sendables, compose, send, deliver, sendMany, autoSend };
394
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * "Send" on the form of a sendable document (RFC 0029 §3.3): the dialog opens with the customer's address, the
3
+ * template's subject and body in their language and the PDF named; everything can be edited before sending. The
4
+ * server renders the PDF, posts the message on the chatter and queues the email. Hidden on documents that are not
5
+ * sendable, and refused by the server on records not ready to send.
6
+ */
7
+ import { type NodeComponentProps } from '../../ui/index.js';
8
+ import { type ReactElement } from 'react';
9
+ export declare function SendButton({ record, model, api, reload, }: NodeComponentProps): ReactElement | null;
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * "Send" on the form of a sendable document (RFC 0029 §3.3): the dialog opens with the customer's address, the
4
+ * template's subject and body in their language and the PDF named; everything can be edited before sending. The
5
+ * server renders the PDF, posts the message on the chatter and queues the email. Hidden on documents that are not
6
+ * sendable, and refused by the server on records not ready to send.
7
+ */
8
+ import { ApiError, Button, Checkbox, Dialog, Input, Label, Textarea, useKosh, } from '../../ui/index.js';
9
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
10
+ import { Mail, Paperclip } from 'lucide-react';
11
+ import { useId, useState } from 'react';
12
+ import { useTranslation } from 'react-i18next';
13
+ export function SendButton({ record, model, api, reload, }) {
14
+ const { t } = useTranslation('mail');
15
+ const { format } = useKosh();
16
+ const id = useId();
17
+ const queryClient = useQueryClient();
18
+ const [open, setOpen] = useState(false);
19
+ const [to, setTo] = useState('');
20
+ const [cc, setCc] = useState('');
21
+ const [subject, setSubject] = useState('');
22
+ const [body, setBody] = useState('');
23
+ const [attach, setAttach] = useState(true);
24
+ const [done, setDone] = useState(false);
25
+ const sendable = useQuery({
26
+ queryKey: ['mail', 'sendable'],
27
+ queryFn: () => api.call('GET', '/api/v1/mail/sendable'),
28
+ staleTime: Number.POSITIVE_INFINITY,
29
+ });
30
+ const compose = useMutation({
31
+ mutationFn: () => api.call('GET', `/api/v1/mail/compose/${model}/${record?.id}`),
32
+ onSuccess: (c) => {
33
+ setTo(c.to
34
+ .map((r) => r.email)
35
+ .filter((e) => e !== null && e !== '')
36
+ .join(', '));
37
+ setCc('');
38
+ setSubject(c.subject);
39
+ setBody(c.body);
40
+ setAttach(c.attachment !== null);
41
+ setDone(false);
42
+ setOpen(true);
43
+ },
44
+ });
45
+ const send = useMutation({
46
+ mutationFn: () => api.call('POST', `/api/v1/mail/send/${model}/${record?.id}`, {
47
+ to,
48
+ cc,
49
+ subject,
50
+ body,
51
+ attach,
52
+ }),
53
+ onSuccess: () => {
54
+ setDone(true);
55
+ setOpen(false);
56
+ // The message is on the chatter now.
57
+ void queryClient.invalidateQueries({ queryKey: ['chatter', model, record?.id] });
58
+ reload?.();
59
+ },
60
+ });
61
+ if (record === null || !(sendable.data?.models ?? []).includes(model))
62
+ return null;
63
+ const c = compose.data;
64
+ const error = compose.error ?? send.error;
65
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { size: "sm", busy: compose.isPending, onClick: () => compose.mutate(), children: [_jsx(Mail, { "aria-hidden": true }), t('send.button')] }), done ? (_jsx("span", { role: "status", className: "text-xs text-muted-foreground", children: t('send.queued') })) : null, error !== null && !open ? (_jsx("span", { role: "alert", className: "text-xs text-danger", children: error instanceof ApiError ? error.message : String(error) })) : null, _jsx(Dialog, { open: open, onOpenChange: setOpen, title: t('send.title'), description: c === undefined || c.sent === 0
66
+ ? t('send.intro')
67
+ : t('send.already', {
68
+ count: c.sent,
69
+ date: c.lastSentAt === null ? '' : format.date(c.lastSentAt),
70
+ }), footer: _jsx(Button, { variant: "primary", busy: send.isPending, disabled: to.trim() === '' || subject.trim() === '', onClick: () => send.mutate(), children: t('send.confirm') }), children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `${id}-to`, children: t('send.to') }), _jsx(Input, { id: `${id}-to`, type: "text", value: to, onChange: (e) => setTo(e.target.value) }), c?.to.some((r) => r.email === null || r.email === '') ? (_jsx("p", { className: "text-xs text-muted-foreground", children: t('send.noEmail', { name: c.to[0]?.name ?? '' }) })) : null] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `${id}-cc`, children: t('send.cc') }), _jsx(Input, { id: `${id}-cc`, type: "text", value: cc, onChange: (e) => setCc(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `${id}-subject`, children: t('send.subject') }), _jsx(Input, { id: `${id}-subject`, value: subject, maxLength: 998, onChange: (e) => setSubject(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: `${id}-body`, children: t('send.body') }), _jsx(Textarea, { id: `${id}-body`, rows: 9, maxLength: 50_000, value: body, onChange: (e) => setBody(e.target.value) })] }), c?.attachment === null || c === undefined ? null : (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { id: `${id}-attach`, checked: attach, onCheckedChange: (on) => setAttach(on === true) }), _jsxs("label", { htmlFor: `${id}-attach`, className: "flex items-center gap-1", children: [_jsx(Paperclip, { "aria-hidden": true, className: "size-4" }), c.attachment] })] })), send.error === null ? null : (_jsx("p", { role: "alert", className: "text-sm text-danger", children: send.error instanceof ApiError ? send.error.message : String(send.error) }))] }) })] }));
71
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * "Send by email" in a list's bar of selected rows (RFC 0029 §3.3): each selected document goes to its partner
3
+ * with its own template, in the partner's language, with its PDF. Documents without an address or not ready are
4
+ * skipped and said so. Hidden on lists of models that are not sendable.
5
+ */
6
+ import { type SelectionActionProps } from '../../ui/index.js';
7
+ import { type ReactElement } from 'react';
8
+ export declare function SendSelected({ api, model, ids, done }: SelectionActionProps): ReactElement | null;
@@ -0,0 +1,33 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * "Send by email" in a list's bar of selected rows (RFC 0029 §3.3): each selected document goes to its partner
4
+ * with its own template, in the partner's language, with its PDF. Documents without an address or not ready are
5
+ * skipped and said so. Hidden on lists of models that are not sendable.
6
+ */
7
+ import { ApiError, Button, Dialog } from '../../ui/index.js';
8
+ import { useMutation, useQuery } from '@tanstack/react-query';
9
+ import { Mail } from 'lucide-react';
10
+ import { useState } from 'react';
11
+ import { useTranslation } from 'react-i18next';
12
+ export function SendSelected({ api, model, ids, done }) {
13
+ const { t } = useTranslation('mail');
14
+ const [open, setOpen] = useState(false);
15
+ const sendable = useQuery({
16
+ queryKey: ['mail', 'sendable'],
17
+ queryFn: () => api.call('GET', '/api/v1/mail/sendable'),
18
+ staleTime: Number.POSITIVE_INFINITY,
19
+ });
20
+ const send = useMutation({
21
+ mutationFn: () => api.call('POST', `/api/v1/mail/send-many/${model}`, { ids }),
22
+ onSuccess: (r) => {
23
+ setOpen(false);
24
+ const skipped = r.skipped.length === 0
25
+ ? ''
26
+ : ` ${t('send.skipped', { count: r.skipped.length, reason: r.skipped[0]?.reason ?? '' })}`;
27
+ done(`${t('send.sentMany', { count: r.sent })}${skipped}`, r.sent === 0 ? 'danger' : 'info');
28
+ },
29
+ });
30
+ if (!(sendable.data?.models ?? []).includes(model))
31
+ return null;
32
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", onClick: () => setOpen(true), children: [_jsx(Mail, { "aria-hidden": true }), t('send.many')] }), _jsx(Dialog, { open: open, onOpenChange: setOpen, title: t('send.manyTitle', { count: ids.length }), description: t('send.manyIntro'), footer: _jsx(Button, { variant: "primary", busy: send.isPending, onClick: () => send.mutate(), children: t('send.confirm') }), children: send.error === null ? null : (_jsx("p", { role: "alert", className: "text-sm text-danger", children: send.error instanceof ApiError ? send.error.message : String(send.error) })) })] }));
33
+ }
@@ -7,6 +7,8 @@ import { type Registry } from '../../ui/index.js';
7
7
  import type { i18n } from 'i18next';
8
8
  import { messages } from './messages.js';
9
9
  export { InboundPage } from './InboundPage.js';
10
+ export { SendButton } from './SendButton.js';
11
+ export { SendSelected } from './SendSelected.js';
10
12
  /** Registers the page (and its messages in `i18n`, when given). */
11
13
  export declare function registerMailWeb(registry: Registry, i18n?: i18n): Registry;
12
14
  export { messages as mailMessages };
@@ -6,10 +6,16 @@
6
6
  import { addMessages } from '../../ui/index.js';
7
7
  import { InboundPage } from './InboundPage.js';
8
8
  import { messages } from './messages.js';
9
+ import { SendButton } from './SendButton.js';
10
+ import { SendSelected } from './SendSelected.js';
9
11
  export { InboundPage } from './InboundPage.js';
12
+ export { SendButton } from './SendButton.js';
13
+ export { SendSelected } from './SendSelected.js';
10
14
  /** Registers the page (and its messages in `i18n`, when given). */
11
15
  export function registerMailWeb(registry, i18n) {
12
16
  registry.registerPage('mail.Inbound', InboundPage);
17
+ registry.registerComponent('mail.Send', SendButton);
18
+ registry.registerSelectionAction('mail.SendSelected', SendSelected);
13
19
  if (i18n !== undefined)
14
20
  addMessages(i18n, 'mail', messages);
15
21
  return registry;
@@ -1,6 +1,24 @@
1
1
  /** Messages of the inbound email page (namespace `mail`). */
2
2
  export const messages = {
3
3
  en: {
4
+ send: {
5
+ button: 'Send',
6
+ title: 'Send by email',
7
+ intro: 'The text comes from the template, in the customer’s language; change anything before sending.',
8
+ already: 'Sent {{count}} times already, last on {{date}}.',
9
+ to: 'To',
10
+ cc: 'Cc',
11
+ subject: 'Subject',
12
+ body: 'Message',
13
+ noEmail: '{{name}} has no email address: type one.',
14
+ confirm: 'Send',
15
+ queued: 'Sent: the email is on its way and on the chatter.',
16
+ many: 'Send by email',
17
+ manyTitle: 'Send {{count}} documents by email?',
18
+ manyIntro: 'Each goes to its customer or vendor with its own template, in their language, with its PDF; it is posted on its chatter.',
19
+ sentMany: '{{count}} sent by email.',
20
+ skipped: '{{count}} not sent: {{reason}}',
21
+ },
4
22
  inbound: {
5
23
  title: 'Inbound email',
6
24
  intro: 'Replies to notification emails post on their record. The messages below matched no record or no known sender: attach them to a record, or discard them.',
@@ -40,6 +58,24 @@ export const messages = {
40
58
  },
41
59
  },
42
60
  bn: {
61
+ send: {
62
+ button: 'পাঠান',
63
+ title: 'ইমেইলে পাঠান',
64
+ intro: 'লেখাটি টেমপ্লেট থেকে, গ্রাহকের ভাষায়; পাঠানোর আগে যেকোনো কিছু বদলাতে পারেন।',
65
+ already: 'আগে {{count}} বার পাঠানো হয়েছে, শেষবার {{date}}।',
66
+ to: 'প্রাপক',
67
+ cc: 'অনুলিপি',
68
+ subject: 'বিষয়',
69
+ body: 'বার্তা',
70
+ noEmail: '{{name}}-এর কোনো ইমেইল ঠিকানা নেই: একটি লিখুন।',
71
+ confirm: 'পাঠান',
72
+ queued: 'পাঠানো হয়েছে: ইমেইলটি যাচ্ছে এবং চ্যাটারে আছে।',
73
+ many: 'ইমেইলে পাঠান',
74
+ manyTitle: '{{count}}টি নথি ইমেইলে পাঠাবেন?',
75
+ manyIntro: 'প্রতিটি নিজ টেমপ্লেটে, প্রাপকের ভাষায়, PDF-সহ তার গ্রাহক বা সরবরাহকারীর কাছে যায়; এর চ্যাটারেও থাকে।',
76
+ sentMany: '{{count}}টি ইমেইলে পাঠানো হয়েছে।',
77
+ skipped: '{{count}}টি পাঠানো হয়নি: {{reason}}',
78
+ },
43
79
  inbound: {
44
80
  title: 'আগত ইমেইল',
45
81
  intro: 'বিজ্ঞপ্তি ইমেইলের উত্তর তাদের রেকর্ডে পোস্ট হয়। নিচের বার্তাগুলো কোনো রেকর্ড বা পরিচিত প্রেরকের সাথে মেলেনি: এগুলো কোনো রেকর্ডে যুক্ত করুন, অথবা বাতিল করুন।',
@@ -1,4 +1,4 @@
1
- // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: fa02b3043eeb7085
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: f330111b566f456e
2
2
  // Bindings of module `maintenance`: import the definition API from here to get types scoped to what it can see.
3
3
  export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
4
4
  import { definitions } from '../../server/orm.js';
@@ -1,4 +1,4 @@
1
- // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: fa02b3043eeb7085
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: f330111b566f456e
2
2
  // Schema view of module `maintenance` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
3
3
  import type { F } from '../../server/orm.js';
4
4
 
@@ -815,6 +815,7 @@ export interface ResPartner {
815
815
  readonly parent: F.Many2one<'res.partner'>;
816
816
  readonly contacts: F.One2many<'res.partner', { readonly required: true }>;
817
817
  readonly email: F.Char;
818
+ readonly lang: F.Selection<'en' | 'bn'>;
818
819
  readonly phone: F.Char;
819
820
  readonly mobile: F.Char;
820
821
  readonly website: F.Char;
@@ -0,0 +1,2 @@
1
+ /** Bangla for the module's labels, keyed by their English text (RFC 0023 §3.1; `pnpm i18n:extract`). */
2
+ export declare const bn: Readonly<Record<string, string>>;