shutro-erp 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (768) hide show
  1. package/dist/account_asset/.kosh/bindings.js +15 -0
  2. package/dist/{mrp_account → account_asset}/.kosh/schema.d.ts +785 -614
  3. package/dist/account_asset/board.d.ts +61 -0
  4. package/dist/account_asset/board.js +86 -0
  5. package/dist/account_asset/books.d.ts +42 -0
  6. package/dist/account_asset/books.js +398 -0
  7. package/dist/account_asset/demo.d.ts +7 -0
  8. package/dist/account_asset/demo.js +72 -0
  9. package/dist/account_asset/i18n/bn.d.ts +2 -0
  10. package/dist/account_asset/i18n/bn.js +97 -0
  11. package/dist/account_asset/index.d.ts +9 -0
  12. package/dist/account_asset/index.js +337 -0
  13. package/dist/account_asset/module.d.ts +11076 -0
  14. package/dist/account_asset/module.js +230 -0
  15. package/dist/account_budget/.kosh/bindings.js +15 -0
  16. package/dist/account_budget/.kosh/schema.d.ts +2841 -0
  17. package/dist/account_budget/budget.d.ts +5 -0
  18. package/dist/account_budget/budget.js +142 -0
  19. package/dist/account_budget/demo.d.ts +6 -0
  20. package/dist/account_budget/demo.js +30 -0
  21. package/dist/account_budget/i18n/bn.d.ts +2 -0
  22. package/dist/account_budget/i18n/bn.js +63 -0
  23. package/dist/account_budget/index.d.ts +4 -0
  24. package/dist/account_budget/index.js +237 -0
  25. package/dist/account_budget/module.d.ts +27289 -0
  26. package/dist/account_budget/module.js +182 -0
  27. package/dist/account_portal/.kosh/bindings.js +1 -1
  28. package/dist/account_portal/.kosh/schema.d.ts +651 -10
  29. package/dist/account_portal/i18n/bn.d.ts +2 -0
  30. package/dist/account_portal/i18n/bn.js +6 -0
  31. package/dist/account_portal/index.js +2 -0
  32. package/dist/account_portal/module.d.ts +5212 -1592
  33. package/dist/accounting/.kosh/bindings.js +1 -1
  34. package/dist/accounting/.kosh/schema.d.ts +651 -10
  35. package/dist/accounting/closing.js +1 -0
  36. package/dist/accounting/fiscal.d.ts +578 -0
  37. package/dist/accounting/fiscal.js +401 -0
  38. package/dist/accounting/followups.d.ts +618 -0
  39. package/dist/accounting/followups.js +420 -0
  40. package/dist/accounting/i18n/bn.d.ts +2 -0
  41. package/dist/accounting/i18n/bn.js +425 -0
  42. package/dist/accounting/index.d.ts +3 -1
  43. package/dist/accounting/index.js +780 -5
  44. package/dist/accounting/module.d.ts +5386 -1543
  45. package/dist/accounting/module.js +155 -18
  46. package/dist/accounting/statements.d.ts +878 -0
  47. package/dist/accounting/statements.js +728 -0
  48. package/dist/accounting/web/BankReconcilePage.d.ts +9 -0
  49. package/dist/accounting/web/BankReconcilePage.js +138 -0
  50. package/dist/accounting/web/FollowupsPage.d.ts +8 -0
  51. package/dist/accounting/web/FollowupsPage.js +54 -0
  52. package/dist/accounting/web/PostingPreview.d.ts +8 -0
  53. package/dist/accounting/web/PostingPreview.js +26 -0
  54. package/dist/accounting/web/index.d.ts +3 -0
  55. package/dist/accounting/web/index.js +9 -0
  56. package/dist/accounting/web/messages.js +140 -0
  57. package/dist/approvals/.kosh/bindings.d.ts +22 -0
  58. package/dist/approvals/.kosh/bindings.js +15 -0
  59. package/dist/approvals/.kosh/schema.d.ts +879 -0
  60. package/dist/approvals/demo.d.ts +7 -0
  61. package/dist/approvals/demo.js +41 -0
  62. package/dist/approvals/i18n/bn.d.ts +2 -0
  63. package/dist/approvals/i18n/bn.js +133 -0
  64. package/dist/approvals/index.d.ts +36 -0
  65. package/dist/approvals/index.js +772 -0
  66. package/dist/approvals/mails.d.ts +30 -0
  67. package/dist/approvals/mails.js +87 -0
  68. package/dist/approvals/module.d.ts +2930 -0
  69. package/dist/approvals/module.js +473 -0
  70. package/dist/approvals/web/Config.d.ts +12 -0
  71. package/dist/approvals/web/Config.js +60 -0
  72. package/dist/approvals/web/History.d.ts +4 -0
  73. package/dist/approvals/web/History.js +13 -0
  74. package/dist/approvals/web/Inbox.d.ts +9 -0
  75. package/dist/approvals/web/Inbox.js +114 -0
  76. package/dist/approvals/web/Matrix.d.ts +9 -0
  77. package/dist/approvals/web/Matrix.js +26 -0
  78. package/dist/approvals/web/Mine.d.ts +7 -0
  79. package/dist/approvals/web/Mine.js +27 -0
  80. package/dist/approvals/web/index.d.ts +15 -0
  81. package/dist/approvals/web/index.js +29 -0
  82. package/dist/approvals/web/messages.d.ts +2 -0
  83. package/dist/approvals/web/messages.js +211 -0
  84. package/dist/approvals/web/shared.d.ts +59 -0
  85. package/dist/approvals/web/shared.js +46 -0
  86. package/dist/approvals_purchase/.kosh/bindings.d.ts +22 -0
  87. package/dist/approvals_purchase/.kosh/bindings.js +15 -0
  88. package/dist/approvals_purchase/.kosh/schema.d.ts +1997 -0
  89. package/dist/approvals_purchase/i18n/bn.d.ts +2 -0
  90. package/dist/approvals_purchase/i18n/bn.js +5 -0
  91. package/dist/approvals_purchase/index.d.ts +3 -0
  92. package/dist/approvals_purchase/index.js +47 -0
  93. package/dist/approvals_purchase/module.d.ts +12670 -0
  94. package/dist/approvals_purchase/module.js +84 -0
  95. package/dist/attendance/.kosh/bindings.d.ts +22 -0
  96. package/dist/attendance/.kosh/bindings.js +15 -0
  97. package/dist/attendance/.kosh/schema.d.ts +1106 -0
  98. package/dist/attendance/days.d.ts +96 -0
  99. package/dist/attendance/days.js +430 -0
  100. package/dist/attendance/demo.d.ts +7 -0
  101. package/dist/attendance/demo.js +58 -0
  102. package/dist/attendance/i18n/bn.d.ts +2 -0
  103. package/dist/attendance/i18n/bn.js +147 -0
  104. package/dist/attendance/index.d.ts +4 -0
  105. package/dist/attendance/index.js +515 -0
  106. package/dist/attendance/module.d.ts +4413 -0
  107. package/dist/attendance/module.js +412 -0
  108. package/dist/attendance/routes.d.ts +16 -0
  109. package/dist/attendance/routes.js +266 -0
  110. package/dist/attendance/web/Check.d.ts +8 -0
  111. package/dist/attendance/web/Check.js +40 -0
  112. package/dist/attendance/web/Kiosk.d.ts +8 -0
  113. package/dist/attendance/web/Kiosk.js +62 -0
  114. package/dist/attendance/web/PinButton.d.ts +7 -0
  115. package/dist/attendance/web/PinButton.js +29 -0
  116. package/dist/attendance/web/index.d.ts +13 -0
  117. package/dist/attendance/web/index.js +22 -0
  118. package/dist/attendance/web/messages.d.ts +2 -0
  119. package/dist/attendance/web/messages.js +61 -0
  120. package/dist/automation/engine.js +1 -1
  121. package/dist/calendar/.kosh/bindings.js +1 -1
  122. package/dist/calendar/.kosh/schema.d.ts +2 -1
  123. package/dist/calendar/i18n/bn.d.ts +2 -0
  124. package/dist/calendar/i18n/bn.js +80 -0
  125. package/dist/calendar/index.js +77 -0
  126. package/dist/calendar/module.d.ts +26 -0
  127. package/dist/calendar/module.js +14 -4
  128. package/dist/cli/apps.js +25 -5
  129. package/dist/cli/init.js +46 -4
  130. package/dist/cli/templates.d.ts +1 -1
  131. package/dist/cli/templates.js +123 -5
  132. package/dist/contacts/.kosh/bindings.js +1 -1
  133. package/dist/contacts/.kosh/schema.d.ts +2 -1
  134. package/dist/contacts/i18n/bn.d.ts +2 -0
  135. package/dist/contacts/i18n/bn.js +67 -0
  136. package/dist/contacts/index.js +3 -0
  137. package/dist/contacts/module.d.ts +42 -0
  138. package/dist/contacts/module.js +19 -3
  139. package/dist/crm/.kosh/bindings.js +1 -1
  140. package/dist/crm/.kosh/schema.d.ts +3 -1
  141. package/dist/crm/i18n/bn.d.ts +2 -0
  142. package/dist/crm/i18n/bn.js +80 -0
  143. package/dist/crm/index.js +22 -0
  144. package/dist/crm/module.d.ts +22 -0
  145. package/dist/crm/module.js +6 -3
  146. package/dist/documents/.kosh/bindings.d.ts +22 -0
  147. package/dist/documents/.kosh/bindings.js +15 -0
  148. package/dist/documents/.kosh/schema.d.ts +863 -0
  149. package/dist/documents/demo.d.ts +6 -0
  150. package/dist/documents/demo.js +22 -0
  151. package/dist/documents/documents.d.ts +98 -0
  152. package/dist/documents/documents.js +318 -0
  153. package/dist/documents/i18n/bn.d.ts +2 -0
  154. package/dist/documents/i18n/bn.js +83 -0
  155. package/dist/documents/index.d.ts +30 -0
  156. package/dist/documents/index.js +569 -0
  157. package/dist/documents/module.d.ts +2801 -0
  158. package/dist/documents/module.js +215 -0
  159. package/dist/documents/web/Versions.d.ts +7 -0
  160. package/dist/documents/web/Versions.js +45 -0
  161. package/dist/documents/web/index.d.ts +11 -0
  162. package/dist/documents/web/index.js +16 -0
  163. package/dist/documents/web/messages.d.ts +2 -0
  164. package/dist/documents/web/messages.js +21 -0
  165. package/dist/event/.kosh/bindings.js +1 -1
  166. package/dist/event/.kosh/schema.d.ts +2 -1
  167. package/dist/event/i18n/bn.d.ts +2 -0
  168. package/dist/event/i18n/bn.js +101 -0
  169. package/dist/event/index.js +66 -0
  170. package/dist/event/module.d.ts +20 -0
  171. package/dist/event/module.js +8 -1
  172. package/dist/event_account/.kosh/bindings.js +1 -1
  173. package/dist/event_account/.kosh/schema.d.ts +651 -10
  174. package/dist/event_account/i18n/bn.d.ts +2 -0
  175. package/dist/event_account/i18n/bn.js +5 -0
  176. package/dist/event_account/index.js +2 -0
  177. package/dist/event_account/module.d.ts +5133 -1513
  178. package/dist/expense/.kosh/bindings.d.ts +22 -0
  179. package/dist/expense/.kosh/bindings.js +15 -0
  180. package/dist/expense/.kosh/schema.d.ts +1620 -0
  181. package/dist/expense/demo.d.ts +7 -0
  182. package/dist/expense/demo.js +69 -0
  183. package/dist/expense/i18n/bn.d.ts +2 -0
  184. package/dist/expense/i18n/bn.js +155 -0
  185. package/dist/expense/index.d.ts +3 -0
  186. package/dist/expense/index.js +502 -0
  187. package/dist/expense/module.d.ts +6768 -0
  188. package/dist/expense/module.js +649 -0
  189. package/dist/expense_account/.kosh/bindings.d.ts +22 -0
  190. package/dist/expense_account/.kosh/bindings.js +15 -0
  191. package/dist/expense_account/.kosh/schema.d.ts +2515 -0
  192. package/dist/expense_account/i18n/bn.d.ts +2 -0
  193. package/dist/expense_account/i18n/bn.js +15 -0
  194. package/dist/expense_account/index.d.ts +3 -0
  195. package/dist/expense_account/index.js +76 -0
  196. package/dist/expense_account/module.d.ts +14628 -0
  197. package/dist/expense_account/module.js +318 -0
  198. package/dist/helpdesk/.kosh/bindings.d.ts +22 -0
  199. package/dist/helpdesk/.kosh/bindings.js +15 -0
  200. package/dist/helpdesk/.kosh/schema.d.ts +886 -0
  201. package/dist/helpdesk/demo.d.ts +7 -0
  202. package/dist/helpdesk/demo.js +65 -0
  203. package/dist/helpdesk/hours.d.ts +17 -0
  204. package/dist/helpdesk/hours.js +94 -0
  205. package/dist/helpdesk/i18n/bn.d.ts +2 -0
  206. package/dist/helpdesk/i18n/bn.js +127 -0
  207. package/dist/helpdesk/index.d.ts +10 -0
  208. package/dist/helpdesk/index.js +463 -0
  209. package/dist/helpdesk/module.d.ts +2960 -0
  210. package/dist/helpdesk/module.js +326 -0
  211. package/dist/helpdesk/routes.d.ts +12 -0
  212. package/dist/helpdesk/routes.js +132 -0
  213. package/dist/helpdesk/sla.d.ts +37 -0
  214. package/dist/helpdesk/sla.js +226 -0
  215. package/dist/hr/.kosh/bindings.js +1 -1
  216. package/dist/hr/.kosh/schema.d.ts +59 -1
  217. package/dist/hr/demo.js +11 -1
  218. package/dist/hr/i18n/bn.d.ts +2 -0
  219. package/dist/hr/i18n/bn.js +126 -0
  220. package/dist/hr/index.d.ts +1 -1
  221. package/dist/hr/index.js +158 -12
  222. package/dist/hr/module.d.ts +932 -59
  223. package/dist/hr/module.js +114 -17
  224. package/dist/indent/.kosh/bindings.d.ts +22 -0
  225. package/dist/indent/.kosh/bindings.js +15 -0
  226. package/dist/indent/.kosh/schema.d.ts +2084 -0
  227. package/dist/indent/demo.d.ts +7 -0
  228. package/dist/indent/demo.js +48 -0
  229. package/dist/indent/i18n/bn.d.ts +2 -0
  230. package/dist/indent/i18n/bn.js +143 -0
  231. package/dist/indent/index.d.ts +3 -0
  232. package/dist/indent/index.js +450 -0
  233. package/dist/indent/module.d.ts +13424 -0
  234. package/dist/indent/module.js +792 -0
  235. package/dist/indent_purchase/.kosh/bindings.d.ts +22 -0
  236. package/dist/indent_purchase/.kosh/bindings.js +15 -0
  237. package/dist/indent_purchase/.kosh/schema.d.ts +2710 -0
  238. package/dist/indent_purchase/i18n/bn.d.ts +2 -0
  239. package/dist/indent_purchase/i18n/bn.js +13 -0
  240. package/dist/indent_purchase/index.d.ts +3 -0
  241. package/dist/indent_purchase/index.js +61 -0
  242. package/dist/indent_purchase/module.d.ts +27950 -0
  243. package/dist/indent_purchase/module.js +163 -0
  244. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  245. package/dist/l10n_bd/.kosh/schema.d.ts +651 -10
  246. package/dist/l10n_bd/i18n/bn.d.ts +2 -0
  247. package/dist/l10n_bd/i18n/bn.js +69 -0
  248. package/dist/l10n_bd/index.js +2 -0
  249. package/dist/l10n_bd/module.d.ts +5212 -1592
  250. package/dist/l10n_bd_payroll/.kosh/bindings.d.ts +22 -0
  251. package/dist/l10n_bd_payroll/.kosh/bindings.js +15 -0
  252. package/dist/l10n_bd_payroll/.kosh/schema.d.ts +2891 -0
  253. package/dist/l10n_bd_payroll/data.d.ts +47 -0
  254. package/dist/l10n_bd_payroll/data.js +347 -0
  255. package/dist/l10n_bd_payroll/i18n/bn.d.ts +2 -0
  256. package/dist/l10n_bd_payroll/i18n/bn.js +4 -0
  257. package/dist/l10n_bd_payroll/index.d.ts +4 -0
  258. package/dist/l10n_bd_payroll/index.js +26 -0
  259. package/dist/l10n_bd_payroll/module.d.ts +17294 -0
  260. package/dist/l10n_bd_payroll/module.js +86 -0
  261. package/dist/l10n_bd_pos/.kosh/bindings.d.ts +22 -0
  262. package/dist/{mrp_account → l10n_bd_pos}/.kosh/bindings.js +2 -2
  263. package/dist/l10n_bd_pos/.kosh/schema.d.ts +2341 -0
  264. package/dist/l10n_bd_pos/i18n/bn.d.ts +2 -0
  265. package/dist/l10n_bd_pos/i18n/bn.js +32 -0
  266. package/dist/l10n_bd_pos/index.d.ts +3 -0
  267. package/dist/l10n_bd_pos/index.js +111 -0
  268. package/dist/{mrp_account → l10n_bd_pos}/module.d.ts +9047 -6981
  269. package/dist/l10n_bd_pos/module.js +43 -0
  270. package/dist/l10n_in/.kosh/bindings.js +1 -1
  271. package/dist/l10n_in/.kosh/schema.d.ts +651 -10
  272. package/dist/l10n_in/i18n/bn.d.ts +2 -0
  273. package/dist/l10n_in/i18n/bn.js +95 -0
  274. package/dist/l10n_in/index.js +2 -0
  275. package/dist/l10n_in/module.d.ts +6778 -1580
  276. package/dist/l10n_in/module.js +25 -5
  277. package/dist/l10n_in/web/GstSettings.js +2 -2
  278. package/dist/mail/inbound/handle.js +8 -5
  279. package/dist/mail/inbound/models.js +5 -3
  280. package/dist/mail/index.d.ts +12 -1
  281. package/dist/mail/index.js +139 -2
  282. package/dist/mail/send.d.ts +56 -0
  283. package/dist/mail/send.js +394 -0
  284. package/dist/mail/web/SendButton.d.ts +9 -0
  285. package/dist/mail/web/SendButton.js +71 -0
  286. package/dist/mail/web/SendSelected.d.ts +8 -0
  287. package/dist/mail/web/SendSelected.js +33 -0
  288. package/dist/mail/web/index.d.ts +2 -0
  289. package/dist/mail/web/index.js +6 -0
  290. package/dist/mail/web/messages.js +36 -0
  291. package/dist/maintenance/.kosh/bindings.js +1 -1
  292. package/dist/maintenance/.kosh/schema.d.ts +2 -1
  293. package/dist/maintenance/i18n/bn.d.ts +2 -0
  294. package/dist/maintenance/i18n/bn.js +132 -0
  295. package/dist/maintenance/index.js +85 -0
  296. package/dist/maintenance/module.d.ts +25 -0
  297. package/dist/maintenance/module.js +27 -9
  298. package/dist/maintenance_stock/.kosh/bindings.js +1 -1
  299. package/dist/maintenance_stock/.kosh/schema.d.ts +374 -3
  300. package/dist/maintenance_stock/i18n/bn.d.ts +2 -0
  301. package/dist/maintenance_stock/i18n/bn.js +21 -0
  302. package/dist/maintenance_stock/index.js +2 -0
  303. package/dist/maintenance_stock/module.d.ts +6095 -1327
  304. package/dist/maintenance_stock/module.js +1 -0
  305. package/dist/modules-sdk/apps.d.ts +10 -0
  306. package/dist/modules-sdk/apps.js +32 -0
  307. package/dist/modules-sdk/index.d.ts +1 -1
  308. package/dist/modules-sdk/manifest.d.ts +105 -2
  309. package/dist/payment/.kosh/bindings.js +1 -1
  310. package/dist/payment/.kosh/schema.d.ts +653 -11
  311. package/dist/payment/i18n/bn.d.ts +2 -0
  312. package/dist/payment/i18n/bn.js +59 -0
  313. package/dist/payment/index.d.ts +41 -0
  314. package/dist/payment/index.js +216 -74
  315. package/dist/payment/module.d.ts +5353 -1675
  316. package/dist/payment/module.js +36 -6
  317. package/dist/payment/settle.js +11 -1
  318. package/dist/payroll/.kosh/bindings.d.ts +22 -0
  319. package/dist/payroll/.kosh/bindings.js +15 -0
  320. package/dist/payroll/.kosh/schema.d.ts +1448 -0
  321. package/dist/payroll/compute.d.ts +35 -0
  322. package/dist/payroll/compute.js +398 -0
  323. package/dist/payroll/demo.d.ts +55 -0
  324. package/dist/payroll/demo.js +121 -0
  325. package/dist/payroll/formula.d.ts +62 -0
  326. package/dist/payroll/formula.js +320 -0
  327. package/dist/payroll/i18n/bn.d.ts +2 -0
  328. package/dist/payroll/i18n/bn.js +206 -0
  329. package/dist/payroll/index.d.ts +230 -0
  330. package/dist/payroll/index.js +715 -0
  331. package/dist/{mrp → payroll}/module.d.ts +4294 -2624
  332. package/dist/payroll/module.js +606 -0
  333. package/dist/payroll/routes.d.ts +10 -0
  334. package/dist/payroll/routes.js +112 -0
  335. package/dist/payroll_account/.kosh/bindings.d.ts +22 -0
  336. package/dist/payroll_account/.kosh/bindings.js +15 -0
  337. package/dist/payroll_account/.kosh/schema.d.ts +2887 -0
  338. package/dist/payroll_account/i18n/bn.d.ts +2 -0
  339. package/dist/payroll_account/i18n/bn.js +10 -0
  340. package/dist/payroll_account/index.d.ts +3 -0
  341. package/dist/payroll_account/index.js +42 -0
  342. package/dist/payroll_account/module.d.ts +16738 -0
  343. package/dist/payroll_account/module.js +256 -0
  344. package/dist/permissions/approvals.d.ts +227 -12
  345. package/dist/permissions/approvals.js +1421 -67
  346. package/dist/permissions/index.d.ts +1 -1
  347. package/dist/permissions/index.js +2 -2
  348. package/dist/permissions/models.js +364 -13
  349. package/dist/permissions/routes.d.ts +9 -1
  350. package/dist/permissions/routes.js +72 -5
  351. package/dist/permissions/web/ApprovalPanel.d.ts +5 -4
  352. package/dist/permissions/web/ApprovalPanel.js +75 -11
  353. package/dist/permissions/web/index.d.ts +6 -4
  354. package/dist/permissions/web/index.js +13 -4
  355. package/dist/permissions/web/messages.d.ts +2 -0
  356. package/dist/permissions/web/messages.js +95 -0
  357. package/dist/portal/.kosh/bindings.js +1 -1
  358. package/dist/portal/.kosh/schema.d.ts +2 -1
  359. package/dist/portal/documents.d.ts +3 -0
  360. package/dist/portal/documents.js +21 -1
  361. package/dist/portal/i18n/bn.d.ts +2 -0
  362. package/dist/portal/i18n/bn.js +33 -0
  363. package/dist/portal/index.js +3 -1
  364. package/dist/portal/module.d.ts +20 -0
  365. package/dist/portal/routes-documents.js +76 -0
  366. package/dist/pos/.kosh/bindings.js +1 -1
  367. package/dist/pos/.kosh/schema.d.ts +652 -10
  368. package/dist/pos/i18n/bn.d.ts +2 -0
  369. package/dist/pos/i18n/bn.js +133 -0
  370. package/dist/pos/index.js +87 -1
  371. package/dist/pos/module.d.ts +6967 -1709
  372. package/dist/pos/module.js +49 -5
  373. package/dist/pos/routes.js +80 -7
  374. package/dist/pos/web/Register.js +2 -5
  375. package/dist/pos_stock/.kosh/bindings.js +1 -1
  376. package/dist/pos_stock/.kosh/schema.d.ts +822 -12
  377. package/dist/pos_stock/i18n/bn.d.ts +2 -0
  378. package/dist/pos_stock/i18n/bn.js +6 -0
  379. package/dist/pos_stock/index.js +2 -0
  380. package/dist/pos_stock/module.d.ts +11241 -2851
  381. package/dist/product/.kosh/bindings.js +1 -1
  382. package/dist/product/.kosh/schema.d.ts +203 -1
  383. package/dist/product/i18n/bn.d.ts +2 -0
  384. package/dist/product/i18n/bn.js +175 -0
  385. package/dist/product/index.d.ts +1 -0
  386. package/dist/product/index.js +301 -1
  387. package/dist/product/module.d.ts +1727 -0
  388. package/dist/product/module.js +67 -6
  389. package/dist/product/pricelist.d.ts +575 -0
  390. package/dist/product/pricelist.js +257 -0
  391. package/dist/product/variants.d.ts +908 -0
  392. package/dist/product/variants.js +251 -0
  393. package/dist/production/.kosh/bindings.d.ts +22 -0
  394. package/dist/production/.kosh/bindings.js +15 -0
  395. package/dist/production/.kosh/schema.d.ts +2345 -0
  396. package/dist/{mrp → production}/demo.d.ts +1 -1
  397. package/dist/{mrp → production}/demo.js +5 -5
  398. package/dist/production/depth.d.ts +790 -0
  399. package/dist/production/depth.js +262 -0
  400. package/dist/production/i18n/bn.d.ts +2 -0
  401. package/dist/production/i18n/bn.js +350 -0
  402. package/dist/production/index.d.ts +6 -0
  403. package/dist/production/index.js +1256 -0
  404. package/dist/production/module.d.ts +17327 -0
  405. package/dist/production/module.js +1333 -0
  406. package/dist/production/plan.d.ts +921 -0
  407. package/dist/production/plan.js +511 -0
  408. package/dist/production/schedule.d.ts +656 -0
  409. package/dist/production/schedule.js +309 -0
  410. package/dist/production_account/.kosh/bindings.d.ts +22 -0
  411. package/dist/production_account/.kosh/bindings.js +15 -0
  412. package/dist/production_account/.kosh/schema.d.ts +3496 -0
  413. package/dist/production_account/i18n/bn.d.ts +2 -0
  414. package/dist/production_account/i18n/bn.js +93 -0
  415. package/dist/production_account/index.d.ts +4 -0
  416. package/dist/production_account/index.js +246 -0
  417. package/dist/production_account/module.d.ts +1192 -0
  418. package/dist/{mrp_account → production_account}/module.js +72 -18
  419. package/dist/production_account/overhead.d.ts +1195 -0
  420. package/dist/production_account/overhead.js +554 -0
  421. package/dist/project/.kosh/bindings.js +1 -1
  422. package/dist/project/.kosh/schema.d.ts +735 -1
  423. package/dist/project/i18n/bn.d.ts +2 -0
  424. package/dist/project/i18n/bn.js +383 -0
  425. package/dist/project/index.d.ts +3 -1
  426. package/dist/project/index.js +1024 -14
  427. package/dist/project/module.d.ts +9912 -390
  428. package/dist/project/module.js +1590 -14
  429. package/dist/project/routes.d.ts +44 -0
  430. package/dist/project/routes.js +314 -0
  431. package/dist/project/schedule.d.ts +72 -0
  432. package/dist/project/schedule.js +192 -0
  433. package/dist/project/web/Refresh.d.ts +8 -0
  434. package/dist/project/web/Refresh.js +29 -0
  435. package/dist/project/web/Schedule.d.ts +9 -0
  436. package/dist/project/web/Schedule.js +93 -0
  437. package/dist/project/web/Structure.d.ts +8 -0
  438. package/dist/project/web/Structure.js +86 -0
  439. package/dist/project/web/Workload.d.ts +8 -0
  440. package/dist/project/web/Workload.js +43 -0
  441. package/dist/project/web/index.d.ts +14 -0
  442. package/dist/project/web/index.js +25 -0
  443. package/dist/project/web/messages.d.ts +2 -0
  444. package/dist/project/web/messages.js +85 -0
  445. package/dist/project_account/.kosh/bindings.d.ts +22 -0
  446. package/dist/project_account/.kosh/bindings.js +15 -0
  447. package/dist/project_account/.kosh/schema.d.ts +2763 -0
  448. package/dist/project_account/i18n/bn.d.ts +2 -0
  449. package/dist/project_account/i18n/bn.js +6 -0
  450. package/dist/project_account/index.d.ts +3 -0
  451. package/dist/project_account/index.js +29 -0
  452. package/dist/project_account/module.d.ts +15013 -0
  453. package/dist/project_account/module.js +76 -0
  454. package/dist/project_portal/.kosh/bindings.js +1 -1
  455. package/dist/project_portal/.kosh/schema.d.ts +735 -1
  456. package/dist/project_portal/i18n/bn.d.ts +2 -0
  457. package/dist/project_portal/i18n/bn.js +6 -0
  458. package/dist/project_portal/index.js +2 -0
  459. package/dist/project_portal/module.d.ts +4796 -87
  460. package/dist/project_purchase/.kosh/bindings.d.ts +22 -0
  461. package/dist/project_purchase/.kosh/bindings.js +15 -0
  462. package/dist/project_purchase/.kosh/schema.d.ts +3393 -0
  463. package/dist/project_purchase/i18n/bn.d.ts +2 -0
  464. package/dist/project_purchase/i18n/bn.js +4 -0
  465. package/dist/project_purchase/index.d.ts +9 -0
  466. package/dist/project_purchase/index.js +24 -0
  467. package/dist/project_purchase/module.d.ts +38613 -0
  468. package/dist/project_purchase/module.js +56 -0
  469. package/dist/purchase/.kosh/bindings.js +1 -1
  470. package/dist/purchase/.kosh/schema.d.ts +908 -5
  471. package/dist/purchase/comparison.d.ts +993 -0
  472. package/dist/purchase/comparison.js +415 -0
  473. package/dist/purchase/extras.d.ts +1509 -0
  474. package/dist/purchase/extras.js +445 -0
  475. package/dist/purchase/i18n/bn.d.ts +2 -0
  476. package/dist/purchase/i18n/bn.js +413 -0
  477. package/dist/purchase/index.d.ts +5 -0
  478. package/dist/purchase/index.js +1280 -4
  479. package/dist/purchase/module.d.ts +7147 -395
  480. package/dist/purchase/module.js +303 -10
  481. package/dist/purchase/request.d.ts +696 -0
  482. package/dist/purchase/request.js +446 -0
  483. package/dist/purchase/tender.d.ts +1208 -0
  484. package/dist/purchase/tender.js +507 -0
  485. package/dist/purchase/vendorprice.d.ts +340 -0
  486. package/dist/purchase/vendorprice.js +153 -0
  487. package/dist/purchase_account/.kosh/bindings.js +1 -1
  488. package/dist/purchase_account/.kosh/schema.d.ts +1209 -16
  489. package/dist/purchase_account/i18n/bn.d.ts +2 -0
  490. package/dist/purchase_account/i18n/bn.js +22 -0
  491. package/dist/purchase_account/index.js +20 -0
  492. package/dist/purchase_account/module.d.ts +12232 -2028
  493. package/dist/purchase_account/module.js +252 -9
  494. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  495. package/dist/purchase_portal/.kosh/schema.d.ts +908 -5
  496. package/dist/purchase_portal/i18n/bn.d.ts +2 -0
  497. package/dist/purchase_portal/i18n/bn.js +5 -0
  498. package/dist/purchase_portal/index.js +2 -0
  499. package/dist/purchase_portal/module.d.ts +6853 -352
  500. package/dist/purchase_stock/.kosh/bindings.js +1 -1
  501. package/dist/purchase_stock/.kosh/schema.d.ts +1096 -21
  502. package/dist/purchase_stock/i18n/bn.d.ts +2 -0
  503. package/dist/purchase_stock/i18n/bn.js +19 -0
  504. package/dist/purchase_stock/index.d.ts +2 -2
  505. package/dist/purchase_stock/index.js +38 -2
  506. package/dist/purchase_stock/module.d.ts +12930 -1612
  507. package/dist/purchase_stock/module.js +317 -9
  508. package/dist/purchase_stock/scorecard.d.ts +22 -0
  509. package/dist/purchase_stock/scorecard.js +77 -0
  510. package/dist/purchase_stock/web/Scorecard.d.ts +7 -0
  511. package/dist/purchase_stock/web/Scorecard.js +22 -0
  512. package/dist/purchase_stock/web/index.d.ts +11 -0
  513. package/dist/purchase_stock/web/index.js +16 -0
  514. package/dist/purchase_stock/web/messages.d.ts +2 -0
  515. package/dist/purchase_stock/web/messages.js +27 -0
  516. package/dist/purchase_stock_account/.kosh/bindings.js +1 -1
  517. package/dist/purchase_stock_account/.kosh/schema.d.ts +1444 -20
  518. package/dist/purchase_stock_account/i18n/bn.d.ts +2 -0
  519. package/dist/purchase_stock_account/i18n/bn.js +8 -0
  520. package/dist/purchase_stock_account/index.js +18 -0
  521. package/dist/purchase_stock_account/module.d.ts +7 -35346
  522. package/dist/purchase_stock_account/module.js +35 -8
  523. package/dist/recruitment/.kosh/bindings.js +1 -1
  524. package/dist/recruitment/.kosh/schema.d.ts +60 -1
  525. package/dist/recruitment/i18n/bn.d.ts +2 -0
  526. package/dist/recruitment/i18n/bn.js +93 -0
  527. package/dist/recruitment/index.js +64 -0
  528. package/dist/recruitment/module.d.ts +460 -8
  529. package/dist/recruitment/module.js +13 -4
  530. package/dist/reporting/documents.d.ts +5 -0
  531. package/dist/reporting/documents.js +11 -1
  532. package/dist/reporting/index.d.ts +1 -1
  533. package/dist/reporting/index.js +1 -1
  534. package/dist/reporting/web/CalendarView.d.ts +18 -5
  535. package/dist/reporting/web/CalendarView.js +163 -29
  536. package/dist/reporting/web/messages.js +18 -0
  537. package/dist/sale_account/.kosh/bindings.js +1 -1
  538. package/dist/sale_account/.kosh/schema.d.ts +763 -18
  539. package/dist/sale_account/i18n/bn.d.ts +2 -0
  540. package/dist/sale_account/i18n/bn.js +48 -0
  541. package/dist/sale_account/index.d.ts +1 -1
  542. package/dist/sale_account/index.js +102 -4
  543. package/dist/sale_account/module.d.ts +8241 -2052
  544. package/dist/sale_account/module.js +535 -46
  545. package/dist/sale_crm/.kosh/bindings.d.ts +22 -0
  546. package/dist/sale_crm/.kosh/bindings.js +15 -0
  547. package/dist/{mrp → sale_crm}/.kosh/schema.d.ts +391 -494
  548. package/dist/sale_crm/i18n/bn.d.ts +2 -0
  549. package/dist/sale_crm/i18n/bn.js +9 -0
  550. package/dist/sale_crm/index.d.ts +7 -0
  551. package/dist/sale_crm/index.js +104 -0
  552. package/dist/sale_crm/module.d.ts +5609 -0
  553. package/dist/sale_crm/module.js +73 -0
  554. package/dist/sale_portal/.kosh/bindings.js +1 -1
  555. package/dist/sale_portal/.kosh/schema.d.ts +292 -4
  556. package/dist/sale_portal/i18n/bn.d.ts +2 -0
  557. package/dist/sale_portal/i18n/bn.js +7 -0
  558. package/dist/sale_portal/index.js +10 -0
  559. package/dist/sale_portal/module.d.ts +2563 -292
  560. package/dist/sale_project/.kosh/bindings.js +1 -1
  561. package/dist/sale_project/.kosh/schema.d.ts +1025 -4
  562. package/dist/sale_project/i18n/bn.d.ts +2 -0
  563. package/dist/sale_project/i18n/bn.js +12 -0
  564. package/dist/sale_project/index.js +2 -0
  565. package/dist/sale_project/module.d.ts +9280 -762
  566. package/dist/sale_project/module.js +17 -1
  567. package/dist/sale_stock/.kosh/bindings.js +1 -1
  568. package/dist/sale_stock/.kosh/schema.d.ts +463 -6
  569. package/dist/sale_stock/i18n/bn.d.ts +2 -0
  570. package/dist/sale_stock/i18n/bn.js +15 -0
  571. package/dist/sale_stock/index.js +2 -0
  572. package/dist/sale_stock/module.d.ts +8355 -1334
  573. package/dist/sale_stock/module.js +20 -3
  574. package/dist/sales/.kosh/bindings.js +1 -1
  575. package/dist/sales/.kosh/schema.d.ts +292 -4
  576. package/dist/sales/i18n/bn.d.ts +2 -0
  577. package/dist/sales/i18n/bn.js +128 -0
  578. package/dist/sales/index.js +124 -5
  579. package/dist/sales/module.d.ts +3568 -633
  580. package/dist/sales/module.js +306 -11
  581. package/dist/server/api/duplicate.d.ts +14 -0
  582. package/dist/server/api/duplicate.js +75 -0
  583. package/dist/server/api/explain.d.ts +70 -0
  584. package/dist/server/api/explain.js +111 -0
  585. package/dist/server/api/metadata.d.ts +2 -0
  586. package/dist/server/api/metadata.js +9 -0
  587. package/dist/server/api/openapi.js +15 -0
  588. package/dist/server/api/rest.d.ts +1 -0
  589. package/dist/server/api/rest.js +76 -12
  590. package/dist/server/api/views.js +1 -1
  591. package/dist/server/apps.d.ts +16 -1
  592. package/dist/server/apps.js +164 -2
  593. package/dist/server/auth/base.js +2 -0
  594. package/dist/server/auth/service.d.ts +8 -0
  595. package/dist/server/auth/service.js +19 -0
  596. package/dist/server/events.d.ts +13 -0
  597. package/dist/server/events.js +22 -0
  598. package/dist/server/i18n.d.ts +27 -0
  599. package/dist/server/i18n.js +143 -0
  600. package/dist/server/index.d.ts +2 -0
  601. package/dist/server/index.js +2 -0
  602. package/dist/server/server.js +6 -0
  603. package/dist/settings/company.d.ts +3 -1
  604. package/dist/settings/company.js +26 -5
  605. package/dist/settings/routes.d.ts +10 -0
  606. package/dist/settings/routes.js +19 -3
  607. package/dist/settings/web/AppsPage.d.ts +3 -2
  608. package/dist/settings/web/AppsPage.js +26 -8
  609. package/dist/settings/web/CompaniesPage.js +91 -6
  610. package/dist/settings/web/messages.js +64 -0
  611. package/dist/sign/.kosh/bindings.d.ts +22 -0
  612. package/dist/{mrp → sign}/.kosh/bindings.js +2 -2
  613. package/dist/sign/.kosh/schema.d.ts +991 -0
  614. package/dist/sign/i18n/bn.d.ts +2 -0
  615. package/dist/sign/i18n/bn.js +97 -0
  616. package/dist/sign/index.d.ts +29 -0
  617. package/dist/sign/index.js +478 -0
  618. package/dist/sign/module.d.ts +4518 -0
  619. package/dist/sign/module.js +167 -0
  620. package/dist/sign/sign.d.ts +82 -0
  621. package/dist/sign/sign.js +474 -0
  622. package/dist/stock/.kosh/bindings.js +1 -1
  623. package/dist/stock/.kosh/schema.d.ts +374 -3
  624. package/dist/stock/demo.d.ts +2 -1
  625. package/dist/stock/demo.js +28 -1
  626. package/dist/stock/i18n/bn.d.ts +2 -0
  627. package/dist/stock/i18n/bn.js +311 -0
  628. package/dist/stock/index.d.ts +4 -1
  629. package/dist/stock/index.js +674 -6
  630. package/dist/stock/ledger.d.ts +78 -0
  631. package/dist/stock/ledger.js +582 -0
  632. package/dist/stock/module.d.ts +6142 -1380
  633. package/dist/stock/module.js +780 -26
  634. package/dist/stock/replenish.d.ts +38 -0
  635. package/dist/stock/replenish.js +191 -0
  636. package/dist/stock/returns.d.ts +157 -0
  637. package/dist/stock/returns.js +163 -0
  638. package/dist/stock/web/Ledger.d.ts +9 -0
  639. package/dist/stock/web/Ledger.js +108 -0
  640. package/dist/stock/web/Operations.d.ts +8 -0
  641. package/dist/stock/web/Operations.js +33 -0
  642. package/dist/stock/web/Replenishment.d.ts +8 -0
  643. package/dist/stock/web/Replenishment.js +84 -0
  644. package/dist/stock/web/ReturnButton.d.ts +7 -0
  645. package/dist/stock/web/ReturnButton.js +62 -0
  646. package/dist/stock/web/index.d.ts +7 -2
  647. package/dist/stock/web/index.js +15 -2
  648. package/dist/stock/web/messages.js +206 -0
  649. package/dist/stock_account/.kosh/bindings.js +1 -1
  650. package/dist/stock_account/.kosh/schema.d.ts +878 -12
  651. package/dist/stock_account/i18n/bn.d.ts +2 -0
  652. package/dist/stock_account/i18n/bn.js +73 -0
  653. package/dist/stock_account/index.d.ts +1 -0
  654. package/dist/stock_account/index.js +141 -3
  655. package/dist/stock_account/landed.d.ts +429 -0
  656. package/dist/stock_account/landed.js +335 -0
  657. package/dist/stock_account/module.d.ts +12143 -3391
  658. package/dist/stock_account/module.js +135 -6
  659. package/dist/stock_account/web/LandedPreview.d.ts +7 -0
  660. package/dist/stock_account/web/LandedPreview.js +23 -0
  661. package/dist/stock_account/web/index.d.ts +11 -0
  662. package/dist/stock_account/web/index.js +16 -0
  663. package/dist/stock_account/web/messages.d.ts +2 -0
  664. package/dist/stock_account/web/messages.js +27 -0
  665. package/dist/todo/i18n/bn.d.ts +2 -0
  666. package/dist/todo/i18n/bn.js +66 -0
  667. package/dist/todo/index.js +59 -0
  668. package/dist/todo/module.d.ts +5 -0
  669. package/dist/todo/module.js +5 -3
  670. package/dist/trade_finance/.kosh/bindings.js +1 -1
  671. package/dist/trade_finance/.kosh/schema.d.ts +2 -1
  672. package/dist/trade_finance/i18n/bn.d.ts +2 -0
  673. package/dist/trade_finance/i18n/bn.js +162 -0
  674. package/dist/trade_finance/index.js +95 -0
  675. package/dist/trade_finance/module.d.ts +26 -0
  676. package/dist/trade_finance/module.js +29 -8
  677. package/dist/trade_finance_account/.kosh/bindings.js +1 -1
  678. package/dist/trade_finance_account/.kosh/schema.d.ts +651 -10
  679. package/dist/trade_finance_account/i18n/bn.d.ts +2 -0
  680. package/dist/trade_finance_account/i18n/bn.js +27 -0
  681. package/dist/trade_finance_account/index.js +3 -0
  682. package/dist/trade_finance_account/module.d.ts +5129 -1509
  683. package/dist/trade_finance_account/module.js +2 -0
  684. package/dist/trade_finance_purchase/.kosh/bindings.js +1 -1
  685. package/dist/trade_finance_purchase/.kosh/schema.d.ts +908 -5
  686. package/dist/trade_finance_purchase/i18n/bn.d.ts +2 -0
  687. package/dist/trade_finance_purchase/i18n/bn.js +6 -0
  688. package/dist/trade_finance_purchase/index.js +2 -0
  689. package/dist/trade_finance_purchase/module.d.ts +6909 -408
  690. package/dist/trade_finance_sale/.kosh/bindings.js +1 -1
  691. package/dist/trade_finance_sale/.kosh/schema.d.ts +292 -4
  692. package/dist/trade_finance_sale/i18n/bn.d.ts +2 -0
  693. package/dist/trade_finance_sale/i18n/bn.js +6 -0
  694. package/dist/trade_finance_sale/index.js +2 -0
  695. package/dist/trade_finance_sale/module.d.ts +2602 -331
  696. package/dist/ui/api.d.ts +2 -0
  697. package/dist/ui/app.d.ts +9 -0
  698. package/dist/ui/app.js +47 -15
  699. package/dist/ui/components/AppIcon.js +9 -2
  700. package/dist/ui/components/Explain.d.ts +69 -0
  701. package/dist/ui/components/Explain.js +62 -0
  702. package/dist/ui/context.d.ts +8 -1
  703. package/dist/ui/context.js +34 -1
  704. package/dist/ui/i18n/messages.d.ts +40 -0
  705. package/dist/ui/i18n/messages.js +80 -0
  706. package/dist/ui/index.d.ts +2 -1
  707. package/dist/ui/index.js +1 -0
  708. package/dist/ui/registry.d.ts +14 -0
  709. package/dist/ui/registry.js +10 -0
  710. package/dist/ui/views/FormView.d.ts +6 -0
  711. package/dist/ui/views/FormView.js +51 -12
  712. package/dist/ui/views/KanbanView.js +9 -1
  713. package/dist/ui/views/ListView.d.ts +8 -0
  714. package/dist/ui/views/ListView.js +14 -5
  715. package/dist/ui/views/StatsRow.js +2 -1
  716. package/dist/ui/views/widgets.js +7 -1
  717. package/dist/views/extend.js +12 -0
  718. package/dist/views/types.d.ts +13 -2
  719. package/dist/views/validate.js +8 -1
  720. package/dist/website/.kosh/bindings.js +1 -1
  721. package/dist/website/.kosh/schema.d.ts +2 -1
  722. package/dist/website/i18n/bn.d.ts +2 -0
  723. package/dist/website/i18n/bn.js +113 -0
  724. package/dist/website/index.js +37 -0
  725. package/dist/website/module.d.ts +20 -0
  726. package/dist/website/module.js +30 -5
  727. package/dist/website_crm/.kosh/bindings.js +1 -1
  728. package/dist/website_crm/.kosh/schema.d.ts +3 -1
  729. package/dist/website_crm/i18n/bn.d.ts +2 -0
  730. package/dist/website_crm/i18n/bn.js +5 -0
  731. package/dist/website_crm/index.js +2 -0
  732. package/dist/website_crm/module.d.ts +41 -0
  733. package/dist/website_sale/.kosh/bindings.d.ts +22 -0
  734. package/dist/website_sale/.kosh/bindings.js +15 -0
  735. package/dist/website_sale/.kosh/schema.d.ts +2909 -0
  736. package/dist/website_sale/couriers.d.ts +50 -0
  737. package/dist/website_sale/couriers.js +59 -0
  738. package/dist/website_sale/demo.d.ts +7 -0
  739. package/dist/website_sale/demo.js +90 -0
  740. package/dist/website_sale/growth.d.ts +31 -0
  741. package/dist/website_sale/growth.js +163 -0
  742. package/dist/website_sale/i18n/bn.d.ts +2 -0
  743. package/dist/website_sale/i18n/bn.js +132 -0
  744. package/dist/website_sale/index.d.ts +49 -0
  745. package/dist/website_sale/index.js +620 -0
  746. package/dist/website_sale/module.d.ts +35182 -0
  747. package/dist/website_sale/module.js +419 -0
  748. package/dist/website_sale/shipments.d.ts +16 -0
  749. package/dist/website_sale/shipments.js +113 -0
  750. package/dist/website_sale/shop.d.ts +81 -0
  751. package/dist/website_sale/shop.js +662 -0
  752. package/dist/website_sale_stock/.kosh/bindings.d.ts +22 -0
  753. package/dist/website_sale_stock/.kosh/bindings.js +15 -0
  754. package/dist/website_sale_stock/.kosh/schema.d.ts +3400 -0
  755. package/dist/website_sale_stock/i18n/bn.d.ts +2 -0
  756. package/dist/website_sale_stock/i18n/bn.js +4 -0
  757. package/dist/website_sale_stock/index.d.ts +9 -0
  758. package/dist/website_sale_stock/index.js +22 -0
  759. package/dist/website_sale_stock/module.d.ts +11 -0
  760. package/dist/website_sale_stock/module.js +32 -0
  761. package/package.json +117 -8
  762. package/dist/mrp/index.d.ts +0 -3
  763. package/dist/mrp/index.js +0 -392
  764. package/dist/mrp/module.js +0 -695
  765. package/dist/mrp_account/index.d.ts +0 -3
  766. package/dist/mrp_account/index.js +0 -22
  767. /package/dist/{mrp → account_asset}/.kosh/bindings.d.ts +0 -0
  768. /package/dist/{mrp_account → account_budget}/.kosh/bindings.d.ts +0 -0
