shutro-erp 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/dist/account_asset/.kosh/bindings.d.ts +22 -0
  2. package/dist/account_asset/.kosh/bindings.js +15 -0
  3. package/dist/account_asset/.kosh/schema.d.ts +1755 -0
  4. package/dist/account_asset/board.d.ts +61 -0
  5. package/dist/account_asset/board.js +86 -0
  6. package/dist/account_asset/books.d.ts +42 -0
  7. package/dist/account_asset/books.js +398 -0
  8. package/dist/account_asset/demo.d.ts +7 -0
  9. package/dist/account_asset/demo.js +72 -0
  10. package/dist/account_asset/index.d.ts +9 -0
  11. package/dist/account_asset/index.js +335 -0
  12. package/dist/account_asset/module.d.ts +7622 -0
  13. package/dist/account_asset/module.js +230 -0
  14. package/dist/account_budget/.kosh/bindings.d.ts +22 -0
  15. package/dist/account_budget/.kosh/bindings.js +15 -0
  16. package/dist/account_budget/.kosh/schema.d.ts +1767 -0
  17. package/dist/account_budget/budget.d.ts +5 -0
  18. package/dist/account_budget/budget.js +142 -0
  19. package/dist/account_budget/demo.d.ts +6 -0
  20. package/dist/account_budget/demo.js +30 -0
  21. package/dist/account_budget/index.d.ts +4 -0
  22. package/dist/account_budget/index.js +223 -0
  23. package/dist/account_budget/module.d.ts +13863 -0
  24. package/dist/account_budget/module.js +167 -0
  25. package/dist/account_portal/.kosh/bindings.js +1 -1
  26. package/dist/account_portal/.kosh/schema.d.ts +123 -4
  27. package/dist/account_portal/module.d.ts +146 -0
  28. package/dist/accounting/.kosh/bindings.js +1 -1
  29. package/dist/accounting/.kosh/schema.d.ts +123 -4
  30. package/dist/accounting/index.d.ts +1 -1
  31. package/dist/accounting/index.js +131 -3
  32. package/dist/accounting/module.d.ts +296 -0
  33. package/dist/accounting/module.js +32 -1
  34. package/dist/approvals/.kosh/bindings.d.ts +22 -0
  35. package/dist/approvals/.kosh/bindings.js +15 -0
  36. package/dist/approvals/.kosh/schema.d.ts +611 -0
  37. package/dist/approvals/index.d.ts +18 -0
  38. package/dist/approvals/index.js +302 -0
  39. package/dist/approvals/mails.d.ts +25 -0
  40. package/dist/approvals/mails.js +82 -0
  41. package/dist/approvals/module.d.ts +1 -0
  42. package/dist/approvals/module.js +12 -0
  43. package/dist/approvals/web/History.d.ts +4 -0
  44. package/dist/approvals/web/History.js +13 -0
  45. package/dist/approvals/web/Inbox.d.ts +9 -0
  46. package/dist/approvals/web/Inbox.js +114 -0
  47. package/dist/approvals/web/Mine.d.ts +7 -0
  48. package/dist/approvals/web/Mine.js +27 -0
  49. package/dist/approvals/web/index.d.ts +13 -0
  50. package/dist/approvals/web/index.js +22 -0
  51. package/dist/approvals/web/messages.d.ts +2 -0
  52. package/dist/approvals/web/messages.js +119 -0
  53. package/dist/approvals/web/shared.d.ts +59 -0
  54. package/dist/approvals/web/shared.js +46 -0
  55. package/dist/attendance/.kosh/bindings.d.ts +22 -0
  56. package/dist/attendance/.kosh/bindings.js +15 -0
  57. package/dist/attendance/.kosh/schema.d.ts +1048 -0
  58. package/dist/attendance/days.d.ts +96 -0
  59. package/dist/attendance/days.js +430 -0
  60. package/dist/attendance/demo.d.ts +7 -0
  61. package/dist/attendance/demo.js +58 -0
  62. package/dist/attendance/index.d.ts +4 -0
  63. package/dist/attendance/index.js +513 -0
  64. package/dist/attendance/module.d.ts +3969 -0
  65. package/dist/attendance/module.js +412 -0
  66. package/dist/attendance/routes.d.ts +16 -0
  67. package/dist/attendance/routes.js +266 -0
  68. package/dist/attendance/web/Check.d.ts +8 -0
  69. package/dist/attendance/web/Check.js +40 -0
  70. package/dist/attendance/web/Kiosk.d.ts +8 -0
  71. package/dist/attendance/web/Kiosk.js +62 -0
  72. package/dist/attendance/web/PinButton.d.ts +7 -0
  73. package/dist/attendance/web/PinButton.js +29 -0
  74. package/dist/attendance/web/index.d.ts +13 -0
  75. package/dist/attendance/web/index.js +22 -0
  76. package/dist/attendance/web/messages.d.ts +2 -0
  77. package/dist/attendance/web/messages.js +61 -0
  78. package/dist/cli/init.js +32 -0
  79. package/dist/cli/templates.d.ts +1 -1
  80. package/dist/cli/templates.js +89 -0
  81. package/dist/contacts/module.js +13 -2
  82. package/dist/crm/.kosh/bindings.js +1 -1
  83. package/dist/crm/.kosh/schema.d.ts +2 -1
  84. package/dist/crm/module.d.ts +2 -0
  85. package/dist/crm/module.js +2 -1
  86. package/dist/documents/.kosh/bindings.d.ts +22 -0
  87. package/dist/documents/.kosh/bindings.js +15 -0
  88. package/dist/documents/.kosh/schema.d.ts +862 -0
  89. package/dist/documents/demo.d.ts +6 -0
  90. package/dist/documents/demo.js +22 -0
  91. package/dist/documents/documents.d.ts +98 -0
  92. package/dist/documents/documents.js +318 -0
  93. package/dist/documents/index.d.ts +30 -0
  94. package/dist/documents/index.js +442 -0
  95. package/dist/documents/module.d.ts +2781 -0
  96. package/dist/documents/module.js +214 -0
  97. package/dist/documents/web/Versions.d.ts +7 -0
  98. package/dist/documents/web/Versions.js +45 -0
  99. package/dist/documents/web/index.d.ts +11 -0
  100. package/dist/documents/web/index.js +16 -0
  101. package/dist/documents/web/messages.d.ts +2 -0
  102. package/dist/documents/web/messages.js +21 -0
  103. package/dist/event_account/.kosh/bindings.js +1 -1
  104. package/dist/event_account/.kosh/schema.d.ts +123 -4
  105. package/dist/event_account/module.d.ts +146 -0
  106. package/dist/expense/.kosh/bindings.d.ts +22 -0
  107. package/dist/expense/.kosh/bindings.js +15 -0
  108. package/dist/expense/.kosh/schema.d.ts +1299 -0
  109. package/dist/expense/demo.d.ts +7 -0
  110. package/dist/expense/demo.js +69 -0
  111. package/dist/expense/index.d.ts +3 -0
  112. package/dist/expense/index.js +495 -0
  113. package/dist/expense/module.d.ts +4747 -0
  114. package/dist/expense/module.js +647 -0
  115. package/dist/expense_account/.kosh/bindings.d.ts +22 -0
  116. package/dist/expense_account/.kosh/bindings.js +15 -0
  117. package/dist/expense_account/.kosh/schema.d.ts +1936 -0
  118. package/dist/expense_account/index.d.ts +3 -0
  119. package/dist/expense_account/index.js +74 -0
  120. package/dist/expense_account/module.d.ts +9153 -0
  121. package/dist/expense_account/module.js +318 -0
  122. package/dist/helpdesk/.kosh/bindings.d.ts +22 -0
  123. package/dist/helpdesk/.kosh/bindings.js +15 -0
  124. package/dist/helpdesk/.kosh/schema.d.ts +885 -0
  125. package/dist/helpdesk/demo.d.ts +7 -0
  126. package/dist/helpdesk/demo.js +65 -0
  127. package/dist/helpdesk/hours.d.ts +17 -0
  128. package/dist/helpdesk/hours.js +94 -0
  129. package/dist/helpdesk/index.d.ts +10 -0
  130. package/dist/helpdesk/index.js +461 -0
  131. package/dist/helpdesk/module.d.ts +2940 -0
  132. package/dist/helpdesk/module.js +326 -0
  133. package/dist/helpdesk/routes.d.ts +12 -0
  134. package/dist/helpdesk/routes.js +132 -0
  135. package/dist/helpdesk/sla.d.ts +37 -0
  136. package/dist/helpdesk/sla.js +226 -0
  137. package/dist/hr/index.d.ts +1 -1
  138. package/dist/hr/index.js +1 -1
  139. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  140. package/dist/l10n_bd/.kosh/schema.d.ts +123 -4
  141. package/dist/l10n_bd/module.d.ts +146 -0
  142. package/dist/l10n_bd_payroll/.kosh/bindings.d.ts +22 -0
  143. package/dist/l10n_bd_payroll/.kosh/bindings.js +15 -0
  144. package/dist/l10n_bd_payroll/.kosh/schema.d.ts +2312 -0
  145. package/dist/l10n_bd_payroll/data.d.ts +47 -0
  146. package/dist/l10n_bd_payroll/data.js +347 -0
  147. package/dist/l10n_bd_payroll/index.d.ts +4 -0
  148. package/dist/l10n_bd_payroll/index.js +24 -0
  149. package/dist/l10n_bd_payroll/module.d.ts +12932 -0
  150. package/dist/l10n_bd_payroll/module.js +86 -0
  151. package/dist/l10n_in/.kosh/bindings.js +1 -1
  152. package/dist/l10n_in/.kosh/schema.d.ts +123 -4
  153. package/dist/l10n_in/module.d.ts +147 -0
  154. package/dist/mail/inbound/handle.js +6 -1
  155. package/dist/maintenance_stock/.kosh/bindings.js +1 -1
  156. package/dist/maintenance_stock/.kosh/schema.d.ts +77 -2
  157. package/dist/maintenance_stock/module.d.ts +788 -66
  158. package/dist/modules-sdk/index.d.ts +1 -1
  159. package/dist/modules-sdk/manifest.d.ts +39 -2
  160. package/dist/mrp/.kosh/bindings.js +1 -1
  161. package/dist/mrp/.kosh/schema.d.ts +77 -2
  162. package/dist/mrp/index.d.ts +1 -1
  163. package/dist/mrp/index.js +2 -1
  164. package/dist/mrp/module.d.ts +781 -54
  165. package/dist/mrp/module.js +64 -2
  166. package/dist/mrp_account/.kosh/bindings.js +1 -1
  167. package/dist/mrp_account/.kosh/schema.d.ts +198 -5
  168. package/dist/mrp_account/module.d.ts +4852 -2541
  169. package/dist/payment/.kosh/bindings.js +1 -1
  170. package/dist/payment/.kosh/schema.d.ts +125 -5
  171. package/dist/payment/index.d.ts +41 -0
  172. package/dist/payment/index.js +131 -74
  173. package/dist/payment/module.d.ts +210 -6
  174. package/dist/payment/module.js +19 -3
  175. package/dist/payment/settle.js +10 -1
  176. package/dist/payroll/.kosh/bindings.d.ts +22 -0
  177. package/dist/payroll/.kosh/bindings.js +15 -0
  178. package/dist/payroll/.kosh/schema.d.ts +1390 -0
  179. package/dist/payroll/compute.d.ts +35 -0
  180. package/dist/payroll/compute.js +398 -0
  181. package/dist/payroll/demo.d.ts +55 -0
  182. package/dist/payroll/demo.js +121 -0
  183. package/dist/payroll/formula.d.ts +62 -0
  184. package/dist/payroll/formula.js +320 -0
  185. package/dist/payroll/index.d.ts +230 -0
  186. package/dist/payroll/index.js +713 -0
  187. package/dist/payroll/module.d.ts +9390 -0
  188. package/dist/payroll/module.js +606 -0
  189. package/dist/payroll/routes.d.ts +10 -0
  190. package/dist/payroll/routes.js +112 -0
  191. package/dist/payroll_account/.kosh/bindings.d.ts +22 -0
  192. package/dist/payroll_account/.kosh/bindings.js +15 -0
  193. package/dist/payroll_account/.kosh/schema.d.ts +2308 -0
  194. package/dist/payroll_account/index.d.ts +3 -0
  195. package/dist/payroll_account/index.js +40 -0
  196. package/dist/payroll_account/module.d.ts +12396 -0
  197. package/dist/payroll_account/module.js +256 -0
  198. package/dist/permissions/approvals.d.ts +131 -9
  199. package/dist/permissions/approvals.js +662 -57
  200. package/dist/permissions/index.d.ts +1 -1
  201. package/dist/permissions/index.js +2 -2
  202. package/dist/permissions/models.js +191 -12
  203. package/dist/permissions/routes.d.ts +6 -1
  204. package/dist/permissions/routes.js +37 -4
  205. package/dist/permissions/web/ApprovalPanel.d.ts +5 -4
  206. package/dist/permissions/web/ApprovalPanel.js +39 -13
  207. package/dist/permissions/web/index.d.ts +6 -4
  208. package/dist/permissions/web/index.js +13 -4
  209. package/dist/permissions/web/messages.d.ts +2 -0
  210. package/dist/permissions/web/messages.js +43 -0
  211. package/dist/portal/index.js +1 -1
  212. package/dist/pos/.kosh/bindings.js +1 -1
  213. package/dist/pos/.kosh/schema.d.ts +123 -4
  214. package/dist/pos/index.js +4 -0
  215. package/dist/pos/module.d.ts +147 -0
  216. package/dist/pos_stock/.kosh/bindings.js +1 -1
  217. package/dist/pos_stock/.kosh/schema.d.ts +198 -5
  218. package/dist/pos_stock/module.d.ts +914 -46
  219. package/dist/product/.kosh/bindings.js +1 -1
  220. package/dist/product/.kosh/schema.d.ts +2 -1
  221. package/dist/product/index.js +13 -0
  222. package/dist/product/module.d.ts +2 -0
  223. package/dist/product/module.js +2 -1
  224. package/dist/project/.kosh/bindings.js +1 -1
  225. package/dist/project/.kosh/schema.d.ts +672 -1
  226. package/dist/project/index.d.ts +3 -1
  227. package/dist/project/index.js +901 -14
  228. package/dist/project/module.d.ts +9892 -390
  229. package/dist/project/module.js +1530 -13
  230. package/dist/project/routes.d.ts +44 -0
  231. package/dist/project/routes.js +314 -0
  232. package/dist/project/schedule.d.ts +72 -0
  233. package/dist/project/schedule.js +192 -0
  234. package/dist/project/web/Refresh.d.ts +8 -0
  235. package/dist/project/web/Refresh.js +29 -0
  236. package/dist/project/web/Schedule.d.ts +9 -0
  237. package/dist/project/web/Schedule.js +93 -0
  238. package/dist/project/web/Structure.d.ts +8 -0
  239. package/dist/project/web/Structure.js +86 -0
  240. package/dist/project/web/Workload.d.ts +8 -0
  241. package/dist/project/web/Workload.js +43 -0
  242. package/dist/project/web/index.d.ts +14 -0
  243. package/dist/project/web/index.js +25 -0
  244. package/dist/project/web/messages.d.ts +2 -0
  245. package/dist/project/web/messages.js +85 -0
  246. package/dist/project_account/.kosh/bindings.d.ts +22 -0
  247. package/dist/project_account/.kosh/bindings.js +15 -0
  248. package/dist/project_account/.kosh/schema.d.ts +2241 -0
  249. package/dist/project_account/index.d.ts +3 -0
  250. package/dist/project_account/index.js +27 -0
  251. package/dist/project_account/module.d.ts +11539 -0
  252. package/dist/project_account/module.js +76 -0
  253. package/dist/project_portal/.kosh/bindings.js +1 -1
  254. package/dist/project_portal/.kosh/schema.d.ts +672 -1
  255. package/dist/project_portal/module.d.ts +4798 -129
  256. package/dist/project_purchase/.kosh/bindings.d.ts +22 -0
  257. package/dist/project_purchase/.kosh/bindings.js +15 -0
  258. package/dist/project_purchase/.kosh/schema.d.ts +2320 -0
  259. package/dist/project_purchase/index.d.ts +9 -0
  260. package/dist/project_purchase/index.js +22 -0
  261. package/dist/project_purchase/module.d.ts +25147 -0
  262. package/dist/project_purchase/module.js +56 -0
  263. package/dist/purchase/.kosh/bindings.js +1 -1
  264. package/dist/purchase/.kosh/schema.d.ts +97 -4
  265. package/dist/purchase/module.d.ts +13 -2
  266. package/dist/purchase/module.js +10 -1
  267. package/dist/purchase_account/.kosh/bindings.js +1 -1
  268. package/dist/purchase_account/.kosh/schema.d.ts +126 -6
  269. package/dist/purchase_account/index.js +1 -0
  270. package/dist/purchase_account/module.d.ts +176 -1
  271. package/dist/purchase_account/module.js +13 -2
  272. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  273. package/dist/purchase_portal/.kosh/schema.d.ts +97 -4
  274. package/dist/purchase_portal/module.d.ts +6 -1
  275. package/dist/purchase_stock/.kosh/bindings.js +1 -1
  276. package/dist/purchase_stock/.kosh/schema.d.ts +174 -5
  277. package/dist/purchase_stock/index.d.ts +1 -1
  278. package/dist/purchase_stock/index.js +9 -2
  279. package/dist/purchase_stock/module.d.ts +840 -68
  280. package/dist/purchase_stock/module.js +242 -6
  281. package/dist/purchase_stock_account/.kosh/bindings.js +1 -1
  282. package/dist/purchase_stock_account/.kosh/schema.d.ts +203 -7
  283. package/dist/purchase_stock_account/module.d.ts +8163 -5514
  284. package/dist/purchase_stock_account/module.js +11 -5
  285. package/dist/recruitment/.kosh/bindings.js +1 -1
  286. package/dist/recruitment/.kosh/schema.d.ts +2 -1
  287. package/dist/recruitment/module.d.ts +2 -0
  288. package/dist/recruitment/module.js +2 -1
  289. package/dist/sale_account/.kosh/bindings.js +1 -1
  290. package/dist/sale_account/.kosh/schema.d.ts +123 -4
  291. package/dist/sale_account/module.d.ts +147 -0
  292. package/dist/sale_portal/.kosh/bindings.js +1 -1
  293. package/dist/sale_portal/.kosh/schema.d.ts +2 -1
  294. package/dist/sale_portal/module.d.ts +1 -0
  295. package/dist/sale_project/.kosh/bindings.js +1 -1
  296. package/dist/sale_project/.kosh/schema.d.ts +673 -1
  297. package/dist/sale_project/module.d.ts +4800 -129
  298. package/dist/sale_project/module.js +17 -1
  299. package/dist/sale_stock/.kosh/bindings.js +1 -1
  300. package/dist/sale_stock/.kosh/schema.d.ts +77 -2
  301. package/dist/sale_stock/module.d.ts +789 -66
  302. package/dist/sales/.kosh/bindings.js +1 -1
  303. package/dist/sales/.kosh/schema.d.ts +2 -1
  304. package/dist/sales/index.js +4 -0
  305. package/dist/sales/module.d.ts +1 -0
  306. package/dist/sign/.kosh/bindings.d.ts +22 -0
  307. package/dist/sign/.kosh/bindings.js +15 -0
  308. package/dist/sign/.kosh/schema.d.ts +990 -0
  309. package/dist/sign/index.d.ts +14 -0
  310. package/dist/sign/index.js +350 -0
  311. package/dist/sign/module.d.ts +4478 -0
  312. package/dist/sign/module.js +158 -0
  313. package/dist/sign/sign.d.ts +82 -0
  314. package/dist/sign/sign.js +474 -0
  315. package/dist/stock/.kosh/bindings.js +1 -1
  316. package/dist/stock/.kosh/schema.d.ts +77 -2
  317. package/dist/stock/demo.d.ts +2 -1
  318. package/dist/stock/demo.js +28 -1
  319. package/dist/stock/index.d.ts +2 -1
  320. package/dist/stock/index.js +233 -4
  321. package/dist/stock/module.d.ts +2363 -837
  322. package/dist/stock/module.js +569 -14
  323. package/dist/stock/replenish.d.ts +38 -0
  324. package/dist/stock/replenish.js +176 -0
  325. package/dist/stock/web/Replenishment.d.ts +8 -0
  326. package/dist/stock/web/Replenishment.js +84 -0
  327. package/dist/stock/web/index.d.ts +4 -2
  328. package/dist/stock/web/index.js +6 -2
  329. package/dist/stock/web/messages.js +52 -0
  330. package/dist/stock_account/.kosh/bindings.js +1 -1
  331. package/dist/stock_account/.kosh/schema.d.ts +198 -5
  332. package/dist/stock_account/index.js +10 -0
  333. package/dist/stock_account/module.d.ts +969 -101
  334. package/dist/trade_finance/index.js +2 -0
  335. package/dist/trade_finance/module.js +2 -0
  336. package/dist/trade_finance_account/.kosh/bindings.js +1 -1
  337. package/dist/trade_finance_account/.kosh/schema.d.ts +123 -4
  338. package/dist/trade_finance_account/index.js +1 -0
  339. package/dist/trade_finance_account/module.d.ts +146 -0
  340. package/dist/trade_finance_account/module.js +1 -0
  341. package/dist/trade_finance_purchase/.kosh/bindings.js +1 -1
  342. package/dist/trade_finance_purchase/.kosh/schema.d.ts +97 -4
  343. package/dist/trade_finance_purchase/module.d.ts +6 -1
  344. package/dist/trade_finance_sale/.kosh/bindings.js +1 -1
  345. package/dist/trade_finance_sale/.kosh/schema.d.ts +2 -1
  346. package/dist/trade_finance_sale/module.d.ts +1 -0
  347. package/dist/ui/app.js +24 -12
  348. package/dist/ui/components/AppIcon.js +8 -1
  349. package/dist/ui/i18n/messages.d.ts +4 -0
  350. package/dist/ui/i18n/messages.js +8 -0
  351. package/dist/ui/views/FormView.js +4 -2
  352. package/dist/ui/views/KanbanView.js +9 -1
  353. package/dist/ui/views/ListView.js +4 -2
  354. package/dist/ui/views/widgets.js +7 -1
  355. package/dist/website/index.js +1 -0
  356. package/dist/website/module.js +1 -0
  357. package/dist/website_crm/.kosh/bindings.js +1 -1
  358. package/dist/website_crm/.kosh/schema.d.ts +2 -1
  359. package/dist/website_crm/module.d.ts +1 -0
  360. package/dist/website_sale/.kosh/bindings.d.ts +22 -0
  361. package/dist/website_sale/.kosh/bindings.js +15 -0
  362. package/dist/website_sale/.kosh/schema.d.ts +2284 -0
  363. package/dist/website_sale/couriers.d.ts +50 -0
  364. package/dist/website_sale/couriers.js +59 -0
  365. package/dist/website_sale/demo.d.ts +7 -0
  366. package/dist/website_sale/demo.js +90 -0
  367. package/dist/website_sale/growth.d.ts +31 -0
  368. package/dist/website_sale/growth.js +163 -0
  369. package/dist/website_sale/index.d.ts +49 -0
  370. package/dist/website_sale/index.js +538 -0
  371. package/dist/website_sale/module.d.ts +22027 -0
  372. package/dist/website_sale/module.js +397 -0
  373. package/dist/website_sale/shipments.d.ts +16 -0
  374. package/dist/website_sale/shipments.js +113 -0
  375. package/dist/website_sale/shop.d.ts +81 -0
  376. package/dist/website_sale/shop.js +649 -0
  377. package/dist/website_sale_stock/.kosh/bindings.d.ts +22 -0
  378. package/dist/website_sale_stock/.kosh/bindings.js +15 -0
  379. package/dist/website_sale_stock/.kosh/schema.d.ts +2681 -0
  380. package/dist/website_sale_stock/index.d.ts +9 -0
  381. package/dist/website_sale_stock/index.js +20 -0
  382. package/dist/website_sale_stock/module.d.ts +24998 -0
  383. package/dist/website_sale_stock/module.js +28 -0
  384. package/package.json +83 -2
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Projects' routes (RFC 0026 step 3):
3
+ *
4
+ * - `GET /api/v1/projects/workload?from=YYYY-MM-DD&weeks=12` each person's allocation week by week across the
5
+ * projects they are members of (active projects only): the highest total on any day of the week, with the projects
6
+ * behind it. Projects managers see everyone; others see themselves.
7
+ * - `POST /api/v1/projects/refresh` `{ projects? }` Projects managers: the booked and committed costs (from
8
+ * the bridges) and the health of active projects (these, or all), now; hourly too. The next of each repeating task
9
+ * done is made then too.
10
+ * - `GET /api/v1/projects/:id/schedule` the project's tasks with their dates, baseline, float and the
11
+ * critical path (as the caller sees them)
12
+ * - `POST /api/v1/projects/:id/schedule/apply` tasks that start before what they wait for is finished move later,
13
+ * their length kept (who may change the tasks)
14
+ */
15
+ import { type ModuleContext } from '../server/index.js';
16
+ import type { Env } from './.kosh/bindings.js';
17
+ /** The Monday on or before `date` (YYYY-MM-DD). */
18
+ export declare function mondayOf(date: string): string;
19
+ export interface WorkloadRow {
20
+ readonly user: number;
21
+ readonly name: string;
22
+ /** The week's peak allocation, in %. */
23
+ readonly weeks: readonly number[];
24
+ readonly projects: readonly {
25
+ readonly id: number;
26
+ readonly name: string;
27
+ readonly allocation: number;
28
+ readonly from: string | null;
29
+ readonly to: string | null;
30
+ }[];
31
+ }
32
+ export declare function workload(env: Env, from: string, weeks: number): Promise<{
33
+ weeks: string[];
34
+ people: WorkloadRow[];
35
+ }>;
36
+ /**
37
+ * The booked and committed costs and the health of the active projects (`ids`, or all of the company), as the
38
+ * system: the bridges' figures through `moneyOf()`, then the worst of schedule, late milestones and cost against the
39
+ * company's thresholds. Returns how many were refreshed.
40
+ */
41
+ export declare function refreshProjects(su: Env, ids?: readonly number[]): Promise<number>;
42
+ /** `date` moved by `n` days, weeks or months. */
43
+ export declare function shiftDate(date: string, n: number, unit: string): string;
44
+ export declare function registerProjectRoutes(context: ModuleContext): void;
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Projects' routes (RFC 0026 step 3):
3
+ *
4
+ * - `GET /api/v1/projects/workload?from=YYYY-MM-DD&weeks=12` each person's allocation week by week across the
5
+ * projects they are members of (active projects only): the highest total on any day of the week, with the projects
6
+ * behind it. Projects managers see everyone; others see themselves.
7
+ * - `POST /api/v1/projects/refresh` `{ projects? }` Projects managers: the booked and committed costs (from
8
+ * the bridges) and the health of active projects (these, or all), now; hourly too. The next of each repeating task
9
+ * done is made then too.
10
+ * - `GET /api/v1/projects/:id/schedule` the project's tasks with their dates, baseline, float and the
11
+ * critical path (as the caller sees them)
12
+ * - `POST /api/v1/projects/:id/schedule/apply` tasks that start before what they wait for is finished move later,
13
+ * their length kept (who may change the tasks)
14
+ */
15
+ import { chatterContext } from '../messaging/index.js';
16
+ import { HttpProblem } from '../server/index.js';
17
+ import { configureProjectChatter, judgeHealth, peakAllocation, } from './module.js';
18
+ import { applySlips, scheduleOf } from './schedule.js';
19
+ const DAY = 86_400_000;
20
+ const iso = (t) => new Date(t).toISOString().slice(0, 10);
21
+ /** The Monday on or before `date` (YYYY-MM-DD). */
22
+ export function mondayOf(date) {
23
+ const t = Date.parse(`${date}T00:00:00Z`);
24
+ const weekday = (new Date(t).getUTCDay() + 6) % 7; // Monday 0
25
+ return iso(t - weekday * DAY);
26
+ }
27
+ export async function workload(env, from, weeks) {
28
+ const start = Date.parse(`${mondayOf(from)}T00:00:00Z`);
29
+ const mondays = Array.from({ length: weeks }, (_, i) => iso(start + i * 7 * DAY));
30
+ const last = iso(start + weeks * 7 * DAY - DAY);
31
+ const everyone = env.administrator || env.groups.has('project.manager');
32
+ const first = mondays[0] ?? last;
33
+ const where = {
34
+ 'project.active': true,
35
+ ...(everyone ? {} : { user: env.userId }),
36
+ };
37
+ const all = await env.asSuperuser({ reason: 'workload' }, async (su) => (await su
38
+ .model('project.member')
39
+ .search({ where: where })
40
+ .then((r) => r.load({ user: ['name'], project: ['name'] }))).map((m) => ({
41
+ user: m.user.id,
42
+ name: String(m.user.name),
43
+ project: m.project.id,
44
+ projectName: String(m.project.name),
45
+ allocation: m.allocation,
46
+ from: m.dateFrom === null ? null : String(m.dateFrom),
47
+ to: m.dateTo === null ? null : String(m.dateTo),
48
+ })));
49
+ // Memberships that touch the weeks shown.
50
+ const members = all.filter((m) => (m.from === null || m.from <= last) && (m.to === null || m.to >= first));
51
+ const byUser = new Map();
52
+ for (const m of members)
53
+ byUser.set(m.user, [...(byUser.get(m.user) ?? []), m]);
54
+ const people = [...byUser.values()]
55
+ .map((rows) => ({
56
+ user: rows[0]?.user ?? 0,
57
+ name: rows[0]?.name ?? '',
58
+ weeks: mondays.map((monday) => peakAllocation(rows, monday, iso(Date.parse(`${monday}T00:00:00Z`) + 6 * DAY))),
59
+ projects: rows.map((r) => ({
60
+ id: r.project,
61
+ name: r.projectName,
62
+ allocation: r.allocation,
63
+ from: r.from,
64
+ to: r.to,
65
+ })),
66
+ }))
67
+ .sort((a, b) => a.name.localeCompare(b.name));
68
+ return { weeks: mondays, people };
69
+ }
70
+ /** A date-only value as `YYYY-MM-DD` (PostgreSQL dates may read as local midnights). */
71
+ function isoDate(v) {
72
+ if (v === null || v === undefined || v === '')
73
+ return null;
74
+ if (!(v instanceof Date))
75
+ return String(v).slice(0, 10);
76
+ const pad = (n) => String(n).padStart(2, '0');
77
+ return `${v.getFullYear()}-${pad(v.getMonth() + 1)}-${pad(v.getDate())}`;
78
+ }
79
+ /**
80
+ * The booked and committed costs and the health of the active projects (`ids`, or all of the company), as the
81
+ * system: the bridges' figures through `moneyOf()`, then the worst of schedule, late milestones and cost against the
82
+ * company's thresholds. Returns how many were refreshed.
83
+ */
84
+ export async function refreshProjects(su, ids) {
85
+ const found = await su.model('project.project').search({
86
+ where: { active: true, ...(ids === undefined ? {} : { id: { in: ids } }) },
87
+ });
88
+ if (found.ids.length === 0)
89
+ return 0;
90
+ const projects = await found.load({ company: true });
91
+ const money = await su.model('project.project').browse(found.ids).moneyOf();
92
+ const today = isoDate(su.today()) ?? new Date().toISOString().slice(0, 10);
93
+ const late = new Map();
94
+ for (const t of await su
95
+ .model('project.task')
96
+ .search({
97
+ where: {
98
+ project: { in: found.ids },
99
+ milestone: true,
100
+ done: false,
101
+ deadline: { lt: today },
102
+ },
103
+ })
104
+ .then((r) => r.load(['project'])))
105
+ late.set(t.project.id, (late.get(t.project.id) ?? 0) + 1);
106
+ const rules = new Map();
107
+ for (const p of projects) {
108
+ const company = p.company.id;
109
+ let r = rules.get(company);
110
+ if (r === undefined) {
111
+ const [c] = (await su
112
+ .model('res.company')
113
+ .browse([company])
114
+ .load(['projectHealthBehindRisk', 'projectHealthBehindOff', 'projectHealthCostRisk'])).read(['projectHealthBehindRisk', 'projectHealthBehindOff', 'projectHealthCostRisk']);
115
+ r = {
116
+ behindRisk: Number(c?.['projectHealthBehindRisk'] ?? 10),
117
+ behindOff: Number(c?.['projectHealthBehindOff'] ?? 25),
118
+ costRisk: Number(c?.['projectHealthCostRisk'] ?? 90),
119
+ };
120
+ rules.set(company, r);
121
+ }
122
+ const figures = money.get(p.id) ?? { booked: '0', committed: '0' };
123
+ const spent = Number(p.labourCost ?? 0) + Number(figures.booked) + Number(figures.committed);
124
+ const judged = judgeHealth({
125
+ today,
126
+ dateStart: isoDate(p.dateStart),
127
+ dateEnd: isoDate(p.dateEnd),
128
+ progress: Number(p.progress ?? 0),
129
+ milestonesLate: late.get(p.id) ?? 0,
130
+ budget: Number(p.budget ?? 0),
131
+ spent,
132
+ }, r);
133
+ await su
134
+ .model('project.project')
135
+ .browse([p.id])
136
+ .write({
137
+ bookedCost: figures.booked,
138
+ committedCost: figures.committed,
139
+ healthComputed: judged.health,
140
+ healthNotes: judged.reasons.join(' '),
141
+ healthCheckedAt: new Date(),
142
+ });
143
+ }
144
+ await repeatTasks(su, found.ids, today);
145
+ // Phases past their end and not finished: late (what the overview and the Late filter read).
146
+ const phases = await su
147
+ .model('project.phase')
148
+ .search({ where: { project: { in: found.ids } } })
149
+ .then((r) => r.load(['dateEnd', 'status', 'late']));
150
+ for (const ph of phases) {
151
+ const end = isoDate(ph.dateEnd);
152
+ const late = end !== null && end < today && !['done', 'cancelled'].includes(ph.status);
153
+ if (late !== ph.late)
154
+ await su
155
+ .model('project.phase')
156
+ .browse([ph.id])
157
+ .write({ late });
158
+ }
159
+ await su.flush();
160
+ return projects.length;
161
+ }
162
+ /** `date` moved by `n` days, weeks or months. */
163
+ export function shiftDate(date, n, unit) {
164
+ const d = new Date(`${date}T00:00:00Z`);
165
+ if (unit === 'month')
166
+ d.setUTCMonth(d.getUTCMonth() + n);
167
+ else
168
+ d.setUTCDate(d.getUTCDate() + n * (unit === 'week' ? 7 : 1));
169
+ return d.toISOString().slice(0, 10);
170
+ }
171
+ /** For each repeating task done in `projects` with no next one yet: the next one, its dates moved, until its end. */
172
+ async function repeatTasks(su, projects, today) {
173
+ const done = await su
174
+ .model('project.task')
175
+ .search({
176
+ where: {
177
+ project: { in: projects },
178
+ done: true,
179
+ repeatEvery: { gt: 0 },
180
+ nextTask: { isNull: true },
181
+ },
182
+ })
183
+ .then((r) => r.load({ assignees: ['id'], tags: ['id'] }));
184
+ if (done.length === 0)
185
+ return 0;
186
+ const [open] = await su
187
+ .model('project.task.stage')
188
+ .search({ where: { done: false }, order: ['sequence asc'], limit: 1 })
189
+ .then((r) => r.load(['id']));
190
+ let made = 0;
191
+ for (const t of done) {
192
+ const every = Number(t.repeatEvery ?? 0);
193
+ const unit = String(t.repeatUnit ?? 'week');
194
+ const start = isoDate(t.dateStart);
195
+ const end = isoDate(t.deadline);
196
+ const nextEnd = end === null ? null : shiftDate(end, every, unit);
197
+ const nextStart = start === null ? null : shiftDate(start, every, unit);
198
+ const until = isoDate(t.repeatUntil);
199
+ const first = nextStart ?? nextEnd ?? shiftDate(today, every, unit);
200
+ if (until !== null && first > until)
201
+ continue;
202
+ const created = await su.model('project.task').create([
203
+ {
204
+ name: t.name,
205
+ project: t.project.id,
206
+ phase: t.phase?.id ?? null,
207
+ workPackage: t.workPackage?.id ?? null,
208
+ category: t.category?.id ?? null,
209
+ partner: t.partner?.id ?? null,
210
+ assignee: t.assignee?.id ?? null,
211
+ assignees: { set: [...t.assignees].map((a) => a.id) },
212
+ tags: { set: [...t.tags].map((x) => x.id) },
213
+ priority: t.priority,
214
+ milestone: t.milestone,
215
+ plannedHours: t.plannedHours,
216
+ description: t.description,
217
+ dateStart: nextStart,
218
+ deadline: nextEnd,
219
+ repeatEvery: t.repeatEvery,
220
+ repeatUnit: t.repeatUnit,
221
+ repeatUntil: t.repeatUntil,
222
+ ...(open === undefined ? {} : { stage: open.id }),
223
+ },
224
+ ]);
225
+ await su
226
+ .model('project.task')
227
+ .browse([t.id])
228
+ .write({ nextTask: created.ids[0] });
229
+ made += 1;
230
+ }
231
+ return made;
232
+ }
233
+ export function registerProjectRoutes(context) {
234
+ const { app, shutro } = context;
235
+ configureProjectChatter(chatterContext(context));
236
+ app.post('/api/v1/projects/refresh', async (request) => {
237
+ const body = (request.body ?? {});
238
+ const ids = Array.isArray(body.projects)
239
+ ? body.projects.filter((x) => Number.isSafeInteger(x))
240
+ : undefined;
241
+ return request.env(async (raw) => {
242
+ const env = raw;
243
+ if (!env.administrator && !env.groups.has('project.manager'))
244
+ throw new HttpProblem(403, 'access', 'Projects managers', 'This needs the Projects manager role.');
245
+ // The projects as the manager sees them, refreshed as the system.
246
+ const visible = ids === undefined
247
+ ? undefined
248
+ : (await env.model('project.project').search({ where: { id: { in: ids } } }))
249
+ .ids;
250
+ return env.asSuperuser({ reason: 'project figures' }, async (su) => ({
251
+ refreshed: await refreshProjects(su, visible),
252
+ }));
253
+ }, { write: true });
254
+ });
255
+ app.get('/api/v1/projects/workload', async (request) => {
256
+ const query = request.query;
257
+ const from = query.from ?? new Date().toISOString().slice(0, 10);
258
+ const weeks = query.weeks === undefined ? 12 : Number(query.weeks);
259
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(from) || Number.isNaN(Date.parse(from)))
260
+ throw new HttpProblem(422, 'invalid-date', 'Which week?', 'Give `from` as YYYY-MM-DD.');
261
+ if (!Number.isInteger(weeks) || weeks < 1 || weeks > 52)
262
+ throw new HttpProblem(422, 'invalid-weeks', 'How many weeks?', '`weeks` is from 1 to 52.');
263
+ // asSuperuser writes an audit row: the request runs in a write scope.
264
+ return request.env(async (raw) => {
265
+ const env = raw;
266
+ if (!env.administrator && !env.groups.has('project.user'))
267
+ throw new HttpProblem(403, 'access', 'Projects users', 'This needs a Projects role.');
268
+ return workload(env, from, weeks);
269
+ }, { write: true });
270
+ });
271
+ const projectId = (request) => {
272
+ const id = Number(request.params.id);
273
+ if (!Number.isSafeInteger(id) || id <= 0)
274
+ throw new HttpProblem(404, 'missing-record', 'Not found', 'No such project.');
275
+ return id;
276
+ };
277
+ const seenProject = async (env, id) => {
278
+ const seen = await env.model('project.project').search({ where: { id: id } });
279
+ if (seen.ids.length === 0)
280
+ throw new HttpProblem(404, 'missing-record', 'Not found', 'No such project of yours.');
281
+ };
282
+ app.get('/api/v1/projects/:id/schedule', async (request) => {
283
+ const id = projectId(request);
284
+ return request.env(async (raw) => {
285
+ const env = raw;
286
+ await seenProject(env, id);
287
+ return scheduleOf(env, id);
288
+ });
289
+ });
290
+ app.post('/api/v1/projects/:id/schedule/apply', async (request) => {
291
+ const id = projectId(request);
292
+ return request.env(async (raw) => {
293
+ const env = raw;
294
+ await seenProject(env, id);
295
+ return { moved: await applySlips(env, id) };
296
+ }, { write: true });
297
+ });
298
+ if (process.env['KOSH_SCHEDULES'] === 'off')
299
+ return;
300
+ const run = async () => {
301
+ try {
302
+ const companies = await shutro.bootstrap({ reason: 'project figures' }, async (env) => (await env.model('res.company').search({}))
303
+ .ids);
304
+ for (const company of companies)
305
+ await shutro.bootstrap({ reason: 'project figures', company }, (env) => env.asSuperuser({ reason: 'project figures' }, (su) => refreshProjects(su)));
306
+ }
307
+ catch (error) {
308
+ app.log.error({ err: error }, 'project figures');
309
+ }
310
+ };
311
+ const timer = setInterval(() => void run(), 3_600_000);
312
+ timer.unref();
313
+ app.addHook('onClose', async () => clearInterval(timer));
314
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * A project's schedule (RFC 0026 step 5): finish-to-start dependencies with a lag in days, the critical path by the
3
+ * critical path method, and moving tasks that start before what they wait for is finished (keeping their length).
4
+ * Dates are `YYYY-MM-DD`; a task's end is its deadline (inclusive). The pure functions are unit-tested; the database
5
+ * ones run as the caller, so record rules decide what is scheduled.
6
+ */
7
+ import type { Env } from './.kosh/bindings.js';
8
+ export interface PlanTask {
9
+ readonly id: number;
10
+ readonly start: string | null;
11
+ readonly end: string | null;
12
+ /** Planned hours: the length in days (8 hours a day) of a task without both dates. */
13
+ readonly hours: number;
14
+ readonly predecessors: readonly {
15
+ readonly id: number;
16
+ readonly lag: number;
17
+ }[];
18
+ }
19
+ export interface Scheduled {
20
+ readonly id: number;
21
+ /** Earliest start and finish (inclusive), latest start and finish, as dates. */
22
+ readonly earlyStart: string;
23
+ readonly earlyFinish: string;
24
+ readonly lateStart: string;
25
+ readonly lateFinish: string;
26
+ /** Days it may slip without moving the project's end. */
27
+ readonly float: number;
28
+ readonly critical: boolean;
29
+ }
30
+ /**
31
+ * The critical path method over `tasks` from `projectStart`: each task starts no earlier than its own start (when set)
32
+ * and the day after each predecessor finishes plus the lag; the project ends with the last finish; working back,
33
+ * each task's latest dates and its float. Tasks with no float are critical.
34
+ */
35
+ export declare function criticalPath(tasks: readonly PlanTask[], projectStart: string): Scheduled[];
36
+ /**
37
+ * The moves that put every dated task after what it waits for: a task starting before the day after a predecessor's
38
+ * end plus the lag moves later, its end with it (its length kept); what waits for it follows in turn.
39
+ */
40
+ export declare function slipMoves(tasks: readonly PlanTask[]): {
41
+ id: number;
42
+ start: string;
43
+ end: string;
44
+ days: number;
45
+ }[];
46
+ export interface ScheduleRow extends Scheduled {
47
+ readonly name: string;
48
+ readonly start: string | null;
49
+ readonly end: string | null;
50
+ readonly baselineStart: string | null;
51
+ readonly baselineEnd: string | null;
52
+ readonly done: boolean;
53
+ readonly milestone: boolean;
54
+ readonly progress: number;
55
+ readonly predecessors: readonly {
56
+ readonly id: number;
57
+ readonly lag: number;
58
+ }[];
59
+ }
60
+ /** The schedule of a project: each task's dates, baseline, float and whether it is critical. */
61
+ export declare function scheduleOf(env: Env, project: number): Promise<{
62
+ start: string;
63
+ end: string;
64
+ tasks: ScheduleRow[];
65
+ }>;
66
+ /** Moves the project's tasks that start before what they wait for is finished; returns the moves. */
67
+ export declare function applySlips(env: Env, project: number): Promise<{
68
+ id: number;
69
+ start: string;
70
+ end: string;
71
+ days: number;
72
+ }[]>;
@@ -0,0 +1,192 @@
1
+ const DAY = 86_400_000;
2
+ const toDay = (iso) => Math.round(Date.parse(`${iso}T00:00:00Z`) / DAY);
3
+ const toIso = (day) => new Date(day * DAY).toISOString().slice(0, 10);
4
+ /** The tasks in an order where each comes after what it waits for (links are acyclic: a constraint keeps them so). */
5
+ function ordered(tasks) {
6
+ const byId = new Map(tasks.map((t) => [t.id, t]));
7
+ const out = [];
8
+ const seen = new Set();
9
+ const visit = (t, path) => {
10
+ if (seen.has(t.id) || path.has(t.id))
11
+ return;
12
+ path.add(t.id);
13
+ for (const p of t.predecessors) {
14
+ const pred = byId.get(p.id);
15
+ if (pred !== undefined)
16
+ visit(pred, path);
17
+ }
18
+ seen.add(t.id);
19
+ out.push(t);
20
+ };
21
+ for (const t of tasks)
22
+ visit(t, new Set());
23
+ return out;
24
+ }
25
+ /** The length of a task in days (at least one). */
26
+ function lengthOf(t) {
27
+ if (t.start !== null && t.end !== null)
28
+ return Math.max(toDay(t.end) - toDay(t.start) + 1, 1);
29
+ return Math.max(Math.ceil(t.hours / 8), 1);
30
+ }
31
+ /**
32
+ * The critical path method over `tasks` from `projectStart`: each task starts no earlier than its own start (when set)
33
+ * and the day after each predecessor finishes plus the lag; the project ends with the last finish; working back,
34
+ * each task's latest dates and its float. Tasks with no float are critical.
35
+ */
36
+ export function criticalPath(tasks, projectStart) {
37
+ const order = ordered(tasks);
38
+ const ids = new Set(tasks.map((t) => t.id));
39
+ const es = new Map();
40
+ const ef = new Map();
41
+ const base = toDay(projectStart);
42
+ for (const t of order) {
43
+ let start = t.start === null ? base : toDay(t.start);
44
+ for (const p of t.predecessors) {
45
+ const finish = ef.get(p.id);
46
+ if (finish !== undefined)
47
+ start = Math.max(start, finish + 1 + p.lag);
48
+ }
49
+ es.set(t.id, start);
50
+ ef.set(t.id, start + lengthOf(t) - 1);
51
+ }
52
+ const end = Math.max(...[...ef.values()], base);
53
+ const successors = new Map();
54
+ for (const t of tasks)
55
+ for (const p of t.predecessors)
56
+ if (ids.has(p.id))
57
+ successors.set(p.id, [...(successors.get(p.id) ?? []), { id: t.id, lag: p.lag }]);
58
+ const ls = new Map();
59
+ const lf = new Map();
60
+ for (const t of [...order].reverse()) {
61
+ let finish = end;
62
+ for (const s of successors.get(t.id) ?? []) {
63
+ const start = ls.get(s.id);
64
+ if (start !== undefined)
65
+ finish = Math.min(finish, start - 1 - s.lag);
66
+ }
67
+ lf.set(t.id, finish);
68
+ ls.set(t.id, finish - lengthOf(t) + 1);
69
+ }
70
+ return tasks.map((t) => {
71
+ const float = (ls.get(t.id) ?? 0) - (es.get(t.id) ?? 0);
72
+ return {
73
+ id: t.id,
74
+ earlyStart: toIso(es.get(t.id) ?? base),
75
+ earlyFinish: toIso(ef.get(t.id) ?? base),
76
+ lateStart: toIso(ls.get(t.id) ?? base),
77
+ lateFinish: toIso(lf.get(t.id) ?? base),
78
+ float,
79
+ critical: float <= 0,
80
+ };
81
+ });
82
+ }
83
+ /**
84
+ * The moves that put every dated task after what it waits for: a task starting before the day after a predecessor's
85
+ * end plus the lag moves later, its end with it (its length kept); what waits for it follows in turn.
86
+ */
87
+ export function slipMoves(tasks) {
88
+ const order = ordered(tasks);
89
+ const start = new Map();
90
+ const end = new Map();
91
+ for (const t of tasks) {
92
+ start.set(t.id, t.start === null ? null : toDay(t.start));
93
+ end.set(t.id, t.end === null ? null : toDay(t.end));
94
+ }
95
+ const moves = [];
96
+ for (const t of order) {
97
+ const s = start.get(t.id) ?? null;
98
+ if (s === null)
99
+ continue;
100
+ let required = s;
101
+ for (const p of t.predecessors) {
102
+ const finish = end.get(p.id);
103
+ if (finish !== undefined && finish !== null)
104
+ required = Math.max(required, finish + 1 + p.lag);
105
+ }
106
+ if (required === s)
107
+ continue;
108
+ const days = required - s;
109
+ const e = end.get(t.id) ?? null;
110
+ const newEnd = e === null ? required + lengthOf(t) - 1 : e + days;
111
+ start.set(t.id, required);
112
+ end.set(t.id, newEnd);
113
+ moves.push({ id: t.id, start: toIso(required), end: toIso(newEnd), days });
114
+ }
115
+ return moves;
116
+ }
117
+ /** A date-only value as `YYYY-MM-DD`. */
118
+ function isoDate(v) {
119
+ if (v === null || v === undefined || v === '')
120
+ return null;
121
+ if (!(v instanceof Date))
122
+ return String(v).slice(0, 10);
123
+ const pad = (n) => String(n).padStart(2, '0');
124
+ return `${v.getFullYear()}-${pad(v.getMonth() + 1)}-${pad(v.getDate())}`;
125
+ }
126
+ /** A project's top-level tasks as the caller sees them, for the schedule. */
127
+ async function planOf(env, project) {
128
+ const [p] = await env
129
+ .model('project.project')
130
+ .browse([project])
131
+ .load(['dateStart']);
132
+ const found = await env
133
+ .model('project.task')
134
+ .search({ where: { project, parentTask: { isNull: true } } })
135
+ .then((r) => r.load({
136
+ predecessors: ['predecessor', 'lag'],
137
+ }));
138
+ const rows = new Map();
139
+ const tasks = [];
140
+ for (const t of found) {
141
+ const predecessors = [...t.predecessors].map((l) => ({
142
+ id: l.predecessor.id,
143
+ lag: Number(l.lag ?? 0),
144
+ }));
145
+ const start = isoDate(t.dateStart);
146
+ const end = isoDate(t.deadline);
147
+ tasks.push({
148
+ id: t.id,
149
+ start,
150
+ end,
151
+ hours: Number(t.plannedHours ?? 0),
152
+ predecessors,
153
+ });
154
+ rows.set(t.id, {
155
+ name: String(t.name),
156
+ start,
157
+ end,
158
+ baselineStart: isoDate(t.baselineStart),
159
+ baselineEnd: isoDate(t.baselineEnd),
160
+ done: t.done === true,
161
+ milestone: t.milestone === true,
162
+ progress: Number(t.progress ?? 0),
163
+ predecessors,
164
+ });
165
+ }
166
+ const starts = tasks.flatMap((t) => (t.start === null ? [] : [t.start])).sort();
167
+ const start = isoDate(p?.dateStart) ?? starts[0] ?? new Date().toISOString().slice(0, 10);
168
+ return { tasks, rows, start };
169
+ }
170
+ /** The schedule of a project: each task's dates, baseline, float and whether it is critical. */
171
+ export async function scheduleOf(env, project) {
172
+ const { tasks, rows, start } = await planOf(env, project);
173
+ const scheduled = criticalPath(tasks, start);
174
+ const out = scheduled.map((s) => ({
175
+ ...rows.get(s.id),
176
+ ...s,
177
+ }));
178
+ out.sort((a, b) => a.earlyStart.localeCompare(b.earlyStart) || a.id - b.id);
179
+ const end = out.reduce((m, r) => (r.earlyFinish > m ? r.earlyFinish : m), start);
180
+ return { start, end, tasks: out };
181
+ }
182
+ /** Moves the project's tasks that start before what they wait for is finished; returns the moves. */
183
+ export async function applySlips(env, project) {
184
+ const { tasks } = await planOf(env, project);
185
+ const moves = slipMoves(tasks);
186
+ for (const m of moves)
187
+ await env
188
+ .model('project.task')
189
+ .browse([m.id])
190
+ .write({ dateStart: m.start, deadline: m.end });
191
+ return moves;
192
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * "Refresh the figures" on a project (RFC 0026 step 4), a node component of the project form: the booked and
3
+ * committed costs from the bridges and the health, now, through `POST /api/v1/projects/refresh`; the form reloads.
4
+ * Shown to Projects managers (the money fields are theirs to see); the server decides.
5
+ */
6
+ import { type NodeComponentProps } from '../../ui/index.js';
7
+ import { type ReactElement } from 'react';
8
+ export declare function Refresh({ api, record, model, served, reload, }: NodeComponentProps): ReactElement | null;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * "Refresh the figures" on a project (RFC 0026 step 4), a node component of the project form: the booked and
4
+ * committed costs from the bridges and the health, now, through `POST /api/v1/projects/refresh`; the form reloads.
5
+ * Shown to Projects managers (the money fields are theirs to see); the server decides.
6
+ */
7
+ import { Alert, Button } from '../../ui/index.js';
8
+ import { useMutation } from '@tanstack/react-query';
9
+ import { useState } from 'react';
10
+ import { useTranslation } from 'react-i18next';
11
+ export function Refresh({ api, record, model, served, reload, }) {
12
+ const { t } = useTranslation('project');
13
+ const [error, setError] = useState(null);
14
+ const refresh = useMutation({
15
+ mutationFn: () => api.call('POST', '/api/v1/projects/refresh', {
16
+ projects: [record?.id],
17
+ }),
18
+ onSuccess: () => {
19
+ setError(null);
20
+ reload();
21
+ },
22
+ onError: (e) => setError(e.message),
23
+ });
24
+ if (record === null || typeof record.id !== 'number')
25
+ return null;
26
+ if (served.fields[model]?.['budget'] === undefined)
27
+ return null;
28
+ return (_jsxs("div", { className: "grid gap-2", children: [error === null ? null : _jsx(Alert, { tone: "danger", children: error }), _jsx("div", { children: _jsx(Button, { busy: refresh.isPending, onClick: () => refresh.mutate(), children: t('refresh.button') }) })] }));
29
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A project's schedule (RFC 0026 step 5), a node component of the project form: its tasks as bars over the weeks from
3
+ * the project's start to its end, the baseline as a thin bar beneath each, the critical path in red, arrows from each
4
+ * task to what waits for it, milestones as diamonds. "Move waiting tasks" puts tasks that start before what they wait
5
+ * for is finished after it, keeping their length (`POST /api/v1/projects/:id/schedule/apply`). Read as the user.
6
+ */
7
+ import { type NodeComponentProps } from '../../ui/index.js';
8
+ import { type ReactElement } from 'react';
9
+ export declare function Schedule({ api, record, version, reload, }: NodeComponentProps): ReactElement | null;