@@ -0,0 +1,1333 @@
1
+ /**
2
+ * The data layer of Production (RFC 0021): bills of materials with versions and effective dates, work centres with
3
+ * their hours and cost, and production orders that say what is required, available and short before they start and
4
+ * what they will cost. Orders move stock only through Stockroom: Confirm reserves the components in a consumption
5
+ * transfer (stock → Production), Produce validates it and the output transfer (Production → stock).
6
+ */
7
+ import { contactsShutro } from '../contacts/index.js';
8
+ import { messagingShutro } from '../messaging/index.js';
9
+ import { approvalGuard, permissionsShutro, requestApprovals } from '../permissions/index.js';
10
+ import { productShutro } from '../product/index.js';
11
+ import { emitting } from '../server/index.js';
12
+ import { orm } from '../server/orm.js';
13
+ import { defaultLocations, inProductUnit, stockShutro } from '../stock/index.js';
14
+ import { check, compute, defineModel, defineModule, extendModel, f, onchange, records, } from './.kosh/bindings.js';
15
+ import { BOM_KINDS, bomsNeedApproval, CompanyProduction, ineligible, ProductionBomCost, ProductionBomEligibility, ProductionBomSpec, ProductionProductSpec, ProductionShift, ProductionSpecParameter, } from './depth.js';
16
+ import { ProductionPlan, ProductionPlanLine, ProductionPlanMaterial } from './plan.js';
17
+ import { followOrders, ProductionSchedule, ProductionScheduleComponent } from './schedule.js';
18
+ const READ = ['read'];
19
+ const ALL = ['read', 'write', 'create', 'unlink'];
20
+ const zero = orm.toDecimal(0);
21
+ const money = (d) => d.toDecimalPlaces(2);
22
+ /** A date as `YYYY-MM-DD` (date-only values from PostgreSQL are local midnights: their local parts). */
23
+ const iso = (v) => {
24
+ if (!(v instanceof Date))
25
+ return String(v ?? '');
26
+ const pad = (n) => String(n).padStart(2, '0');
27
+ return `${v.getFullYear()}-${pad(v.getMonth() + 1)}-${pad(v.getDate())}`;
28
+ };
29
+ /** The bill of `product` valid on `date`: the latest that starts on or before it and has not ended. */
30
+ export async function validBom(env, product, date) {
31
+ const bills = await (await env.model('production.bom').search({ where: { product } })).load([
32
+ 'validFrom',
33
+ 'validTo',
34
+ 'state',
35
+ 'company',
36
+ ]);
37
+ // Active bills; drafts too when the company needs no approval (RFC 0031 §3.2).
38
+ const needs = new Map();
39
+ const usable = async (b) => {
40
+ if (b.state === 'active')
41
+ return true;
42
+ if (b.state !== 'draft')
43
+ return false;
44
+ let need = needs.get(b.company.id);
45
+ if (need === undefined) {
46
+ need = await bomsNeedApproval(env, b.company.id);
47
+ needs.set(b.company.id, need);
48
+ }
49
+ return !need;
50
+ };
51
+ const candidates = [];
52
+ for (const b of bills)
53
+ if (await usable(b))
54
+ candidates.push(b);
55
+ const valid = candidates
56
+ .filter((b) => (b.validFrom === null || iso(b.validFrom) <= date) &&
57
+ (b.validTo === null || iso(b.validTo) >= date))
58
+ .sort((a, b) => iso(b.validFrom).localeCompare(iso(a.validFrom)) || b.id - a.id);
59
+ return valid[0]?.id ?? null;
60
+ }
61
+ export const ProductionWorkcenter = defineModel('production.workcenter', {
62
+ label: 'Work centre',
63
+ recName: 'name',
64
+ order: ['name asc'],
65
+ archivable: true,
66
+ companyScoped: { required: true },
67
+ access: { 'production.user': READ, 'production.manager': [...ALL, 'export', 'import'] },
68
+ fields: {
69
+ name: f.char({ required: true }),
70
+ code: f.char(),
71
+ /** Hours it works a day (its capacity). */
72
+ hoursPerDay: f.decimal({ scale: 2, required: true, default: '8', label: 'Hours a day' }),
73
+ /** Labour and overhead per hour of work. */
74
+ costPerHour: f.decimal({ scale: 2, required: true, default: '0', label: 'Cost per hour' }),
75
+ batchMin: f.decimal({ scale: 4, label: 'Minimum batch', help: 'The smallest batch it runs.' }),
76
+ batchMax: f.decimal({
77
+ scale: 4,
78
+ label: 'Maximum batch',
79
+ help: 'The largest batch it runs at once.',
80
+ }),
81
+ oeeTarget: f.decimal({
82
+ scale: 2,
83
+ default: '85',
84
+ label: 'OEE target (%)',
85
+ help: 'The overall equipment effectiveness aimed at: availability × performance × quality.',
86
+ }),
87
+ setupMinutes: f.decimal({
88
+ scale: 2,
89
+ default: '0',
90
+ label: 'Setup (minutes)',
91
+ help: 'Before each batch.',
92
+ }),
93
+ cleanupMinutes: f.decimal({
94
+ scale: 2,
95
+ default: '0',
96
+ label: 'Cleanup (minutes)',
97
+ help: 'After each batch.',
98
+ }),
99
+ alternatives: f.many2many('production.workcenter', {
100
+ relation: 'production_wc_alternative',
101
+ label: 'Alternative work centres',
102
+ help: 'Where the same operation may run when this centre is full.',
103
+ }),
104
+ note: f.text(),
105
+ },
106
+ sqlConstraints: {
107
+ positive: {
108
+ check: orm.sql `hours_per_day > 0 AND cost_per_hour >= 0`,
109
+ message: 'A work centre works some hours a day, at a cost that is not negative.',
110
+ },
111
+ },
112
+ });
113
+ export const ProductionBom = defineModel('production.bom', {
114
+ label: 'Bill of materials',
115
+ recName: 'label',
116
+ order: ['product asc', 'validFrom desc', 'id desc'],
117
+ archivable: true,
118
+ companyScoped: { required: true },
119
+ tracking: ['quantity', 'validFrom', 'validTo', 'kind', 'state'],
120
+ access: { 'production.user': READ, 'production.manager': [...ALL, 'export', 'import'] },
121
+ fields: {
122
+ product: f.many2one('product.product', { required: true, ondelete: 'restrict' }),
123
+ /** How many of the product one bill makes, in the product's unit. */
124
+ quantity: f.decimal({ scale: 4, required: true, default: '1', label: 'Makes' }),
125
+ kind: f.selection(BOM_KINDS, {
126
+ required: true,
127
+ default: 'standard',
128
+ help: 'Standard: components in quantities. Formula: components as shares of the batch (ratios adding up to 100 %), scaled to any quantity. Packaging: a pack of the product made.',
129
+ }),
130
+ packSize: f.decimal({
131
+ scale: 4,
132
+ label: 'Pack size',
133
+ help: 'For a packaging bill: how much of the product one pack holds, in the product’s unit.',
134
+ }),
135
+ expectedLoss: f.decimal({
136
+ scale: 2,
137
+ default: '0',
138
+ label: 'Expected loss (%)',
139
+ help: 'What is lost making or packing a batch, as a share of it.',
140
+ }),
141
+ tolerance: f.decimal({
142
+ scale: 2,
143
+ default: '0',
144
+ label: 'Tolerance (±%)',
145
+ help: 'How far the quantity made may stray from the batch before it is questioned.',
146
+ }),
147
+ version: f.char({ required: true, default: 'v1' }),
148
+ /** From when this version is used (empty: always). */
149
+ validFrom: f.date({ label: 'Valid from' }),
150
+ validTo: f.date({ label: 'Valid until' }),
151
+ lines: f.one2many('production.bom.line', 'bom', { label: 'Components' }),
152
+ operations: f.one2many('production.bom.operation', 'bom'),
153
+ byproducts: f.one2many('production.bom.byproduct', 'bom', { label: 'By-products' }),
154
+ specs: f.one2many('production.bom.spec', 'bom', { label: 'Specifications' }),
155
+ costs: f.one2many('production.bom.cost', 'bom', { label: 'Costing history' }),
156
+ unitCost: f.decimal({
157
+ scale: 4,
158
+ store: true,
159
+ label: 'Unit cost',
160
+ help: 'The components (gross of their losses) at their cost and the operations at their work centres’ rates, per unit made.',
161
+ ...compute('production.bom', 'unitCost', [
162
+ 'quantity',
163
+ 'lines.grossQuantity',
164
+ 'lines.quantity',
165
+ 'lines.uom',
166
+ 'lines.product.standardPrice',
167
+ 'lines.product.uom',
168
+ 'operations.minutes',
169
+ 'operations.workcenter.costPerHour',
170
+ ], (bills) => {
171
+ for (const b of bills) {
172
+ let total = zero;
173
+ for (const l of b.lines) {
174
+ const qty = inProductUnit(l.grossQuantity ?? l.quantity, (l.uom ?? null), (l.product.uom ?? null));
175
+ total = total.plus(qty.times(l.product.standardPrice ?? zero));
176
+ }
177
+ for (const op of b.operations)
178
+ total = total.plus(op.minutes.div(60).times(op.workcenter.costPerHour));
179
+ b.unitCost = b.quantity.gt(0) ? total.div(b.quantity).toDecimalPlaces(4) : zero;
180
+ }
181
+ }),
182
+ }),
183
+ state: f.state({
184
+ states: { draft: 'Draft', review: 'Under review', active: 'Active', inactive: 'Inactive' },
185
+ initial: 'draft',
186
+ help: 'Drafted, submitted for review, approved into active (the version used), set inactive when superseded; a revision copies it to the next version. A company that needs no approval uses drafts as well.',
187
+ transitions: {
188
+ submit: {
189
+ from: ['draft'],
190
+ to: 'review',
191
+ guard: records('production.bom', async (bills) => {
192
+ const out = [];
193
+ for (const b of await bills.load({ lines: true, specs: true })) {
194
+ const lines = [...b.lines];
195
+ const ratios = lines.reduce((s, l) => s.plus(l.ratio ?? zero), zero);
196
+ if (lines.length === 0 ||
197
+ (b.kind === 'formula' && !ratios.eq(100)) ||
198
+ [...b.specs].some((s) => s.within === false))
199
+ out.push(b.id);
200
+ }
201
+ return out;
202
+ }),
203
+ message: 'A bill under review has components, formula ratios adding up to 100 % and every specification within range.',
204
+ after: records('production.bom', async (bills) => {
205
+ await requestApprovals(bills);
206
+ for (const b of await bills.load({}))
207
+ await bills.env.emit('production.bom.submitted', { id: b.id });
208
+ }),
209
+ },
210
+ approve: {
211
+ from: ['review'],
212
+ to: 'active',
213
+ guard: records('production.bom', (bills) => approvalGuard(bills)),
214
+ message: 'Waiting for approvals.',
215
+ after: records('production.bom', async (bills) => {
216
+ const env = bills.env;
217
+ for (const b of await bills.load({})) {
218
+ await env.asSuperuser({ reason: 'bill costing history' }, (su) => su.model('production.bom.cost').create([
219
+ {
220
+ bom: b.id,
221
+ date: env.today(),
222
+ unitCost: b.unitCost ?? zero,
223
+ note: 'Activated',
224
+ },
225
+ ]));
226
+ await env.emit('production.bom.activated', { id: b.id });
227
+ }
228
+ }),
229
+ },
230
+ reject: { from: ['review'], to: 'draft' },
231
+ deactivate: { from: ['active'], to: 'inactive' },
232
+ reactivate: { from: ['inactive'], to: 'active' },
233
+ /** A copy as the next version, in draft, to change and submit; the cost of this version kept. */
234
+ revise: {
235
+ from: ['active'],
236
+ to: 'active',
237
+ after: records('production.bom', async (bills) => {
238
+ const env = bills.env;
239
+ for (const id of bills.ids)
240
+ await reviseBom(env, id);
241
+ }),
242
+ },
243
+ },
244
+ extensible: true,
245
+ }),
246
+ note: f.text(),
247
+ label: f.char({
248
+ store: true,
249
+ label: 'Bill of materials',
250
+ ...compute('production.bom', 'label', ['product.name', 'version'], (bills) => {
251
+ for (const b of bills)
252
+ b.label = `${b.product.name} ${b.version}`;
253
+ }),
254
+ }),
255
+ },
256
+ sqlConstraints: {
257
+ positive: { check: orm.sql `quantity > 0`, message: 'A bill makes a positive quantity.' },
258
+ packaging: {
259
+ check: orm.sql `kind <> 'packaging' OR (pack_size IS NOT NULL AND pack_size > 0)`,
260
+ message: 'A packaging bill has a pack size.',
261
+ },
262
+ shares: {
263
+ check: orm.sql `expected_loss >= 0 AND expected_loss < 100 AND tolerance >= 0 AND tolerance <= 100`,
264
+ message: 'Loss and tolerance are percentages.',
265
+ },
266
+ period: {
267
+ check: orm.sql `valid_from IS NULL OR valid_to IS NULL OR valid_to >= valid_from`,
268
+ message: 'A bill ends on or after it starts.',
269
+ },
270
+ },
271
+ });
272
+ /** The next version's name: v1 → v2, 2026-A → 2026-A-2. */
273
+ function nextVersion(version) {
274
+ const m = /^(.*?)(\d+)$/.exec(version);
275
+ return m === null ? `${version}-2` : `${m[1]}${Number(m[2]) + 1}`;
276
+ }
277
+ /** A copy of a bill as its next version, in draft: lines, operations, by-products and specifications. */
278
+ export async function reviseBom(env, id) {
279
+ const [b] = await env
280
+ .model('production.bom')
281
+ .browse([id])
282
+ .load({ lines: true, operations: true, byproducts: true, specs: true, company: true });
283
+ if (b === undefined)
284
+ return null;
285
+ const made = await env.model('production.bom').create([
286
+ {
287
+ product: b.product.id,
288
+ company: b.company.id,
289
+ quantity: b.quantity,
290
+ kind: b.kind,
291
+ packSize: b.packSize,
292
+ expectedLoss: b.expectedLoss,
293
+ tolerance: b.tolerance,
294
+ version: nextVersion(b.version),
295
+ note: b.note,
296
+ lines: {
297
+ create: [...b.lines].map((l) => ({
298
+ product: l.product.id,
299
+ quantity: l.quantity,
300
+ uom: l.uom?.id ?? null,
301
+ ratio: l.ratio,
302
+ loss: l.loss,
303
+ })),
304
+ },
305
+ operations: {
306
+ create: [...b.operations].map((o) => ({
307
+ name: o.name,
308
+ workcenter: o.workcenter.id,
309
+ minutes: o.minutes,
310
+ })),
311
+ },
312
+ byproducts: {
313
+ create: [...b.byproducts].map((x) => ({
314
+ product: x.product.id,
315
+ quantity: x.quantity,
316
+ uom: x.uom?.id ?? null,
317
+ costShare: x.costShare,
318
+ })),
319
+ },
320
+ specs: {
321
+ create: [...b.specs].map((x) => ({
322
+ parameter: x.parameter.id,
323
+ minimum: x.minimum,
324
+ maximum: x.maximum,
325
+ })),
326
+ },
327
+ },
328
+ ]);
329
+ await env.emit('production.bom.revised', { id: b.id });
330
+ return made.ids[0] ?? null;
331
+ }
332
+ export const ProductionBomLine = defineModel('production.bom.line', {
333
+ label: 'Component',
334
+ order: ['id asc'],
335
+ access: { 'production.user': READ, 'production.manager': ALL },
336
+ fields: {
337
+ bom: f.many2one('production.bom', { required: true, ondelete: 'cascade' }),
338
+ product: f.many2one('product.product', { required: true, ondelete: 'restrict' }),
339
+ quantity: f.decimal({ scale: 4, required: true, default: '1' }),
340
+ uom: f.many2one('uom.uom', { label: 'Unit' }),
341
+ ratio: f.decimal({
342
+ scale: 2,
343
+ label: 'Ratio (%)',
344
+ help: 'For a formula: the component’s share of the batch; the ratios add up to 100 %.',
345
+ }),
346
+ loss: f.decimal({
347
+ scale: 2,
348
+ default: '0',
349
+ label: 'Loss (%)',
350
+ help: 'What is lost of this component in making (spillage, moisture): the gross quantity covers it.',
351
+ }),
352
+ netQuantity: f.decimal({
353
+ scale: 4,
354
+ store: true,
355
+ label: 'Net quantity',
356
+ help: 'What goes into the product: the quantity, or the ratio of the batch for a formula.',
357
+ ...compute('production.bom.line', 'netQuantity', ['quantity', 'ratio', 'bom.kind', 'bom.quantity'], (lines) => {
358
+ for (const l of lines)
359
+ l.netQuantity =
360
+ l.bom.kind === 'formula' && l.ratio !== null
361
+ ? l.bom.quantity.times(l.ratio).div(100).toDecimalPlaces(4)
362
+ : l.quantity;
363
+ }),
364
+ }),
365
+ grossQuantity: f.decimal({
366
+ scale: 4,
367
+ store: true,
368
+ label: 'Gross quantity',
369
+ help: 'The net quantity plus the loss: what the order consumes.',
370
+ ...compute('production.bom.line', 'grossQuantity', ['netQuantity', 'loss'], (lines) => {
371
+ for (const l of lines)
372
+ l.grossQuantity = (l.netQuantity ?? l.quantity)
373
+ .times(orm.toDecimal(100).plus(l.loss ?? zero))
374
+ .div(100)
375
+ .toDecimalPlaces(4);
376
+ }),
377
+ }),
378
+ },
379
+ onchange: {
380
+ product: onchange('production.bom.line', async (d) => {
381
+ const p = await d.record('product');
382
+ if (p?.uom)
383
+ d.set('uom', p.uom.id);
384
+ }),
385
+ },
386
+ sqlConstraints: {
387
+ positive: {
388
+ check: orm.sql `quantity > 0`,
389
+ message: 'A component is needed in a positive quantity.',
390
+ },
391
+ shares: {
392
+ check: orm.sql `(ratio IS NULL OR (ratio >= 0 AND ratio <= 100)) AND loss >= 0 AND loss < 100`,
393
+ message: 'Ratios and losses are percentages.',
394
+ },
395
+ },
396
+ constraints: {
397
+ // The company's eligibility rules for the bill's kind (RFC 0031 §3.2): refused lines say why.
398
+ eligible: {
399
+ ...check('production.bom.line', ['product', 'bom'], async (lines) => {
400
+ const env = lines.env;
401
+ const failing = [];
402
+ for (const l of await lines.load({ bom: { company: true }, product: true })) {
403
+ const why = await ineligible(env, l.bom.company.id, l.bom.kind, l.product.id);
404
+ if (why !== null) {
405
+ failing.push(l.id);
406
+ throw new orm.ValidationError('production.bom.line', ['product'], why, 'Choose an allowed component, or change the eligibility rules (Production › Configuration).');
407
+ }
408
+ }
409
+ return failing;
410
+ }),
411
+ message: 'This product is not an allowed component of the bill’s kind.',
412
+ },
413
+ notItself: {
414
+ ...check('production.bom.line', ['product', 'bom'], async (lines) => (await lines.load({ bom: true }))
415
+ .filter((l) => l.bom.product.id === l.product.id)
416
+ .map((l) => l.id)),
417
+ message: 'A product is not a component of itself.',
418
+ },
419
+ },
420
+ });
421
+ /** What else one bill makes (sawdust, offcuts), and the share of the order's cost it carries. */
422
+ export const ProductionBomByproduct = defineModel('production.bom.byproduct', {
423
+ label: 'By-product',
424
+ order: ['id asc'],
425
+ access: { 'production.user': READ, 'production.manager': ALL },
426
+ fields: {
427
+ bom: f.many2one('production.bom', { required: true, ondelete: 'cascade' }),
428
+ product: f.many2one('product.product', { required: true, ondelete: 'restrict' }),
429
+ quantity: f.decimal({ scale: 4, required: true, default: '1' }),
430
+ uom: f.many2one('uom.uom', { label: 'Unit' }),
431
+ /** Percent of the order's cost (the main product carries the rest). */
432
+ costShare: f.decimal({
433
+ scale: 2,
434
+ required: true,
435
+ default: '0',
436
+ label: 'Share of the cost (%)',
437
+ }),
438
+ },
439
+ sqlConstraints: {
440
+ positive: {
441
+ check: orm.sql `quantity > 0 AND cost_share >= 0 AND cost_share < 100`,
442
+ message: 'A by-product is made in a positive quantity, with a share of the cost under 100 %.',
443
+ },
444
+ },
445
+ constraints: {
446
+ sharesUnderAll: {
447
+ ...check('production.bom.byproduct', ['costShare', 'bom', 'product'], async (lines) => {
448
+ const failing = [];
449
+ for (const l of await lines.load({ bom: { byproducts: true } })) {
450
+ const total = [...l.bom.byproducts].reduce((s, b) => s.plus(b.costShare), zero);
451
+ if (total.gte(100) || l.product.id === l.bom.product.id)
452
+ failing.push(l.id);
453
+ }
454
+ return failing;
455
+ }),
456
+ message: 'By-products are other products than the one made, and their shares of the cost stay under 100 %.',
457
+ },
458
+ },
459
+ });
460
+ export const ProductionBomOperation = defineModel('production.bom.operation', {
461
+ label: 'Operation',
462
+ order: ['id asc'],
463
+ access: { 'production.user': READ, 'production.manager': ALL },
464
+ fields: {
465
+ bom: f.many2one('production.bom', { required: true, ondelete: 'cascade' }),
466
+ name: f.char({ required: true }),
467
+ workcenter: f.many2one('production.workcenter', { required: true, ondelete: 'restrict' }),
468
+ /** Minutes for what one bill makes. */
469
+ minutes: f.decimal({ scale: 2, required: true, default: '0' }),
470
+ },
471
+ });
472
+ export const ProductionOrderComponent = defineModel('production.order.component', {
473
+ label: 'Component need',
474
+ order: ['id asc'],
475
+ access: { 'production.user': READ, 'production.manager': READ },
476
+ fields: {
477
+ production: f.many2one('production.order', { required: true, ondelete: 'cascade' }),
478
+ product: f.many2one('product.product', { required: true, ondelete: 'restrict' }),
479
+ /** In the product's unit. */
480
+ required: f.decimal({ scale: 4, required: true, default: '0' }),
481
+ /** Available in the warehouse when the order was last checked. */
482
+ available: f.decimal({ scale: 4, required: true, default: '0' }),
483
+ short: f.decimal({
484
+ scale: 4,
485
+ store: true,
486
+ label: 'Short',
487
+ help: 'How much more is needed than the warehouse had available when the order was last checked.',
488
+ ...compute('production.order.component', 'short', ['required', 'available'], (lines) => {
489
+ for (const l of lines) {
490
+ const missing = l.required.minus(l.available);
491
+ l.short = missing.gt(0) ? missing : zero;
492
+ }
493
+ }),
494
+ }),
495
+ cost: f.decimal({
496
+ scale: 2,
497
+ store: true,
498
+ label: 'Cost',
499
+ help: 'The quantity needed at the component’s cost.',
500
+ ...compute('production.order.component', 'cost', ['required', 'product.standardPrice'], (lines) => {
501
+ for (const l of lines)
502
+ l.cost = money(l.required.times(l.product.standardPrice));
503
+ }),
504
+ }),
505
+ },
506
+ });
507
+ /**
508
+ * A bill's component needs for `quantity` of its product (in the components' own units, gross of losses) with what
509
+ * is available — on hand and not reserved — in the warehouse now.
510
+ */
511
+ export async function componentNeeds(env, bom, quantity, warehouse, company) {
512
+ const [bill] = await env
513
+ .model('production.bom')
514
+ .browse([bom])
515
+ .load({ lines: { product: { uom: true }, uom: true } });
516
+ if (bill === undefined || bill.quantity.lte(0))
517
+ return [];
518
+ const factor = quantity.div(bill.quantity);
519
+ const needs = new Map();
520
+ for (const l of bill.lines) {
521
+ const inUnit = inProductUnit((l.grossQuantity ?? l.quantity).times(factor), l.uom ?? null, l.product.uom ?? null);
522
+ needs.set(l.product.id, (needs.get(l.product.id) ?? zero).plus(inUnit));
523
+ }
524
+ const places = await defaultLocations(env, 'internal', warehouse, company);
525
+ const out = [];
526
+ for (const [product, required] of needs) {
527
+ let available = zero;
528
+ if (places.source !== null) {
529
+ const quants = await (await env.model('stock.quant').search({
530
+ where: {
531
+ product: orm.asId(product, 'product.product'),
532
+ location: { childOf: places.source },
533
+ },
534
+ })).load(['quantity', 'reserved']);
535
+ available = [...quants].reduce((s, q) => s.plus(q.quantity).minus(q.reserved ?? zero), zero);
536
+ }
537
+ out.push({
538
+ product,
539
+ required: required.toDecimalPlaces(4),
540
+ available: available.gt(0) ? available.toDecimalPlaces(4) : zero,
541
+ });
542
+ }
543
+ return out;
544
+ }
545
+ /**
546
+ * Lists the orders' component needs from their bills (in the product's unit, for the quantity ordered) with what is
547
+ * available in their warehouse now; as the system (component lines are not edited by hand).
548
+ */
549
+ async function explode(ids, env) {
550
+ await env.asSuperuser({ reason: 'production components' }, async (su) => {
551
+ const orders = await su.model('production.order').browse(ids).load({ components: true });
552
+ for (const o of orders) {
553
+ if (o.components.length > 0)
554
+ await o.components.unlink();
555
+ if (o.bom === null)
556
+ continue;
557
+ for (const n of await componentNeeds(su, o.bom.id, o.quantity, o.warehouse?.id ?? null, o.company.id))
558
+ await su.model('production.order.component').create([
559
+ {
560
+ production: o.id,
561
+ product: orm.asId(n.product, 'product.product'),
562
+ required: n.required,
563
+ available: n.available,
564
+ },
565
+ ]);
566
+ }
567
+ });
568
+ }
569
+ /** When a work centre's day starts (local time). */
570
+ export const DAY_STARTS_AT = 8;
571
+ /**
572
+ * Forward schedule of `minutes` of work at a centre working `hoursPerDay` from `DAY_STARTS_AT`, from `from`: what does
573
+ * not fit in a day continues the next day. Returns the start and the end.
574
+ */
575
+ export function schedule(from, minutes, hoursPerDay) {
576
+ const dayStart = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate(), DAY_STARTS_AT);
577
+ const dayEnd = (d) => new Date(dayStart(d).getTime() + hoursPerDay * 3_600_000);
578
+ let at = new Date(Math.max(from.getTime(), dayStart(from).getTime()));
579
+ if (at >= dayEnd(at))
580
+ at = dayStart(new Date(at.getTime() + 86_400_000));
581
+ const start = at;
582
+ let left = minutes * 60_000;
583
+ for (;;) {
584
+ const room = dayEnd(at).getTime() - at.getTime();
585
+ if (left <= room)
586
+ return { start, stop: new Date(at.getTime() + left) };
587
+ left -= room;
588
+ at = dayStart(new Date(dayStart(at).getTime() + 86_400_000));
589
+ }
590
+ }
591
+ /**
592
+ * A step of a production order at a work centre: planned from the bill's operation (its minutes for the quantity
593
+ * ordered), scheduled after what the centre already has, then started and finished with the minutes it took.
594
+ */
595
+ export const ProductionWorkorder = defineModel('production.workorder', {
596
+ label: 'Work order',
597
+ tracking: ['state', 'plannedStart', 'workcenter'],
598
+ recName: 'name',
599
+ order: ['plannedStart asc', 'id asc'],
600
+ companyScoped: { required: true },
601
+ access: {
602
+ 'production.user': ['read', 'write'],
603
+ 'production.manager': [...ALL, 'export'],
604
+ },
605
+ fields: {
606
+ production: f.many2one('production.order', { required: true, ondelete: 'cascade' }),
607
+ name: f.char({ required: true, label: 'Operation' }),
608
+ workcenter: f.many2one('production.workcenter', { required: true, ondelete: 'restrict' }),
609
+ plannedMinutes: f.decimal({ scale: 2, required: true, default: '0', label: 'Planned minutes' }),
610
+ plannedStart: f.datetime({ label: 'Planned start' }),
611
+ plannedStop: f.datetime({ label: 'Planned end' }),
612
+ startedAt: f.datetime({ label: 'Started' }),
613
+ finishedAt: f.datetime({ label: 'Finished' }),
614
+ /** The minutes it took (from start to finish). */
615
+ actualMinutes: f.decimal({ scale: 2, label: 'Minutes taken' }),
616
+ state: f.state({
617
+ states: { ready: 'Ready', progress: 'In progress', done: 'Done', cancel: 'Cancelled' },
618
+ initial: 'ready',
619
+ help: 'Started and finished at the work centre; the minutes between them are recorded.',
620
+ transitions: {
621
+ start: {
622
+ from: ['ready'],
623
+ to: 'progress',
624
+ after: records('production.workorder', async (orders) => {
625
+ await orders.write({ startedAt: new Date() });
626
+ }),
627
+ },
628
+ finish: {
629
+ from: ['progress'],
630
+ to: 'done',
631
+ after: records('production.workorder', async (orders) => {
632
+ const now = new Date();
633
+ for (const w of await orders.load({})) {
634
+ const began = w.startedAt instanceof Date ? w.startedAt : now;
635
+ await w.write({
636
+ finishedAt: now,
637
+ // Whole milliseconds, then minutes (Shutro takes no fractional JS numbers).
638
+ actualMinutes: orm
639
+ .toDecimal(now.getTime() - began.getTime())
640
+ .div(60_000)
641
+ .toDecimalPlaces(2),
642
+ });
643
+ }
644
+ }),
645
+ },
646
+ /** Done with its order (Produce), however far it got. */
647
+ close: { from: ['ready', 'progress'], to: 'done' },
648
+ cancel: { from: ['ready', 'progress'], to: 'cancel' },
649
+ },
650
+ }),
651
+ },
652
+ });
653
+ /**
654
+ * Makes the orders' work orders from their bills' operations and schedules them forward, step after step, each at
655
+ * its work centre after what the centre already has planned (from the order's date).
656
+ */
657
+ async function planWork(ids, env) {
658
+ await env.asSuperuser({ reason: 'production schedule' }, async (su) => {
659
+ const orders = await su
660
+ .model('production.order')
661
+ .browse(ids)
662
+ .load({ bom: { operations: { workcenter: true } } });
663
+ for (const o of orders) {
664
+ if (o.bom === null)
665
+ continue;
666
+ const factor = o.quantity.div(o.bom.quantity);
667
+ // The order's day, local (a date is read as text or as a local midnight).
668
+ const day = new Date(`${iso(o.date)}T00:00:00`);
669
+ let after = new Date(day.getFullYear(), day.getMonth(), day.getDate(), DAY_STARTS_AT);
670
+ for (const op of o.bom.operations) {
671
+ const minutes = op.minutes.times(factor).toDecimalPlaces(2);
672
+ // The centre is free after the last step it has that is not done or cancelled.
673
+ const busy = await (await su.model('production.workorder').search({
674
+ where: { workcenter: op.workcenter.id, state: { in: ['ready', 'progress'] } },
675
+ order: ['plannedStop desc'],
676
+ limit: 1,
677
+ })).load(['plannedStop']);
678
+ const free = [...busy][0]?.plannedStop;
679
+ const from = free instanceof Date && free > after ? free : after;
680
+ const { start, stop } = schedule(from, Number(minutes), Number(op.workcenter.hoursPerDay));
681
+ await su.model('production.workorder').create([
682
+ {
683
+ production: o.id,
684
+ company: o.company.id,
685
+ name: op.name,
686
+ workcenter: op.workcenter.id,
687
+ plannedMinutes: minutes,
688
+ plannedStart: start,
689
+ plannedStop: stop,
690
+ },
691
+ ]);
692
+ after = stop;
693
+ }
694
+ }
695
+ });
696
+ }
697
+ /** The places of an order: its warehouse's stock, and the Production location. */
698
+ /** A date-only value as `YYYY-MM-DD`. */
699
+ function dayOf(v) {
700
+ if (!(v instanceof Date))
701
+ return String(v ?? '').slice(0, 10);
702
+ const pad = (n) => String(n).padStart(2, '0');
703
+ return `${v.getFullYear()}-${pad(v.getMonth() + 1)}-${pad(v.getDate())}`;
704
+ }
705
+ async function placesOf(env, warehouse, company) {
706
+ const stock = (await defaultLocations(env, 'internal', warehouse, company)).source;
707
+ const production = (await env.model('stock.location').search({ where: { usage: 'production' }, limit: 1 })).ids[0];
708
+ if (stock === null || production === undefined)
709
+ throw new orm.ValidationError('production.order', ['warehouse'], 'There is no stock location or Production location to work from.', 'Check the warehouse in Stockroom › Configuration.');
710
+ return { stock, production };
711
+ }
712
+ export const ProductionOrder = defineModel('production.order', {
713
+ label: 'Production order',
714
+ recName: 'name',
715
+ order: ['date desc', 'id desc'],
716
+ companyScoped: { required: true },
717
+ tracking: ['state', 'quantity', 'bom'],
718
+ access: {
719
+ 'production.user': ['read', 'write', 'create'],
720
+ 'production.manager': [...ALL, 'export', 'import'],
721
+ },
722
+ fields: {
723
+ name: f.char({
724
+ required: true,
725
+ readonly: true,
726
+ sequence: { code: 'production.order', dateField: 'date' },
727
+ label: 'Reference',
728
+ }),
729
+ product: f.many2one('product.product', { required: true, ondelete: 'restrict' }),
730
+ quantity: f.decimal({ scale: 4, required: true, default: '1' }),
731
+ bom: f.many2one('production.bom', { ondelete: 'restrict', label: 'Bill of materials' }),
732
+ plan: f.many2one('production.plan', { ondelete: 'set null', label: 'Production plan' }),
733
+ schedule: f.many2one('production.schedule', { ondelete: 'set null', label: 'Batch' }),
734
+ stage: f.selection({ make: 'Make', pack: 'Pack' }, {
735
+ required: true,
736
+ default: 'make',
737
+ help: 'Make: the product from its components. Pack: packs of a made product, taken from its lot.',
738
+ }),
739
+ batch: f.char({
740
+ label: 'Batch number',
741
+ help: 'The lot the batch is produced into, for a tracked product.',
742
+ }),
743
+ sourceOrder: f.many2one('production.order', {
744
+ ondelete: 'set null',
745
+ label: 'Source order',
746
+ help: 'The make order a pack order packs.',
747
+ }),
748
+ sourceLot: f.many2one('stock.lot', {
749
+ ondelete: 'set null',
750
+ label: 'Source lot',
751
+ help: 'The lot a pack order takes its bulk from: the make order’s, once produced.',
752
+ }),
753
+ lot: f.many2one('stock.lot', { ondelete: 'set null', label: 'Produced lot' }),
754
+ producedQty: f.decimal({
755
+ scale: 4,
756
+ label: 'Produced',
757
+ help: 'What came out, set before producing; the quantity ordered when empty.',
758
+ }),
759
+ consumedQty: f.decimal({
760
+ scale: 4,
761
+ store: true,
762
+ label: 'Consumed',
763
+ help: 'For a pack order the bulk it packs; else the components in the product’s own unit (a formula’s shares).',
764
+ ...compute('production.order', 'consumedQty', [
765
+ 'stage',
766
+ 'components.required',
767
+ 'components.product.uom',
768
+ 'product.uom',
769
+ 'sourceOrder.product',
770
+ ], (orders) => {
771
+ for (const o of orders) {
772
+ const lines = [...o.components];
773
+ if (o.stage === 'pack') {
774
+ const bulk = lines.find((c) => c.product.id === (o.sourceOrder?.product.id ?? -1));
775
+ o.consumedQty = bulk?.required ?? zero;
776
+ continue;
777
+ }
778
+ const unit = o.product.uom?.id ?? null;
779
+ o.consumedQty = lines
780
+ .filter((c) => (c.product.uom?.id ?? null) === unit)
781
+ .reduce((s, c) => s.plus(c.required), zero);
782
+ }
783
+ }),
784
+ }),
785
+ lossQty: f.decimal({
786
+ scale: 4,
787
+ store: true,
788
+ label: 'Process loss',
789
+ help: 'Consumed less produced (packs × the pack size for a pack order), once done; negative: a gain.',
790
+ ...compute('production.order', 'lossQty', ['state', 'stage', 'consumedQty', 'producedQty', 'quantity', 'bom.packSize'], (orders) => {
791
+ for (const o of orders) {
792
+ if (o.state !== 'done') {
793
+ o.lossQty = zero;
794
+ continue;
795
+ }
796
+ const out = o.producedQty ?? o.quantity;
797
+ const bulk = o.stage === 'pack' ? out.times(o.bom?.packSize ?? orm.toDecimal(1)) : out;
798
+ o.lossQty = (o.consumedQty ?? zero).minus(bulk);
799
+ }
800
+ }),
801
+ }),
802
+ lossPercent: f.decimal({
803
+ scale: 2,
804
+ store: true,
805
+ label: 'Loss %',
806
+ help: 'The process loss as a share of what was consumed.',
807
+ ...compute('production.order', 'lossPercent', ['lossQty', 'consumedQty'], (orders) => {
808
+ for (const o of orders) {
809
+ const c = o.consumedQty ?? zero;
810
+ o.lossPercent = c.gt(0) ? (o.lossQty ?? zero).div(c).times(100).toDecimalPlaces(2) : zero;
811
+ }
812
+ }),
813
+ }),
814
+ yieldStatus: f.selection({ pending: 'Pending', within: 'Within tolerance', beyond: 'Beyond tolerance' }, {
815
+ store: true,
816
+ label: 'Yield',
817
+ help: 'Once done: the loss against the bill’s expected loss, within its tolerance or beyond it.',
818
+ ...compute('production.order', 'yieldStatus', ['state', 'lossPercent', 'consumedQty', 'bom.expectedLoss', 'bom.tolerance'], (orders) => {
819
+ for (const o of orders) {
820
+ if (o.state !== 'done' || (o.consumedQty ?? zero).lte(0)) {
821
+ o.yieldStatus = 'pending';
822
+ continue;
823
+ }
824
+ const diff = (o.lossPercent ?? zero).minus(o.bom?.expectedLoss ?? zero);
825
+ const gap = diff.lt(0) ? zero.minus(diff) : diff;
826
+ o.yieldStatus = gap.lte(o.bom?.tolerance ?? zero) ? 'within' : 'beyond';
827
+ }
828
+ }),
829
+ }),
830
+ actualStart: f.datetime({ label: 'Started' }),
831
+ actualEnd: f.datetime({ label: 'Finished' }),
832
+ operator: f.many2one('res.users', { ondelete: 'set null' }),
833
+ shift: f.many2one('production.shift', { ondelete: 'set null' }),
834
+ manufacturingDate: f.date({ label: 'Manufactured on' }),
835
+ expiryDate: f.date({
836
+ label: 'Expires on',
837
+ help: 'From the product’s shelf life, on the batch’s lot.',
838
+ }),
839
+ date: f.date({ required: true, default: (env) => env.today(), label: 'Planned for' }),
840
+ warehouse: f.many2one('stock.warehouse', { ondelete: 'restrict' }),
841
+ components: f.one2many('production.order.component', 'production'),
842
+ /** Scrap recorded on the order, with its reasons (Stockroom's scrap). */
843
+ scraps: f.one2many('stock.scrap', 'production'),
844
+ workorders: f.one2many('production.workorder', 'production', { label: 'Work orders' }),
845
+ consumption: f.many2one('stock.picking', {
846
+ ondelete: 'set null',
847
+ label: 'Components transfer',
848
+ }),
849
+ output: f.many2one('stock.picking', { ondelete: 'set null', label: 'Output transfer' }),
850
+ /** The components at their cost. */
851
+ materialCost: f.decimal({
852
+ scale: 2,
853
+ store: true,
854
+ label: 'Materials',
855
+ help: 'The components needed at their cost.',
856
+ ...compute('production.order', 'materialCost', ['components.cost'], (orders) => {
857
+ for (const o of orders)
858
+ o.materialCost = [...o.components].reduce((s, c) => s.plus(c.cost ?? zero), zero);
859
+ }),
860
+ }),
861
+ /** The operations at their work centres' cost per hour. */
862
+ operationCost: f.decimal({
863
+ scale: 2,
864
+ store: true,
865
+ label: 'Operations',
866
+ help: 'The operations’ minutes for the quantity ordered at their work centres’ cost per hour.',
867
+ ...compute('production.order', 'operationCost', [
868
+ 'quantity',
869
+ 'bom.quantity',
870
+ 'bom.operations.minutes',
871
+ 'bom.operations.workcenter.costPerHour',
872
+ ], (orders) => {
873
+ for (const o of orders) {
874
+ if (o.bom === null) {
875
+ o.operationCost = zero;
876
+ continue;
877
+ }
878
+ const factor = o.quantity.div(o.bom.quantity);
879
+ o.operationCost = money([...o.bom.operations].reduce((s, op) => s.plus(op.minutes.times(factor).div(60).times(op.workcenter.costPerHour)), zero));
880
+ }
881
+ }),
882
+ }),
883
+ totalCost: f.decimal({
884
+ scale: 2,
885
+ store: true,
886
+ label: 'Total cost',
887
+ help: 'The materials and the operations together.',
888
+ ...compute('production.order', 'totalCost', ['materialCost', 'operationCost'], (orders) => {
889
+ for (const o of orders)
890
+ o.totalCost = (o.materialCost ?? zero).plus(o.operationCost ?? zero);
891
+ }),
892
+ }),
893
+ unitCost: f.decimal({
894
+ scale: 4,
895
+ store: true,
896
+ label: 'Unit cost',
897
+ help: 'The total cost divided by the quantity made.',
898
+ ...compute('production.order', 'unitCost', ['totalCost', 'quantity'], (orders) => {
899
+ for (const o of orders)
900
+ o.unitCost = o.quantity.gt(0)
901
+ ? (o.totalCost ?? zero).div(o.quantity).toDecimalPlaces(4)
902
+ : zero;
903
+ }),
904
+ }),
905
+ /** The one chip: Draft, Waiting (components short), Ready, Done, Cancelled. */
906
+ status: f.selection({
907
+ draft: 'Draft',
908
+ waiting: 'Waiting',
909
+ ready: 'Ready',
910
+ progress: 'In progress',
911
+ done: 'Done',
912
+ cancelled: 'Cancelled',
913
+ }, {
914
+ store: true,
915
+ label: 'Status',
916
+ help: 'A confirmed order is ready once all its components are reserved, otherwise it is waiting.',
917
+ ...compute('production.order', 'status', ['state', 'consumption.availability'], (orders) => {
918
+ for (const o of orders)
919
+ o.status =
920
+ o.state === 'confirmed'
921
+ ? o.consumption?.availability === 'full'
922
+ ? 'ready'
923
+ : 'waiting'
924
+ : o.state === 'cancel'
925
+ ? 'cancelled'
926
+ : o.state;
927
+ }),
928
+ }),
929
+ note: f.text(),
930
+ state: f.state({
931
+ states: {
932
+ draft: 'Draft',
933
+ confirmed: 'Confirmed',
934
+ progress: 'In progress',
935
+ done: 'Done',
936
+ cancel: 'Cancelled',
937
+ },
938
+ initial: 'draft',
939
+ help: 'Confirmed, the components are reserved and the work orders scheduled; done, the product is made.',
940
+ transitions: {
941
+ /** Lists the components with what is available and short, and the cost, before anything starts. */
942
+ check: {
943
+ from: ['draft', 'confirmed'],
944
+ to: 'draft',
945
+ guard: records('production.order', async (orders) => (await orders.load({})).filter((o) => o.state !== 'draft')),
946
+ message: 'A confirmed order is checked with Check availability.',
947
+ after: records('production.order', async (orders) => {
948
+ await explode(orders.ids, orders.env);
949
+ }),
950
+ },
951
+ confirm: emitting('production.order.confirmed', {
952
+ from: ['draft'],
953
+ to: 'confirmed',
954
+ guard: records('production.order', async (orders) => {
955
+ const env = orders.env;
956
+ const out = [];
957
+ for (const o of await orders.load({ bom: true, company: true })) {
958
+ if (o.bom === null || o.bom.state === 'inactive' || o.bom.state === 'review') {
959
+ out.push(o.id);
960
+ continue;
961
+ }
962
+ if (o.bom.state === 'draft' && (await bomsNeedApproval(env, o.company.id)))
963
+ out.push(o.id);
964
+ }
965
+ return out;
966
+ }),
967
+ message: 'Choose a bill of materials that may be used: an active one (or a draft, when bills need no approval).',
968
+ // The components, their transfer to Production (reserved), and the work orders, scheduled.
969
+ after: records('production.order', async (orders) => {
970
+ await explode(orders.ids, orders.env);
971
+ await planWork(orders.ids, orders.env);
972
+ const env = orders.env;
973
+ for (const o of await env
974
+ .model('production.order')
975
+ .browse(orders.ids)
976
+ .load({ components: true, sourceOrder: true })) {
977
+ // Nothing to consume (labour only): no components transfer.
978
+ if (o.components.length === 0)
979
+ continue;
980
+ // A pack order takes its bulk from the make order's lot.
981
+ const bulkProduct = o.sourceOrder?.product.id ?? null;
982
+ const { stock, production } = await placesOf(env, o.warehouse?.id ?? null, o.company.id);
983
+ const made = await env.model('stock.picking').create([
984
+ {
985
+ kind: 'production',
986
+ company: o.company.id,
987
+ warehouse: o.warehouse?.id ?? null,
988
+ source: stock,
989
+ destination: production,
990
+ origin: o.name,
991
+ scheduledDate: o.date,
992
+ moves: {
993
+ create: [...o.components].map((c) => ({
994
+ product: c.product.id,
995
+ quantity: c.required,
996
+ restrictLot: o.sourceLot !== null && c.product.id === bulkProduct
997
+ ? o.sourceLot.id
998
+ : null,
999
+ })),
1000
+ },
1001
+ },
1002
+ ]);
1003
+ await made.confirm();
1004
+ await env
1005
+ .model('production.order')
1006
+ .browse([o.id])
1007
+ .write({ consumption: made.ids[0] ?? null });
1008
+ }
1009
+ }),
1010
+ }),
1011
+ /** "Check availability": reserve the components that came in since. */
1012
+ reserve: {
1013
+ from: ['confirmed'],
1014
+ to: 'confirmed',
1015
+ after: records('production.order', async (orders) => {
1016
+ const env = orders.env;
1017
+ for (const o of await env.model('production.order').browse(orders.ids).load({}))
1018
+ if (o.consumption !== null)
1019
+ await env.model('stock.picking').browse([o.consumption.id]).reserve();
1020
+ }),
1021
+ },
1022
+ /** Work begins: the actual start; the batch in progress. */
1023
+ start: {
1024
+ from: ['confirmed'],
1025
+ to: 'progress',
1026
+ after: records('production.order', async (orders) => {
1027
+ const env = orders.env;
1028
+ await env
1029
+ .model('production.order')
1030
+ .browse(orders.ids)
1031
+ .write({ actualStart: env.now() });
1032
+ await env.flush();
1033
+ for (const o of await orders.load({}))
1034
+ if (o.schedule !== null)
1035
+ await followOrders(env, o.schedule.id);
1036
+ }),
1037
+ },
1038
+ produce: emitting('production.order.done', {
1039
+ from: ['confirmed', 'progress'],
1040
+ to: 'done',
1041
+ guard: records('production.order', async (orders) => (await orders.load({ consumption: true })).filter((o) => o.consumption !== null && o.consumption.availability !== 'full')),
1042
+ message: 'Components are short: check availability, or wait for them to come in.',
1043
+ before: records('production.order', async (orders) => {
1044
+ const env = orders.env;
1045
+ // What is left of the work is done with the order.
1046
+ const open = await env.model('production.workorder').search({
1047
+ where: { production: { in: [...orders.ids] }, state: { in: ['ready', 'progress'] } },
1048
+ });
1049
+ if (open.length > 0)
1050
+ await open.close();
1051
+ for (const o of await env
1052
+ .model('production.order')
1053
+ .browse(orders.ids)
1054
+ .load({ product: true })) {
1055
+ if (o.consumption !== null)
1056
+ await env.model('stock.picking').browse([o.consumption.id]).validate();
1057
+ const { stock, production } = await placesOf(env, o.warehouse?.id ?? null, o.company.id);
1058
+ const [bill] = o.bom === null
1059
+ ? []
1060
+ : await env.model('production.bom').browse([o.bom.id]).load({ byproducts: true });
1061
+ const produced = o.producedQty ?? o.quantity;
1062
+ const factor = bill === undefined ? zero : produced.div(bill.quantity);
1063
+ // A tracked product is produced into the batch's lot (the order's reference without a batch).
1064
+ const tracking = o.product.tracking ?? 'none';
1065
+ const tracked = tracking !== 'none';
1066
+ const lotName = o.batch ?? o.name;
1067
+ const made = await env.model('stock.picking').create([
1068
+ {
1069
+ kind: 'production',
1070
+ company: o.company.id,
1071
+ warehouse: o.warehouse?.id ?? null,
1072
+ source: production,
1073
+ destination: stock,
1074
+ origin: o.name,
1075
+ moves: {
1076
+ create: [
1077
+ { product: o.product.id, quantity: produced },
1078
+ ...[...(bill?.byproducts ?? [])].map((b) => ({
1079
+ product: b.product.id,
1080
+ quantity: b.quantity.times(factor).toDecimalPlaces(4),
1081
+ uom: b.uom?.id ?? null,
1082
+ })),
1083
+ ],
1084
+ },
1085
+ ...(tracked
1086
+ ? {
1087
+ lotLines: {
1088
+ create: [{ product: o.product.id, lotName, quantity: produced }],
1089
+ },
1090
+ }
1091
+ : {}),
1092
+ },
1093
+ ]);
1094
+ // Linked before it is validated: what values it (production_account) finds its order.
1095
+ await env
1096
+ .model('production.order')
1097
+ .browse([o.id])
1098
+ .write({ output: made.ids[0] ?? null });
1099
+ await made.confirm();
1100
+ await made.validate();
1101
+ // The batch's lot with its expiry from the product's shelf life; the dates; the end of the work.
1102
+ const today = dayOf(env.today());
1103
+ let lot = null;
1104
+ let expiryDate = null;
1105
+ if (tracked) {
1106
+ lot =
1107
+ (await env
1108
+ .model('stock.lot')
1109
+ .search({ where: { product: o.product.id, name: lotName }, limit: 1 })).ids[0] ?? null;
1110
+ const days = o.product.shelfLifeDays ?? 0;
1111
+ if (lot !== null && days > 0) {
1112
+ const expiry = new Date(`${today}T00:00:00Z`);
1113
+ expiry.setUTCDate(expiry.getUTCDate() + days);
1114
+ expiryDate = expiry.toISOString().slice(0, 10);
1115
+ await env.model('stock.lot').browse([lot]).write({ expiryDate });
1116
+ }
1117
+ }
1118
+ await env.model('production.order').browse([o.id]).write({
1119
+ lot,
1120
+ producedQty: produced,
1121
+ manufacturingDate: today,
1122
+ expiryDate,
1123
+ actualEnd: env.now(),
1124
+ });
1125
+ await env.flush();
1126
+ // The pack orders waiting for this batch: confirmed now, their bulk taken from its lot.
1127
+ const packs = await env
1128
+ .model('production.order')
1129
+ .search({ where: { sourceOrder: o.id, state: 'draft' } });
1130
+ if (packs.length > 0) {
1131
+ await env.model('production.order').browse(packs.ids).write({ sourceLot: lot });
1132
+ await env.flush();
1133
+ await packs.confirm();
1134
+ await env.flush();
1135
+ }
1136
+ }
1137
+ }),
1138
+ after: records('production.order', async (orders) => {
1139
+ const env = orders.env;
1140
+ for (const o of await orders.load({}))
1141
+ if (o.schedule !== null)
1142
+ await followOrders(env, o.schedule.id);
1143
+ }),
1144
+ }),
1145
+ cancel: {
1146
+ from: ['draft', 'confirmed', 'progress'],
1147
+ to: 'cancel',
1148
+ after: records('production.order', async (orders) => {
1149
+ const env = orders.env;
1150
+ const open = await env.model('production.workorder').search({
1151
+ where: { production: { in: [...orders.ids] }, state: { in: ['ready', 'progress'] } },
1152
+ });
1153
+ for (const w of await open.load({}))
1154
+ await w.cancel();
1155
+ for (const o of await env
1156
+ .model('production.order')
1157
+ .browse(orders.ids)
1158
+ .load({ consumption: true }))
1159
+ if (o.consumption !== null &&
1160
+ o.consumption.state !== 'done' &&
1161
+ o.consumption.state !== 'cancel')
1162
+ await env.model('stock.picking').browse([o.consumption.id]).cancel();
1163
+ }),
1164
+ },
1165
+ redraft: { from: ['cancel'], to: 'draft' },
1166
+ },
1167
+ }),
1168
+ },
1169
+ onchange: {
1170
+ // The bill valid on the order's date, and the company's first warehouse.
1171
+ product: onchange('production.order', async (d, env) => {
1172
+ const product = d.get('product');
1173
+ if (product === null || product === undefined)
1174
+ return;
1175
+ d.set('bom', await validBom(env, product, String(d.get('date') ?? env.today())));
1176
+ if ((d.get('warehouse') ?? null) === null)
1177
+ d.set('warehouse', (await defaultLocations(env, 'internal')).warehouse);
1178
+ }),
1179
+ date: onchange('production.order', async (d, env) => {
1180
+ const product = d.get('product');
1181
+ if (product === null || product === undefined)
1182
+ return;
1183
+ d.set('bom', await validBom(env, product, String(d.get('date') ?? env.today())));
1184
+ }),
1185
+ },
1186
+ sqlConstraints: {
1187
+ positive: { check: orm.sql `quantity > 0`, message: 'An order makes a positive quantity.' },
1188
+ },
1189
+ constraints: {
1190
+ billOfTheProduct: {
1191
+ ...check('production.order', ['bom', 'product'], async (orders) => (await orders.load({ bom: true }))
1192
+ .filter((o) => o.bom !== null && o.bom.product.id !== o.product.id)
1193
+ .map((o) => o.id)),
1194
+ message: 'The bill of materials is for another product.',
1195
+ },
1196
+ },
1197
+ });
1198
+ /** Scrap recorded on a production order (a broken component, a faulty unit made), with its reason. */
1199
+ /** Production's product fields: the shelf life of what is made, the retail price printed on its label. */
1200
+ export const ProductProduction = extendModel('product.product', {
1201
+ fields: {
1202
+ shelfLifeDays: f.integer({
1203
+ label: 'Shelf life (days)',
1204
+ help: 'A produced lot expires this many days after its manufacturing date; empty: no expiry.',
1205
+ }),
1206
+ retailPrice: f.decimal({
1207
+ scale: 2,
1208
+ label: 'Maximum retail price',
1209
+ help: 'Printed on the batch label (MRP).',
1210
+ }),
1211
+ },
1212
+ });
1213
+ export const ScrapProduction = extendModel('stock.scrap', {
1214
+ fields: {
1215
+ production: f.many2one('production.order', { ondelete: 'set null', label: 'Production order' }),
1216
+ },
1217
+ });
1218
+ /**
1219
+ * Reorder rules that make (RFC 0024 §2.6): production orders not produced yet are on order; ordering makes one
1220
+ * production order per rule with the bill valid on its date, confirmed (its components reserved, its work planned).
1221
+ */
1222
+ export const ReorderMake = extendModel('stock.reorder', {
1223
+ overrides: {
1224
+ pending: async function (next) {
1225
+ const out = await next();
1226
+ const rules = this;
1227
+ const env = rules.env;
1228
+ for (const r of await rules.load({})) {
1229
+ const main = (await defaultLocations(env, 'internal', null, r.company.id))
1230
+ .warehouse;
1231
+ const orders = await (await env.model('production.order').search({
1232
+ where: {
1233
+ product: r.product.id,
1234
+ company: r.company.id,
1235
+ state: { in: ['draft', 'confirmed'] },
1236
+ },
1237
+ })).load({});
1238
+ const found = out.get(r.id) ?? [];
1239
+ for (const o of orders)
1240
+ if ((o.warehouse?.id ?? main) === r.warehouse.id)
1241
+ found.push({ quantity: o.quantity.toString(), document: o.name });
1242
+ out.set(r.id, found);
1243
+ }
1244
+ return out;
1245
+ },
1246
+ procure: async function (next, quantities) {
1247
+ const out = await next(quantities);
1248
+ const rules = this;
1249
+ const env = rules.env;
1250
+ for (const r of await rules.load({ product: true })) {
1251
+ const qty = quantities.get(r.id);
1252
+ if (r.method !== 'make' || qty === undefined)
1253
+ continue;
1254
+ const date = new Date(`${env.today()}T00:00:00Z`);
1255
+ date.setUTCDate(date.getUTCDate() + r.leadDays);
1256
+ const when = date.toISOString().slice(0, 10);
1257
+ const bom = await validBom(env, r.product.id, when);
1258
+ if (bom === null)
1259
+ throw new orm.ValidationError('stock.reorder', ['method'], `${r.product.name} has no bill of materials valid on ${when}.`, 'Add one in Production › Bills of materials, or replenish it another way.');
1260
+ const made = await env.model('production.order').create([
1261
+ {
1262
+ product: r.product.id,
1263
+ quantity: qty,
1264
+ bom,
1265
+ date: when,
1266
+ warehouse: r.warehouse.id,
1267
+ company: r.company.id,
1268
+ note: 'Replenishment',
1269
+ },
1270
+ ]);
1271
+ await made.confirm();
1272
+ const [order] = await made.load({});
1273
+ out.set(r.id, order?.name ?? '');
1274
+ }
1275
+ return out;
1276
+ },
1277
+ },
1278
+ });
1279
+ export const productionShutro = defineModule({
1280
+ name: 'production',
1281
+ version: '0.3.0',
1282
+ depends: [productShutro, stockShutro, contactsShutro, messagingShutro, permissionsShutro],
1283
+ groups: [
1284
+ orm.defineGroup({ code: 'production.user', label: 'Production user', implies: ['stock.user'] }),
1285
+ orm.defineGroup({
1286
+ code: 'production.manager',
1287
+ label: 'Production manager',
1288
+ implies: ['production.user'],
1289
+ }),
1290
+ ],
1291
+ sequences: [
1292
+ orm.defineSequence({
1293
+ code: 'production.order',
1294
+ prefix: 'MO/{year}/',
1295
+ padding: 5,
1296
+ reset: 'yearly',
1297
+ }),
1298
+ orm.defineSequence({
1299
+ code: 'production.plan',
1300
+ prefix: 'PLAN/{year}/',
1301
+ padding: 5,
1302
+ reset: 'yearly',
1303
+ }),
1304
+ orm.defineSequence({
1305
+ code: 'production.schedule',
1306
+ prefix: 'BATCH/{year}/',
1307
+ padding: 5,
1308
+ reset: 'yearly',
1309
+ }),
1310
+ ],
1311
+ models: [
1312
+ ProductionWorkcenter,
1313
+ ProductionBom,
1314
+ ProductionBomLine,
1315
+ ProductionBomByproduct,
1316
+ ProductionBomOperation,
1317
+ ProductionOrder,
1318
+ ProductionOrderComponent,
1319
+ ProductionWorkorder,
1320
+ ProductionSpecParameter,
1321
+ ProductionProductSpec,
1322
+ ProductionBomSpec,
1323
+ ProductionBomCost,
1324
+ ProductionBomEligibility,
1325
+ ProductionShift,
1326
+ ProductionPlan,
1327
+ ProductionPlanLine,
1328
+ ProductionPlanMaterial,
1329
+ ProductionSchedule,
1330
+ ProductionScheduleComponent,
1331
+ ],
1332
+ extensions: [ScrapProduction, ReorderMake, CompanyProduction, ProductProduction],
1333
+ });