shutro-erp 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account_asset/.kosh/bindings.js +1 -1
- package/dist/account_asset/.kosh/schema.d.ts +530 -8
- package/dist/account_asset/i18n/bn.d.ts +2 -0
- package/dist/account_asset/i18n/bn.js +97 -0
- package/dist/account_asset/index.js +2 -0
- package/dist/account_asset/module.d.ts +4899 -1445
- package/dist/account_budget/.kosh/bindings.js +1 -1
- package/dist/account_budget/.kosh/schema.d.ts +1130 -56
- package/dist/account_budget/i18n/bn.d.ts +2 -0
- package/dist/account_budget/i18n/bn.js +63 -0
- package/dist/account_budget/index.js +14 -0
- package/dist/account_budget/module.d.ts +17958 -4532
- package/dist/account_budget/module.js +15 -0
- package/dist/account_portal/.kosh/bindings.js +1 -1
- package/dist/account_portal/.kosh/schema.d.ts +530 -8
- package/dist/account_portal/i18n/bn.d.ts +2 -0
- package/dist/account_portal/i18n/bn.js +6 -0
- package/dist/account_portal/index.js +2 -0
- package/dist/account_portal/module.d.ts +5167 -1693
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +530 -8
- package/dist/accounting/closing.js +1 -0
- package/dist/accounting/fiscal.d.ts +578 -0
- package/dist/accounting/fiscal.js +401 -0
- package/dist/accounting/followups.d.ts +618 -0
- package/dist/accounting/followups.js +420 -0
- package/dist/accounting/i18n/bn.d.ts +2 -0
- package/dist/accounting/i18n/bn.js +425 -0
- package/dist/accounting/index.d.ts +2 -0
- package/dist/accounting/index.js +649 -2
- package/dist/accounting/module.d.ts +4998 -1451
- package/dist/accounting/module.js +124 -18
- package/dist/accounting/statements.d.ts +878 -0
- package/dist/accounting/statements.js +728 -0
- package/dist/accounting/web/BankReconcilePage.d.ts +9 -0
- package/dist/accounting/web/BankReconcilePage.js +138 -0
- package/dist/accounting/web/FollowupsPage.d.ts +8 -0
- package/dist/accounting/web/FollowupsPage.js +54 -0
- package/dist/accounting/web/PostingPreview.d.ts +8 -0
- package/dist/accounting/web/PostingPreview.js +26 -0
- package/dist/accounting/web/index.d.ts +3 -0
- package/dist/accounting/web/index.js +9 -0
- package/dist/accounting/web/messages.js +140 -0
- package/dist/approvals/.kosh/bindings.js +1 -1
- package/dist/approvals/.kosh/schema.d.ts +272 -4
- package/dist/approvals/demo.d.ts +7 -0
- package/dist/approvals/demo.js +41 -0
- package/dist/approvals/i18n/bn.d.ts +2 -0
- package/dist/approvals/i18n/bn.js +133 -0
- package/dist/approvals/index.d.ts +19 -1
- package/dist/approvals/index.js +477 -7
- package/dist/approvals/mails.d.ts +7 -2
- package/dist/approvals/mails.js +8 -3
- package/dist/approvals/module.d.ts +2930 -1
- package/dist/approvals/module.js +468 -7
- package/dist/approvals/web/Config.d.ts +12 -0
- package/dist/approvals/web/Config.js +60 -0
- package/dist/approvals/web/Matrix.d.ts +9 -0
- package/dist/approvals/web/Matrix.js +26 -0
- package/dist/approvals/web/index.d.ts +3 -1
- package/dist/approvals/web/index.js +8 -1
- package/dist/approvals/web/messages.js +92 -0
- package/dist/approvals_purchase/.kosh/bindings.js +15 -0
- package/dist/approvals_purchase/.kosh/schema.d.ts +1997 -0
- package/dist/approvals_purchase/i18n/bn.d.ts +2 -0
- package/dist/approvals_purchase/i18n/bn.js +5 -0
- package/dist/approvals_purchase/index.d.ts +3 -0
- package/dist/approvals_purchase/index.js +47 -0
- package/dist/approvals_purchase/module.d.ts +12670 -0
- package/dist/approvals_purchase/module.js +84 -0
- package/dist/attendance/.kosh/bindings.js +1 -1
- package/dist/attendance/.kosh/schema.d.ts +59 -1
- package/dist/attendance/i18n/bn.d.ts +2 -0
- package/dist/attendance/i18n/bn.js +147 -0
- package/dist/attendance/index.js +3 -1
- package/dist/attendance/module.d.ts +452 -8
- package/dist/automation/engine.js +1 -1
- package/dist/calendar/.kosh/bindings.js +1 -1
- package/dist/calendar/.kosh/schema.d.ts +2 -1
- package/dist/calendar/i18n/bn.d.ts +2 -0
- package/dist/calendar/i18n/bn.js +80 -0
- package/dist/calendar/index.js +77 -0
- package/dist/calendar/module.d.ts +26 -0
- package/dist/calendar/module.js +14 -4
- package/dist/cli/apps.js +25 -5
- package/dist/cli/init.js +14 -4
- package/dist/cli/templates.d.ts +1 -1
- package/dist/cli/templates.js +34 -5
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +2 -1
- package/dist/contacts/i18n/bn.d.ts +2 -0
- package/dist/contacts/i18n/bn.js +67 -0
- package/dist/contacts/index.js +3 -0
- package/dist/contacts/module.d.ts +42 -0
- package/dist/contacts/module.js +6 -1
- package/dist/crm/.kosh/bindings.js +1 -1
- package/dist/crm/.kosh/schema.d.ts +2 -1
- package/dist/crm/i18n/bn.d.ts +2 -0
- package/dist/crm/i18n/bn.js +80 -0
- package/dist/crm/index.js +22 -0
- package/dist/crm/module.d.ts +20 -0
- package/dist/crm/module.js +4 -2
- package/dist/documents/.kosh/bindings.js +1 -1
- package/dist/documents/.kosh/schema.d.ts +2 -1
- package/dist/documents/i18n/bn.d.ts +2 -0
- package/dist/documents/i18n/bn.js +83 -0
- package/dist/documents/index.js +128 -1
- package/dist/documents/module.d.ts +20 -0
- package/dist/documents/module.js +1 -0
- package/dist/event/.kosh/bindings.js +1 -1
- package/dist/event/.kosh/schema.d.ts +2 -1
- package/dist/event/i18n/bn.d.ts +2 -0
- package/dist/event/i18n/bn.js +101 -0
- package/dist/event/index.js +66 -0
- package/dist/event/module.d.ts +20 -0
- package/dist/event/module.js +8 -1
- package/dist/event_account/.kosh/bindings.js +1 -1
- package/dist/event_account/.kosh/schema.d.ts +530 -8
- package/dist/event_account/i18n/bn.d.ts +2 -0
- package/dist/event_account/i18n/bn.js +5 -0
- package/dist/event_account/index.js +2 -0
- package/dist/event_account/module.d.ts +4905 -1431
- package/dist/expense/.kosh/bindings.js +1 -1
- package/dist/expense/.kosh/schema.d.ts +323 -2
- package/dist/expense/i18n/bn.d.ts +2 -0
- package/dist/expense/i18n/bn.js +155 -0
- package/dist/expense/index.js +8 -1
- package/dist/expense/module.d.ts +2715 -694
- package/dist/expense/module.js +3 -1
- package/dist/expense_account/.kosh/bindings.js +1 -1
- package/dist/expense_account/.kosh/schema.d.ts +587 -8
- package/dist/expense_account/i18n/bn.d.ts +2 -0
- package/dist/expense_account/i18n/bn.js +15 -0
- package/dist/expense_account/index.js +2 -0
- package/dist/expense_account/module.d.ts +8094 -2619
- package/dist/helpdesk/.kosh/bindings.js +1 -1
- package/dist/helpdesk/.kosh/schema.d.ts +2 -1
- package/dist/helpdesk/i18n/bn.d.ts +2 -0
- package/dist/helpdesk/i18n/bn.js +127 -0
- package/dist/helpdesk/index.js +2 -0
- package/dist/helpdesk/module.d.ts +20 -0
- package/dist/hr/.kosh/bindings.js +1 -1
- package/dist/hr/.kosh/schema.d.ts +59 -1
- package/dist/hr/demo.js +11 -1
- package/dist/hr/i18n/bn.d.ts +2 -0
- package/dist/hr/i18n/bn.js +126 -0
- package/dist/hr/index.d.ts +1 -1
- package/dist/hr/index.js +158 -12
- package/dist/hr/module.d.ts +932 -59
- package/dist/hr/module.js +114 -17
- package/dist/{mrp → indent}/.kosh/bindings.js +2 -2
- package/dist/indent/.kosh/schema.d.ts +2084 -0
- package/dist/indent/demo.d.ts +7 -0
- package/dist/indent/demo.js +48 -0
- package/dist/indent/i18n/bn.d.ts +2 -0
- package/dist/indent/i18n/bn.js +143 -0
- package/dist/indent/index.d.ts +3 -0
- package/dist/indent/index.js +450 -0
- package/dist/{mrp → indent}/module.d.ts +7164 -3075
- package/dist/indent/module.js +792 -0
- package/dist/indent_purchase/.kosh/bindings.d.ts +22 -0
- package/dist/indent_purchase/.kosh/bindings.js +15 -0
- package/dist/indent_purchase/.kosh/schema.d.ts +2710 -0
- package/dist/indent_purchase/i18n/bn.d.ts +2 -0
- package/dist/indent_purchase/i18n/bn.js +13 -0
- package/dist/indent_purchase/index.d.ts +3 -0
- package/dist/indent_purchase/index.js +61 -0
- package/dist/indent_purchase/module.d.ts +27950 -0
- package/dist/indent_purchase/module.js +163 -0
- package/dist/l10n_bd/.kosh/bindings.js +1 -1
- package/dist/l10n_bd/.kosh/schema.d.ts +530 -8
- package/dist/l10n_bd/i18n/bn.d.ts +2 -0
- package/dist/l10n_bd/i18n/bn.js +69 -0
- package/dist/l10n_bd/index.js +2 -0
- package/dist/l10n_bd/module.d.ts +5167 -1693
- package/dist/l10n_bd_payroll/.kosh/bindings.js +1 -1
- package/dist/l10n_bd_payroll/.kosh/schema.d.ts +587 -8
- package/dist/l10n_bd_payroll/i18n/bn.d.ts +2 -0
- package/dist/l10n_bd_payroll/i18n/bn.js +4 -0
- package/dist/l10n_bd_payroll/index.js +2 -0
- package/dist/l10n_bd_payroll/module.d.ts +5910 -1548
- package/dist/l10n_bd_pos/.kosh/bindings.d.ts +22 -0
- package/dist/{mrp_account → l10n_bd_pos}/.kosh/bindings.js +2 -2
- package/dist/{mrp_account → l10n_bd_pos}/.kosh/schema.d.ts +693 -651
- package/dist/l10n_bd_pos/i18n/bn.d.ts +2 -0
- package/dist/l10n_bd_pos/i18n/bn.js +32 -0
- package/dist/l10n_bd_pos/index.d.ts +3 -0
- package/dist/l10n_bd_pos/index.js +111 -0
- package/dist/{mrp_account → l10n_bd_pos}/module.d.ts +7402 -7647
- package/dist/l10n_bd_pos/module.js +43 -0
- package/dist/l10n_in/.kosh/bindings.js +1 -1
- package/dist/l10n_in/.kosh/schema.d.ts +530 -8
- package/dist/l10n_in/i18n/bn.d.ts +2 -0
- package/dist/l10n_in/i18n/bn.js +95 -0
- package/dist/l10n_in/index.js +2 -0
- package/dist/l10n_in/module.d.ts +6703 -1652
- package/dist/l10n_in/module.js +25 -5
- package/dist/l10n_in/web/GstSettings.js +2 -2
- package/dist/mail/inbound/handle.js +2 -4
- package/dist/mail/inbound/models.js +5 -3
- package/dist/mail/index.d.ts +12 -1
- package/dist/mail/index.js +139 -2
- package/dist/mail/send.d.ts +56 -0
- package/dist/mail/send.js +394 -0
- package/dist/mail/web/SendButton.d.ts +9 -0
- package/dist/mail/web/SendButton.js +71 -0
- package/dist/mail/web/SendSelected.d.ts +8 -0
- package/dist/mail/web/SendSelected.js +33 -0
- package/dist/mail/web/index.d.ts +2 -0
- package/dist/mail/web/index.js +6 -0
- package/dist/mail/web/messages.js +36 -0
- package/dist/maintenance/.kosh/bindings.js +1 -1
- package/dist/maintenance/.kosh/schema.d.ts +2 -1
- package/dist/maintenance/i18n/bn.d.ts +2 -0
- package/dist/maintenance/i18n/bn.js +132 -0
- package/dist/maintenance/index.js +85 -0
- package/dist/maintenance/module.d.ts +25 -0
- package/dist/maintenance/module.js +27 -9
- package/dist/maintenance_stock/.kosh/bindings.js +1 -1
- package/dist/maintenance_stock/.kosh/schema.d.ts +298 -2
- package/dist/maintenance_stock/i18n/bn.d.ts +2 -0
- package/dist/maintenance_stock/i18n/bn.js +21 -0
- package/dist/maintenance_stock/index.js +2 -0
- package/dist/maintenance_stock/module.d.ts +5302 -1256
- package/dist/maintenance_stock/module.js +1 -0
- package/dist/modules-sdk/apps.d.ts +10 -0
- package/dist/modules-sdk/apps.js +32 -0
- package/dist/modules-sdk/index.d.ts +1 -1
- package/dist/modules-sdk/manifest.d.ts +68 -2
- package/dist/payment/.kosh/bindings.js +1 -1
- package/dist/payment/.kosh/schema.d.ts +530 -8
- package/dist/payment/i18n/bn.d.ts +2 -0
- package/dist/payment/i18n/bn.js +59 -0
- package/dist/payment/index.js +85 -0
- package/dist/payment/module.d.ts +5134 -1660
- package/dist/payment/module.js +17 -3
- package/dist/payment/settle.js +1 -0
- package/dist/payroll/.kosh/bindings.js +1 -1
- package/dist/payroll/.kosh/schema.d.ts +59 -1
- package/dist/payroll/i18n/bn.d.ts +2 -0
- package/dist/payroll/i18n/bn.js +206 -0
- package/dist/payroll/index.js +2 -0
- package/dist/payroll/module.d.ts +936 -48
- package/dist/payroll_account/.kosh/bindings.js +1 -1
- package/dist/payroll_account/.kosh/schema.d.ts +587 -8
- package/dist/payroll_account/i18n/bn.d.ts +2 -0
- package/dist/payroll_account/i18n/bn.js +10 -0
- package/dist/payroll_account/index.js +2 -0
- package/dist/payroll_account/module.d.ts +7534 -3192
- package/dist/permissions/approvals.d.ts +97 -4
- package/dist/permissions/approvals.js +780 -31
- package/dist/permissions/index.d.ts +1 -1
- package/dist/permissions/index.js +2 -2
- package/dist/permissions/models.js +177 -5
- package/dist/permissions/routes.d.ts +3 -0
- package/dist/permissions/routes.js +36 -2
- package/dist/permissions/web/ApprovalPanel.js +43 -5
- package/dist/permissions/web/messages.js +52 -0
- package/dist/portal/.kosh/bindings.js +1 -1
- package/dist/portal/.kosh/schema.d.ts +2 -1
- package/dist/portal/documents.d.ts +3 -0
- package/dist/portal/documents.js +21 -1
- package/dist/portal/i18n/bn.d.ts +2 -0
- package/dist/portal/i18n/bn.js +33 -0
- package/dist/portal/index.js +2 -0
- package/dist/portal/module.d.ts +20 -0
- package/dist/portal/routes-documents.js +76 -0
- package/dist/pos/.kosh/bindings.js +1 -1
- package/dist/pos/.kosh/schema.d.ts +531 -8
- package/dist/pos/i18n/bn.d.ts +2 -0
- package/dist/pos/i18n/bn.js +133 -0
- package/dist/pos/index.js +83 -1
- package/dist/pos/module.d.ts +6888 -1777
- package/dist/pos/module.js +49 -5
- package/dist/pos/routes.js +80 -7
- package/dist/pos/web/Register.js +2 -5
- package/dist/pos_stock/.kosh/bindings.js +1 -1
- package/dist/pos_stock/.kosh/schema.d.ts +626 -9
- package/dist/pos_stock/i18n/bn.d.ts +2 -0
- package/dist/pos_stock/i18n/bn.js +6 -0
- package/dist/pos_stock/index.js +2 -0
- package/dist/pos_stock/module.d.ts +10798 -3276
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +202 -1
- package/dist/product/i18n/bn.d.ts +2 -0
- package/dist/product/i18n/bn.js +175 -0
- package/dist/product/index.d.ts +1 -0
- package/dist/product/index.js +288 -1
- package/dist/product/module.d.ts +1725 -0
- package/dist/product/module.js +66 -6
- package/dist/product/pricelist.d.ts +575 -0
- package/dist/product/pricelist.js +257 -0
- package/dist/product/variants.d.ts +908 -0
- package/dist/product/variants.js +251 -0
- package/dist/production/.kosh/bindings.d.ts +22 -0
- package/dist/production/.kosh/bindings.js +15 -0
- package/dist/production/.kosh/schema.d.ts +2345 -0
- package/dist/{mrp → production}/demo.d.ts +1 -1
- package/dist/{mrp → production}/demo.js +5 -5
- package/dist/production/depth.d.ts +790 -0
- package/dist/production/depth.js +262 -0
- package/dist/production/i18n/bn.d.ts +2 -0
- package/dist/production/i18n/bn.js +350 -0
- package/dist/production/index.d.ts +6 -0
- package/dist/production/index.js +1256 -0
- package/dist/production/module.d.ts +17327 -0
- package/dist/production/module.js +1333 -0
- package/dist/production/plan.d.ts +921 -0
- package/dist/production/plan.js +511 -0
- package/dist/production/schedule.d.ts +656 -0
- package/dist/production/schedule.js +309 -0
- package/dist/production_account/.kosh/bindings.d.ts +22 -0
- package/dist/production_account/.kosh/bindings.js +15 -0
- package/dist/production_account/.kosh/schema.d.ts +3496 -0
- package/dist/production_account/i18n/bn.d.ts +2 -0
- package/dist/production_account/i18n/bn.js +93 -0
- package/dist/production_account/index.d.ts +4 -0
- package/dist/production_account/index.js +246 -0
- package/dist/production_account/module.d.ts +1192 -0
- package/dist/{mrp_account → production_account}/module.js +72 -18
- package/dist/production_account/overhead.d.ts +1195 -0
- package/dist/production_account/overhead.js +554 -0
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +65 -2
- package/dist/project/i18n/bn.d.ts +2 -0
- package/dist/project/i18n/bn.js +383 -0
- package/dist/project/index.js +123 -0
- package/dist/project/module.d.ts +20 -0
- package/dist/project/module.js +73 -14
- package/dist/project_account/.kosh/bindings.js +1 -1
- package/dist/project_account/.kosh/schema.d.ts +530 -8
- package/dist/project_account/i18n/bn.d.ts +2 -0
- package/dist/project_account/i18n/bn.js +6 -0
- package/dist/project_account/index.js +2 -0
- package/dist/project_account/module.d.ts +4926 -1452
- package/dist/project_portal/.kosh/bindings.js +1 -1
- package/dist/project_portal/.kosh/schema.d.ts +65 -2
- package/dist/project_portal/i18n/bn.d.ts +2 -0
- package/dist/project_portal/i18n/bn.js +6 -0
- package/dist/project_portal/index.js +2 -0
- package/dist/project_portal/module.d.ts +40 -0
- package/dist/project_purchase/.kosh/bindings.js +1 -1
- package/dist/project_purchase/.kosh/schema.d.ts +1126 -53
- package/dist/project_purchase/i18n/bn.d.ts +2 -0
- package/dist/project_purchase/i18n/bn.js +4 -0
- package/dist/project_purchase/index.js +2 -0
- package/dist/project_purchase/module.d.ts +17912 -4446
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +813 -3
- package/dist/purchase/comparison.d.ts +993 -0
- package/dist/purchase/comparison.js +415 -0
- package/dist/purchase/extras.d.ts +1509 -0
- package/dist/purchase/extras.js +445 -0
- package/dist/purchase/i18n/bn.d.ts +2 -0
- package/dist/purchase/i18n/bn.js +413 -0
- package/dist/purchase/index.d.ts +5 -0
- package/dist/purchase/index.js +1280 -4
- package/dist/purchase/module.d.ts +7139 -398
- package/dist/purchase/module.js +295 -11
- package/dist/purchase/request.d.ts +696 -0
- package/dist/purchase/request.js +446 -0
- package/dist/purchase/tender.d.ts +1208 -0
- package/dist/purchase/tender.js +507 -0
- package/dist/purchase/vendorprice.d.ts +340 -0
- package/dist/purchase/vendorprice.js +153 -0
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +1130 -57
- package/dist/purchase_account/i18n/bn.d.ts +2 -0
- package/dist/purchase_account/i18n/bn.js +22 -0
- package/dist/purchase_account/index.js +19 -0
- package/dist/purchase_account/module.d.ts +12276 -2247
- package/dist/purchase_account/module.js +241 -9
- package/dist/purchase_portal/.kosh/bindings.js +1 -1
- package/dist/purchase_portal/.kosh/schema.d.ts +813 -3
- package/dist/purchase_portal/i18n/bn.d.ts +2 -0
- package/dist/purchase_portal/i18n/bn.js +5 -0
- package/dist/purchase_portal/index.js +2 -0
- package/dist/purchase_portal/module.d.ts +6849 -353
- package/dist/purchase_stock/.kosh/bindings.js +1 -1
- package/dist/purchase_stock/.kosh/schema.d.ts +910 -4
- package/dist/purchase_stock/i18n/bn.d.ts +2 -0
- package/dist/purchase_stock/i18n/bn.js +19 -0
- package/dist/purchase_stock/index.d.ts +1 -1
- package/dist/purchase_stock/index.js +29 -0
- package/dist/purchase_stock/module.d.ts +12410 -1864
- package/dist/purchase_stock/module.js +81 -9
- package/dist/purchase_stock/scorecard.d.ts +22 -0
- package/dist/purchase_stock/scorecard.js +77 -0
- package/dist/purchase_stock/web/Scorecard.d.ts +7 -0
- package/dist/purchase_stock/web/Scorecard.js +22 -0
- package/dist/purchase_stock/web/index.d.ts +11 -0
- package/dist/purchase_stock/web/index.js +16 -0
- package/dist/purchase_stock/web/messages.d.ts +2 -0
- package/dist/purchase_stock/web/messages.js +27 -0
- package/dist/purchase_stock_account/.kosh/bindings.js +1 -1
- package/dist/purchase_stock_account/.kosh/schema.d.ts +1286 -58
- package/dist/purchase_stock_account/i18n/bn.d.ts +2 -0
- package/dist/purchase_stock_account/i18n/bn.js +8 -0
- package/dist/purchase_stock_account/index.js +18 -0
- package/dist/purchase_stock_account/module.d.ts +7 -37995
- package/dist/purchase_stock_account/module.js +24 -3
- package/dist/recruitment/.kosh/bindings.js +1 -1
- package/dist/recruitment/.kosh/schema.d.ts +59 -1
- package/dist/recruitment/i18n/bn.d.ts +2 -0
- package/dist/recruitment/i18n/bn.js +93 -0
- package/dist/recruitment/index.js +64 -0
- package/dist/recruitment/module.d.ts +458 -8
- package/dist/recruitment/module.js +11 -3
- package/dist/reporting/documents.d.ts +5 -0
- package/dist/reporting/documents.js +11 -1
- package/dist/reporting/index.d.ts +1 -1
- package/dist/reporting/index.js +1 -1
- package/dist/reporting/web/CalendarView.d.ts +18 -5
- package/dist/reporting/web/CalendarView.js +163 -29
- package/dist/reporting/web/messages.js +18 -0
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +642 -16
- package/dist/sale_account/i18n/bn.d.ts +2 -0
- package/dist/sale_account/i18n/bn.js +48 -0
- package/dist/sale_account/index.d.ts +1 -1
- package/dist/sale_account/index.js +102 -4
- package/dist/sale_account/module.d.ts +8254 -2212
- package/dist/sale_account/module.js +535 -46
- package/dist/sale_crm/.kosh/bindings.d.ts +22 -0
- package/dist/sale_crm/.kosh/bindings.js +15 -0
- package/dist/{mrp → sale_crm}/.kosh/schema.d.ts +389 -567
- package/dist/sale_crm/i18n/bn.d.ts +2 -0
- package/dist/sale_crm/i18n/bn.js +9 -0
- package/dist/sale_crm/index.d.ts +7 -0
- package/dist/sale_crm/index.js +104 -0
- package/dist/sale_crm/module.d.ts +5609 -0
- package/dist/sale_crm/module.js +73 -0
- package/dist/sale_portal/.kosh/bindings.js +1 -1
- package/dist/sale_portal/.kosh/schema.d.ts +291 -4
- package/dist/sale_portal/i18n/bn.d.ts +2 -0
- package/dist/sale_portal/i18n/bn.js +7 -0
- package/dist/sale_portal/index.js +10 -0
- package/dist/sale_portal/module.d.ts +2564 -294
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +354 -5
- package/dist/sale_project/i18n/bn.d.ts +2 -0
- package/dist/sale_project/i18n/bn.js +12 -0
- package/dist/sale_project/index.js +2 -0
- package/dist/sale_project/module.d.ts +6249 -2402
- package/dist/sale_stock/.kosh/bindings.js +1 -1
- package/dist/sale_stock/.kosh/schema.d.ts +387 -5
- package/dist/sale_stock/i18n/bn.d.ts +2 -0
- package/dist/sale_stock/i18n/bn.js +15 -0
- package/dist/sale_stock/index.js +2 -0
- package/dist/sale_stock/module.d.ts +8057 -1759
- package/dist/sale_stock/module.js +20 -3
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +291 -4
- package/dist/sales/i18n/bn.d.ts +2 -0
- package/dist/sales/i18n/bn.js +128 -0
- package/dist/sales/index.js +120 -5
- package/dist/sales/module.d.ts +3567 -633
- package/dist/sales/module.js +306 -11
- package/dist/server/api/duplicate.d.ts +14 -0
- package/dist/server/api/duplicate.js +75 -0
- package/dist/server/api/explain.d.ts +70 -0
- package/dist/server/api/explain.js +111 -0
- package/dist/server/api/metadata.d.ts +2 -0
- package/dist/server/api/metadata.js +9 -0
- package/dist/server/api/openapi.js +15 -0
- package/dist/server/api/rest.d.ts +1 -0
- package/dist/server/api/rest.js +76 -12
- package/dist/server/api/views.js +1 -1
- package/dist/server/apps.d.ts +16 -1
- package/dist/server/apps.js +164 -2
- package/dist/server/auth/base.js +2 -0
- package/dist/server/auth/service.d.ts +8 -0
- package/dist/server/auth/service.js +19 -0
- package/dist/server/events.d.ts +13 -0
- package/dist/server/events.js +22 -0
- package/dist/server/i18n.d.ts +27 -0
- package/dist/server/i18n.js +143 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/server.js +6 -0
- package/dist/settings/company.d.ts +3 -1
- package/dist/settings/company.js +26 -5
- package/dist/settings/routes.d.ts +10 -0
- package/dist/settings/routes.js +19 -3
- package/dist/settings/web/AppsPage.d.ts +3 -2
- package/dist/settings/web/AppsPage.js +26 -8
- package/dist/settings/web/CompaniesPage.js +91 -6
- package/dist/settings/web/messages.js +64 -0
- package/dist/sign/.kosh/bindings.js +1 -1
- package/dist/sign/.kosh/schema.d.ts +2 -1
- package/dist/sign/i18n/bn.d.ts +2 -0
- package/dist/sign/i18n/bn.js +97 -0
- package/dist/sign/index.d.ts +15 -0
- package/dist/sign/index.js +129 -1
- package/dist/sign/module.d.ts +40 -0
- package/dist/sign/module.js +11 -2
- package/dist/stock/.kosh/bindings.js +1 -1
- package/dist/stock/.kosh/schema.d.ts +298 -2
- package/dist/stock/i18n/bn.d.ts +2 -0
- package/dist/stock/i18n/bn.js +311 -0
- package/dist/stock/index.d.ts +3 -1
- package/dist/stock/index.js +442 -3
- package/dist/stock/ledger.d.ts +78 -0
- package/dist/stock/ledger.js +582 -0
- package/dist/stock/module.d.ts +5280 -2044
- package/dist/stock/module.js +222 -23
- package/dist/stock/replenish.js +16 -1
- package/dist/stock/returns.d.ts +157 -0
- package/dist/stock/returns.js +163 -0
- package/dist/stock/web/Ledger.d.ts +9 -0
- package/dist/stock/web/Ledger.js +108 -0
- package/dist/stock/web/Operations.d.ts +8 -0
- package/dist/stock/web/Operations.js +33 -0
- package/dist/stock/web/ReturnButton.d.ts +7 -0
- package/dist/stock/web/ReturnButton.js +62 -0
- package/dist/stock/web/index.d.ts +3 -0
- package/dist/stock/web/index.js +9 -0
- package/dist/stock/web/messages.js +154 -0
- package/dist/stock_account/.kosh/bindings.js +1 -1
- package/dist/stock_account/.kosh/schema.d.ts +682 -9
- package/dist/stock_account/i18n/bn.d.ts +2 -0
- package/dist/stock_account/i18n/bn.js +73 -0
- package/dist/stock_account/index.d.ts +1 -0
- package/dist/stock_account/index.js +131 -3
- package/dist/stock_account/landed.d.ts +429 -0
- package/dist/stock_account/landed.js +335 -0
- package/dist/stock_account/module.d.ts +11247 -3363
- package/dist/stock_account/module.js +135 -6
- package/dist/stock_account/web/LandedPreview.d.ts +7 -0
- package/dist/stock_account/web/LandedPreview.js +23 -0
- package/dist/stock_account/web/index.d.ts +11 -0
- package/dist/stock_account/web/index.js +16 -0
- package/dist/stock_account/web/messages.d.ts +2 -0
- package/dist/stock_account/web/messages.js +27 -0
- package/dist/todo/i18n/bn.d.ts +2 -0
- package/dist/todo/i18n/bn.js +66 -0
- package/dist/todo/index.js +59 -0
- package/dist/todo/module.d.ts +5 -0
- package/dist/todo/module.js +5 -3
- package/dist/trade_finance/.kosh/bindings.js +1 -1
- package/dist/trade_finance/.kosh/schema.d.ts +2 -1
- package/dist/trade_finance/i18n/bn.d.ts +2 -0
- package/dist/trade_finance/i18n/bn.js +162 -0
- package/dist/trade_finance/index.js +93 -0
- package/dist/trade_finance/module.d.ts +26 -0
- package/dist/trade_finance/module.js +27 -8
- package/dist/trade_finance_account/.kosh/bindings.js +1 -1
- package/dist/trade_finance_account/.kosh/schema.d.ts +530 -8
- package/dist/trade_finance_account/i18n/bn.d.ts +2 -0
- package/dist/trade_finance_account/i18n/bn.js +27 -0
- package/dist/trade_finance_account/index.js +2 -0
- package/dist/trade_finance_account/module.d.ts +4906 -1432
- package/dist/trade_finance_account/module.js +1 -0
- package/dist/trade_finance_purchase/.kosh/bindings.js +1 -1
- package/dist/trade_finance_purchase/.kosh/schema.d.ts +813 -3
- package/dist/trade_finance_purchase/i18n/bn.d.ts +2 -0
- package/dist/trade_finance_purchase/i18n/bn.js +6 -0
- package/dist/trade_finance_purchase/index.js +2 -0
- package/dist/trade_finance_purchase/module.d.ts +6909 -413
- package/dist/trade_finance_sale/.kosh/bindings.js +1 -1
- package/dist/trade_finance_sale/.kosh/schema.d.ts +291 -4
- package/dist/trade_finance_sale/i18n/bn.d.ts +2 -0
- package/dist/trade_finance_sale/i18n/bn.js +6 -0
- package/dist/trade_finance_sale/index.js +2 -0
- package/dist/trade_finance_sale/module.d.ts +2601 -331
- package/dist/ui/api.d.ts +2 -0
- package/dist/ui/app.d.ts +9 -0
- package/dist/ui/app.js +24 -4
- package/dist/ui/components/AppIcon.js +2 -2
- package/dist/ui/components/Explain.d.ts +69 -0
- package/dist/ui/components/Explain.js +62 -0
- package/dist/ui/context.d.ts +8 -1
- package/dist/ui/context.js +34 -1
- package/dist/ui/i18n/messages.d.ts +36 -0
- package/dist/ui/i18n/messages.js +72 -0
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/registry.d.ts +14 -0
- package/dist/ui/registry.js +10 -0
- package/dist/ui/views/FormView.d.ts +6 -0
- package/dist/ui/views/FormView.js +47 -10
- package/dist/ui/views/ListView.d.ts +8 -0
- package/dist/ui/views/ListView.js +10 -3
- package/dist/ui/views/StatsRow.js +2 -1
- package/dist/views/extend.js +12 -0
- package/dist/views/types.d.ts +13 -2
- package/dist/views/validate.js +8 -1
- package/dist/website/.kosh/bindings.js +1 -1
- package/dist/website/.kosh/schema.d.ts +2 -1
- package/dist/website/i18n/bn.d.ts +2 -0
- package/dist/website/i18n/bn.js +113 -0
- package/dist/website/index.js +36 -0
- package/dist/website/module.d.ts +20 -0
- package/dist/website/module.js +29 -5
- package/dist/website_crm/.kosh/bindings.js +1 -1
- package/dist/website_crm/.kosh/schema.d.ts +2 -1
- package/dist/website_crm/i18n/bn.d.ts +2 -0
- package/dist/website_crm/i18n/bn.js +5 -0
- package/dist/website_crm/index.js +2 -0
- package/dist/website_crm/module.d.ts +40 -0
- package/dist/website_sale/.kosh/bindings.js +1 -1
- package/dist/website_sale/.kosh/schema.d.ts +642 -17
- package/dist/website_sale/i18n/bn.d.ts +2 -0
- package/dist/website_sale/i18n/bn.js +132 -0
- package/dist/website_sale/index.js +83 -1
- package/dist/website_sale/module.d.ts +18705 -5550
- package/dist/website_sale/module.js +32 -10
- package/dist/website_sale/shop.js +22 -9
- package/dist/website_sale_stock/.kosh/bindings.js +1 -1
- package/dist/website_sale_stock/.kosh/schema.d.ts +737 -18
- package/dist/website_sale_stock/i18n/bn.d.ts +2 -0
- package/dist/website_sale_stock/i18n/bn.js +4 -0
- package/dist/website_sale_stock/index.js +2 -0
- package/dist/website_sale_stock/module.d.ts +10 -24997
- package/dist/website_sale_stock/module.js +7 -3
- package/package.json +36 -8
- package/dist/mrp/index.d.ts +0 -3
- package/dist/mrp/index.js +0 -393
- package/dist/mrp/module.js +0 -757
- package/dist/mrp_account/index.d.ts +0 -3
- package/dist/mrp_account/index.js +0 -22
- /package/dist/{mrp → approvals_purchase}/.kosh/bindings.d.ts +0 -0
- /package/dist/{mrp_account → indent}/.kosh/bindings.d.ts +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { defineModule } from '../modules-sdk/index.js';
|
|
8
8
|
export { registerAdminApi } from './admin.js';
|
|
9
|
-
export { type ApprovalCard, type ApprovalRequestInfo, approvalCards, approvalGuard, approvalState, approvalStats, approveMany, type Decision, decide, decidersOf, groupsOf, inbox, installApprovalPolicies, issueLinks, linkOf, requestApprovals, requestsOf, sweepDeadlines, turnOf, useLink, type Who, whoIs, withdraw, } from './approvals.js';
|
|
9
|
+
export { APPROVER_KEYS, type ApprovalCard, type ApprovalRequestInfo, approvalCards, approvalGuard, approvalMatrix, approvalState, approvalStats, approveMany, type Decision, decide, decidersOf, eventsOf, groupsOf, inbox, installApprovalPolicies, issueLinks, linkOf, MATRIX_PERIODS, type MatrixCard, type MatrixPeriod, notifyBackups, previewPolicy, reassign, requestApprovals, requestsOf, sweepDeadlines, syncLevels, turnOf, useLink, type Who, whoIs, withdraw, } from './approvals.js';
|
|
10
10
|
export { permissionsShutro } from './models.js';
|
|
11
11
|
export { registerRoleRoutes } from './roles.js';
|
|
12
12
|
export { registerApprovalRoutes } from './routes.js';
|
|
@@ -12,7 +12,7 @@ import { permissionsShutro } from './models.js';
|
|
|
12
12
|
import { registerRoleRoutes } from './roles.js';
|
|
13
13
|
import { registerApprovalRoutes } from './routes.js';
|
|
14
14
|
export { registerAdminApi } from './admin.js';
|
|
15
|
-
export { approvalCards, approvalGuard, approvalState, approvalStats, approveMany, decide, decidersOf, groupsOf, inbox, installApprovalPolicies, issueLinks, linkOf, requestApprovals, requestsOf, sweepDeadlines, turnOf, useLink, whoIs, withdraw, } from './approvals.js';
|
|
15
|
+
export { APPROVER_KEYS, approvalCards, approvalGuard, approvalMatrix, approvalState, approvalStats, approveMany, decide, decidersOf, eventsOf, groupsOf, inbox, installApprovalPolicies, issueLinks, linkOf, MATRIX_PERIODS, notifyBackups, previewPolicy, reassign, requestApprovals, requestsOf, sweepDeadlines, syncLevels, turnOf, useLink, whoIs, withdraw, } from './approvals.js';
|
|
16
16
|
export { permissionsShutro } from './models.js';
|
|
17
17
|
export { registerRoleRoutes } from './roles.js';
|
|
18
18
|
export { registerApprovalRoutes } from './routes.js';
|
|
@@ -29,7 +29,7 @@ export const permissions = defineModule({
|
|
|
29
29
|
accessLevels: {
|
|
30
30
|
auditor: { label: 'Security auditor', groups: ['permissions.auditor'] },
|
|
31
31
|
},
|
|
32
|
-
privateModels: ['approval.request', 'approval.vote', 'approval.link'],
|
|
32
|
+
privateModels: ['approval.request', 'approval.vote', 'approval.link', 'approval.event'],
|
|
33
33
|
hooks: {
|
|
34
34
|
defaults: async (env, modules) => {
|
|
35
35
|
const n = await installApprovalPolicies(env, modules);
|
|
@@ -12,7 +12,11 @@ const modelOf = (env, name) => env.model(name);
|
|
|
12
12
|
async function readAll(records, keys) {
|
|
13
13
|
const set = records;
|
|
14
14
|
// A many-to-many is loaded by the object spec (defaults plus the relation) and read as ids.
|
|
15
|
-
const spec = keys.
|
|
15
|
+
const spec = keys.some((k) => k === 'approverUsers' || k === 'companies')
|
|
16
|
+
? Object.fromEntries(keys
|
|
17
|
+
.filter((k) => ['approverUsers', 'extraUsers', 'backupUsers', 'companies'].includes(k))
|
|
18
|
+
.map((k) => [k, true]))
|
|
19
|
+
: keys;
|
|
16
20
|
return set.length === 0 ? [] : (await set.load(spec)).read(keys);
|
|
17
21
|
}
|
|
18
22
|
/** The installed model a policy names, or `undefined`. */
|
|
@@ -35,8 +39,16 @@ const ApprovalPolicy = defineModel('approval.policy', {
|
|
|
35
39
|
name: f.char({ required: true }),
|
|
36
40
|
targetModel: f.char({ required: true, index: true, label: 'Model' }),
|
|
37
41
|
sequence: f.integer({ default: 10 }),
|
|
38
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* The companies it covers (RFC 0027 §2.3): this company, these companies and every company of this segment
|
|
44
|
+
* together; all three empty: every company.
|
|
45
|
+
*/
|
|
39
46
|
company: f.many2one('res.company', { ondelete: 'cascade' }),
|
|
47
|
+
companies: f.many2many('res.company', {
|
|
48
|
+
relation: 'approval_policy_company_rel',
|
|
49
|
+
label: 'Companies',
|
|
50
|
+
}),
|
|
51
|
+
segment: f.many2one('res.segment', { ondelete: 'restrict', label: 'Segment' }),
|
|
40
52
|
/** A transition of the record run when every level approved (as the system, attributed in the audit log). */
|
|
41
53
|
onApproved: f.char(),
|
|
42
54
|
/** A transition run when a level rejects. */
|
|
@@ -47,9 +59,57 @@ const ApprovalPolicy = defineModel('approval.policy', {
|
|
|
47
59
|
onWithdrawn: f.char({ label: 'On withdrawn' }),
|
|
48
60
|
/** A rejection or a sending back must say why. */
|
|
49
61
|
reasonRequired: f.boolean({ default: true, label: 'Reason required' }),
|
|
62
|
+
/**
|
|
63
|
+
* The records the policy is for, as a Shutro domain on the model (RFC 0025 step 4: a request category's policy
|
|
64
|
+
* covers that category's requests); empty: every record.
|
|
65
|
+
*/
|
|
66
|
+
condition: f.json({ label: 'For records where' }),
|
|
67
|
+
/**
|
|
68
|
+
* When a level finds nobody to decide — no person, and no role to stand in (RFC 0027 §2.4): refuse the submission
|
|
69
|
+
* and say why, let administrators decide, or skip the level (recorded as skipped).
|
|
70
|
+
*/
|
|
71
|
+
whenNobody: f.selection({ refuse: 'Refuse the submission', admins: 'Administrators decide', skip: 'Skip the level' }, { required: true, default: 'refuse', label: 'When nobody is found' }),
|
|
72
|
+
/**
|
|
73
|
+
* A level whose approvers include someone who already approved this round is approved for them, marked as
|
|
74
|
+
* such (RFC 0027 §2.2); a level needing more approvals still waits for the others.
|
|
75
|
+
*/
|
|
76
|
+
skipDuplicates: f.boolean({ default: false, label: 'Skip approvers who already approved' }),
|
|
77
|
+
/** When a level's approvers change, its waiting requests follow: not, adding the new people, or replacing. */
|
|
78
|
+
syncPending: f.selection({ off: 'No', add: 'Add the new approvers', replace: 'Replace the approvers' }, { required: true, default: 'add', label: 'Waiting requests follow changes' }),
|
|
79
|
+
/** Email the people added to a waiting request whose turn it is. */
|
|
80
|
+
notifyOnSync: f.boolean({ default: true, label: 'Tell the people added' }),
|
|
50
81
|
levels: f.one2many('approval.level', 'policy'),
|
|
51
82
|
},
|
|
52
83
|
constraints: {
|
|
84
|
+
validCondition: {
|
|
85
|
+
depends: ['condition', 'targetModel'],
|
|
86
|
+
check: async (records) => {
|
|
87
|
+
const { env, ids } = records;
|
|
88
|
+
return env.asSuperuser({ reason: 'validate approval policy condition' }, async (su) => {
|
|
89
|
+
const failing = [];
|
|
90
|
+
for (const row of await readAll(modelOf(su, 'approval.policy').browse(ids), [
|
|
91
|
+
'condition',
|
|
92
|
+
'targetModel',
|
|
93
|
+
])) {
|
|
94
|
+
const condition = row['condition'];
|
|
95
|
+
if (condition === null || condition === undefined)
|
|
96
|
+
continue;
|
|
97
|
+
const target = row['targetModel'];
|
|
98
|
+
const ok = typeof condition === 'object' &&
|
|
99
|
+
!Array.isArray(condition) &&
|
|
100
|
+
installed(su, target) &&
|
|
101
|
+
(await modelOf(su, String(target))
|
|
102
|
+
.search({ where: condition, limit: 1 })
|
|
103
|
+
.then(() => true)
|
|
104
|
+
.catch(() => false));
|
|
105
|
+
if (!ok)
|
|
106
|
+
failing.push(row['id']);
|
|
107
|
+
}
|
|
108
|
+
return failing;
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
message: 'The policy’s condition is not a valid domain on its model.',
|
|
112
|
+
},
|
|
53
113
|
installedModel: {
|
|
54
114
|
depends: ['targetModel'],
|
|
55
115
|
check: async (records) => {
|
|
@@ -64,9 +124,60 @@ const ApprovalPolicy = defineModel('approval.policy', {
|
|
|
64
124
|
},
|
|
65
125
|
},
|
|
66
126
|
});
|
|
127
|
+
/**
|
|
128
|
+
* A business segment (RFC 0027 §2.3): a group of companies — Feed, Poultry, Dairy — that approval policies cover at
|
|
129
|
+
* once; a company belongs to one segment.
|
|
130
|
+
*/
|
|
131
|
+
const ResSegment = defineModel('res.segment', {
|
|
132
|
+
label: 'Segment',
|
|
133
|
+
recName: 'name',
|
|
134
|
+
archivable: true,
|
|
135
|
+
order: ['name asc'],
|
|
136
|
+
access: { 'base.user': ['read'], 'approvals.manager': ['read', 'write', 'create', 'unlink'] },
|
|
137
|
+
fields: {
|
|
138
|
+
name: f.char({ required: true, label: 'Name' }),
|
|
139
|
+
code: f.char({ label: 'Code' }),
|
|
140
|
+
/** Its companies; a company is in one segment at most. */
|
|
141
|
+
companies: f.many2many('res.company', {
|
|
142
|
+
relation: 'res_segment_company_rel',
|
|
143
|
+
label: 'Companies',
|
|
144
|
+
}),
|
|
145
|
+
note: f.text({ label: 'Note' }),
|
|
146
|
+
},
|
|
147
|
+
sqlConstraints: { uniqueName: { unique: ['name'], message: 'Another segment has this name.' } },
|
|
148
|
+
constraints: {
|
|
149
|
+
oneSegment: {
|
|
150
|
+
depends: ['companies', 'active'],
|
|
151
|
+
check: async (records) => {
|
|
152
|
+
const { env, ids } = records;
|
|
153
|
+
return env.asSuperuser({ reason: 'validate segments' }, async (su) => {
|
|
154
|
+
const all = await readAll(await modelOf(su, 'res.segment').search({ where: { active: true } }), ['companies']);
|
|
155
|
+
const seen = new Map();
|
|
156
|
+
const failing = new Set();
|
|
157
|
+
for (const row of all)
|
|
158
|
+
for (const c of row['companies'] ?? []) {
|
|
159
|
+
const other = seen.get(c);
|
|
160
|
+
if (other !== undefined) {
|
|
161
|
+
if (ids.includes(row['id']))
|
|
162
|
+
failing.add(row['id']);
|
|
163
|
+
if (ids.includes(other))
|
|
164
|
+
failing.add(other);
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
seen.set(c, row['id']);
|
|
168
|
+
}
|
|
169
|
+
return [...failing];
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
message: 'A company is in one segment at most.',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
});
|
|
67
176
|
const ApprovalLevel = defineModel('approval.level', {
|
|
68
177
|
access: { 'base.user': ['read'], 'approvals.manager': ['read', 'write', 'create', 'unlink'] },
|
|
69
178
|
order: ['policy asc', 'sequence asc'],
|
|
179
|
+
// Waiting requests follow changes of their level's approvers (RFC 0027 §2.6), after the save commits.
|
|
180
|
+
changeEvents: true,
|
|
70
181
|
fields: {
|
|
71
182
|
policy: f.many2one('approval.policy', { required: true, ondelete: 'cascade' }),
|
|
72
183
|
sequence: f.integer({ default: 10 }),
|
|
@@ -81,15 +192,32 @@ const ApprovalLevel = defineModel('approval.level', {
|
|
|
81
192
|
role: 'A role',
|
|
82
193
|
users: 'Named people',
|
|
83
194
|
manager: 'The requester’s manager',
|
|
195
|
+
department_head: 'The head of the requester’s department',
|
|
196
|
+
department_approvers: 'The approvers of the requester’s department',
|
|
197
|
+
employee: 'A person on the requester’s employee record',
|
|
84
198
|
field: 'A person on the record',
|
|
199
|
+
record: 'The record decides',
|
|
85
200
|
}, { required: true, default: 'role', label: 'Approved by' }),
|
|
201
|
+
/** People added to whoever the kind finds (RFC 0027 §2.1). */
|
|
202
|
+
extraUsers: f.many2many('res.users', {
|
|
203
|
+
relation: 'approval_level_extra_user_rel',
|
|
204
|
+
label: 'Also',
|
|
205
|
+
}),
|
|
206
|
+
/** Standing backups: may always decide the level beside its approvers (RFC 0027 §2.2). */
|
|
207
|
+
backupUsers: f.many2many('res.users', {
|
|
208
|
+
relation: 'approval_level_backup_user_rel',
|
|
209
|
+
label: 'Backups',
|
|
210
|
+
}),
|
|
86
211
|
/** The role (or, for a manager or a field, the role used when there is nobody). */
|
|
87
212
|
approverRole: f.char({ label: 'Role' }),
|
|
88
213
|
approverUsers: f.many2many('res.users', {
|
|
89
214
|
relation: 'approval_level_user_rel',
|
|
90
215
|
label: 'People',
|
|
91
216
|
}),
|
|
92
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* A many-to-one field of the record to a user (`user`, `project.user`); for "the record decides", the key passed
|
|
219
|
+
* to the model's `approvalUsers(key)` (optional).
|
|
220
|
+
*/
|
|
93
221
|
approverField: f.char({ label: 'Field' }),
|
|
94
222
|
/** How many decide: any one, all of them, or a number. */
|
|
95
223
|
quorum: f.selection({ any: 'Any one', all: 'All of them', count: 'A number of them' }, { required: true, default: 'any', label: 'Decided by' }),
|
|
@@ -127,7 +255,7 @@ const ApprovalLevel = defineModel('approval.level', {
|
|
|
127
255
|
const role = typeof r['approverRole'] === 'string' && r['approverRole'] !== '';
|
|
128
256
|
const users = Array.isArray(r['approverUsers']) ? r['approverUsers'].length : 0;
|
|
129
257
|
const kind = r['approverKind'];
|
|
130
|
-
return ((kind
|
|
258
|
+
return ((kind === 'role' && !role) ||
|
|
131
259
|
(kind === 'users' && users === 0) ||
|
|
132
260
|
(kind === 'field' &&
|
|
133
261
|
!(typeof r['approverField'] === 'string' && r['approverField'] !== '')) ||
|
|
@@ -136,7 +264,7 @@ const ApprovalLevel = defineModel('approval.level', {
|
|
|
136
264
|
.map((r) => r['id']);
|
|
137
265
|
});
|
|
138
266
|
},
|
|
139
|
-
message: 'A level names its approvers: a role
|
|
267
|
+
message: 'A level names its approvers: a role, people, or a field; a quorum by number needs the number.',
|
|
140
268
|
},
|
|
141
269
|
// A broken condition is refused when saved, not when a user submits a record.
|
|
142
270
|
validCondition: {
|
|
@@ -189,6 +317,11 @@ const ApprovalRequest = defineModel('approval.request', {
|
|
|
189
317
|
relation: 'approval_request_user_rel',
|
|
190
318
|
label: 'Approvers',
|
|
191
319
|
}),
|
|
320
|
+
/** Backups who may decide it too (from the level). */
|
|
321
|
+
backups: f.many2many('res.users', {
|
|
322
|
+
relation: 'approval_request_backup_rel',
|
|
323
|
+
label: 'Backups',
|
|
324
|
+
}),
|
|
192
325
|
/** How many approvals it needs. */
|
|
193
326
|
required: f.integer({ required: true, default: 1, label: 'Approvals needed' }),
|
|
194
327
|
/** The submission it belongs to (a resubmission after sending back is the next round). */
|
|
@@ -200,6 +333,10 @@ const ApprovalRequest = defineModel('approval.request', {
|
|
|
200
333
|
/** The role that may also decide since the deadline passed. */
|
|
201
334
|
escalatedTo: f.char({ label: 'Escalated to' }),
|
|
202
335
|
escalatedAt: f.datetime({ label: 'Escalated at' }),
|
|
336
|
+
/** When the level's turn came (every lower level approved); the deadline counts from it (RFC 0027 §2.5). */
|
|
337
|
+
startedAt: f.datetime({ label: 'Started' }),
|
|
338
|
+
/** How its approvers were found, written when it was made. */
|
|
339
|
+
explanation: f.text({ label: 'How the approvers were found' }),
|
|
203
340
|
state: f.selection({
|
|
204
341
|
pending: 'Pending',
|
|
205
342
|
approved: 'Approved',
|
|
@@ -213,6 +350,35 @@ const ApprovalRequest = defineModel('approval.request', {
|
|
|
213
350
|
comment: f.text(),
|
|
214
351
|
},
|
|
215
352
|
});
|
|
353
|
+
/**
|
|
354
|
+
* The matrix log (RFC 0027 §2.5): every step of a request — made, its turn, votes, decisions, cancelled, skipped,
|
|
355
|
+
* reminded, escalated — with who and a note. Private like requests: read through the record's approvals.
|
|
356
|
+
*/
|
|
357
|
+
const ApprovalEvent = defineModel('approval.event', {
|
|
358
|
+
order: ['at asc', 'id asc'],
|
|
359
|
+
fields: {
|
|
360
|
+
request: f.many2one('approval.request', { required: true, ondelete: 'cascade', index: true }),
|
|
361
|
+
at: f.datetime({ required: true }),
|
|
362
|
+
user: f.many2one('res.users', { ondelete: 'set null' }),
|
|
363
|
+
kind: f.selection({
|
|
364
|
+
created: 'Created',
|
|
365
|
+
started: 'Its turn',
|
|
366
|
+
voted: 'Voted',
|
|
367
|
+
approved: 'Approved',
|
|
368
|
+
rejected: 'Rejected',
|
|
369
|
+
sent_back: 'Sent back',
|
|
370
|
+
cancelled: 'Cancelled',
|
|
371
|
+
skipped: 'Skipped',
|
|
372
|
+
reminded: 'Reminded',
|
|
373
|
+
escalated: 'Escalated',
|
|
374
|
+
auto: 'Approved already',
|
|
375
|
+
reassigned: 'Reassigned',
|
|
376
|
+
synced: 'Synced',
|
|
377
|
+
notified: 'Backups told',
|
|
378
|
+
}, { required: true }),
|
|
379
|
+
note: f.text(),
|
|
380
|
+
},
|
|
381
|
+
});
|
|
216
382
|
/** One approver's decision on a request (a level decided by several keeps each). */
|
|
217
383
|
const ApprovalVote = defineModel('approval.vote', {
|
|
218
384
|
order: ['request asc', 'at asc', 'id asc'],
|
|
@@ -226,6 +392,10 @@ const ApprovalVote = defineModel('approval.vote', {
|
|
|
226
392
|
onBehalfOf: f.many2one('res.users', { ondelete: 'set null', label: 'For' }),
|
|
227
393
|
/** Decided from an email link: the address it came from. */
|
|
228
394
|
ip: f.char({ label: 'From address' }),
|
|
395
|
+
/** Copied from the person's approval at an earlier level (skip approvers who already approved). */
|
|
396
|
+
auto: f.boolean({ default: false, label: 'Approved already' }),
|
|
397
|
+
/** Decided as one of the level's backups. */
|
|
398
|
+
asBackup: f.boolean({ default: false, label: 'As backup' }),
|
|
229
399
|
},
|
|
230
400
|
sqlConstraints: {
|
|
231
401
|
once: { unique: ['request', 'user'], message: 'Each approver decides a request once.' },
|
|
@@ -311,9 +481,11 @@ export const permissionsShutro = defineModule({
|
|
|
311
481
|
}),
|
|
312
482
|
],
|
|
313
483
|
models: [
|
|
484
|
+
ResSegment,
|
|
314
485
|
ApprovalPolicy,
|
|
315
486
|
ApprovalLevel,
|
|
316
487
|
ApprovalRequest,
|
|
488
|
+
ApprovalEvent,
|
|
317
489
|
ApprovalVote,
|
|
318
490
|
ApprovalDelegation,
|
|
319
491
|
ApprovalLink,
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* - `GET /api/v1/approvals/cards?list=inbox|mine|history` the Approvals app's cards (RFC 0025)
|
|
11
11
|
* - `GET /api/v1/approvals/stats` waiting, overdue, decided this month, the median time
|
|
12
12
|
* - `POST /api/v1/approvals/bulk` `{ requests, comment? }`: approves each it may
|
|
13
|
+
* - `POST /api/v1/approvals/requests/:id/reassign` `{ users, mode: 'replace' | 'add', reason }` (RFC 0027)
|
|
14
|
+
* - `POST /api/v1/approvals/requests/:id/notify-backups` tells the level's backups it waits (RFC 0027)
|
|
15
|
+
* - `GET /api/v1/approvals/matrix?period=&mode=` the matrix dashboard: cards per policy, direct or delegated
|
|
13
16
|
*/
|
|
14
17
|
import { type ModuleContext } from '../server/index.js';
|
|
15
18
|
/** Registers the approval routes. */
|
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
* - `GET /api/v1/approvals/cards?list=inbox|mine|history` the Approvals app's cards (RFC 0025)
|
|
11
11
|
* - `GET /api/v1/approvals/stats` waiting, overdue, decided this month, the median time
|
|
12
12
|
* - `POST /api/v1/approvals/bulk` `{ requests, comment? }`: approves each it may
|
|
13
|
+
* - `POST /api/v1/approvals/requests/:id/reassign` `{ users, mode: 'replace' | 'add', reason }` (RFC 0027)
|
|
14
|
+
* - `POST /api/v1/approvals/requests/:id/notify-backups` tells the level's backups it waits (RFC 0027)
|
|
15
|
+
* - `GET /api/v1/approvals/matrix?period=&mode=` the matrix dashboard: cards per policy, direct or delegated
|
|
13
16
|
*/
|
|
14
17
|
import { HttpProblem } from '../server/index.js';
|
|
15
|
-
import { approvalCards, approvalStats, approveMany, decide, inbox, requestsOf, withdraw, } from './approvals.js';
|
|
18
|
+
import { approvalCards, approvalMatrix, approvalStats, approveMany, decide, eventsOf, inbox, MATRIX_PERIODS, notifyBackups, reassign, requestsOf, withdraw, } from './approvals.js';
|
|
16
19
|
function positive(value, what) {
|
|
17
20
|
const id = Number(value);
|
|
18
21
|
if (!Number.isSafeInteger(id) || id <= 0)
|
|
@@ -33,7 +36,12 @@ export function registerApprovalRoutes({ app }) {
|
|
|
33
36
|
app.get('/api/v1/approvals/:model/:id', async (request) => {
|
|
34
37
|
const { model, id } = request.params;
|
|
35
38
|
const recordId = positive(id, 'id');
|
|
36
|
-
return request.env(async (env) => ({
|
|
39
|
+
return request.env(async (env) => ({
|
|
40
|
+
requests: await requestsOf(env, model, recordId),
|
|
41
|
+
events: await eventsOf(env, model, recordId),
|
|
42
|
+
// Approvals managers reassign waiting requests (RFC 0027 §2.6).
|
|
43
|
+
manage: env.administrator || env.groups.has('approvals.manager'),
|
|
44
|
+
}), {
|
|
37
45
|
write: true, // reads through audited system scopes
|
|
38
46
|
});
|
|
39
47
|
});
|
|
@@ -47,6 +55,32 @@ export function registerApprovalRoutes({ app }) {
|
|
|
47
55
|
const text = comment(request);
|
|
48
56
|
return request.env(async (env) => decide(env, id, decision, text));
|
|
49
57
|
});
|
|
58
|
+
// RFC 0027 §2.7: the matrix dashboard.
|
|
59
|
+
app.get('/api/v1/approvals/matrix', async (request) => {
|
|
60
|
+
const q = (request.query ?? {});
|
|
61
|
+
const period = (q.period ?? '3m');
|
|
62
|
+
if (!MATRIX_PERIODS.includes(period))
|
|
63
|
+
throw new HttpProblem(400, 'invalid-query', 'Invalid query', `period is one of ${MATRIX_PERIODS.join(', ')}.`);
|
|
64
|
+
const mode = q.mode === 'delegated' ? 'delegated' : 'direct';
|
|
65
|
+
return request.env(async (env) => approvalMatrix(env, period, mode), { write: true });
|
|
66
|
+
});
|
|
67
|
+
// RFC 0027: reassigning a waiting request; telling its backups.
|
|
68
|
+
app.post('/api/v1/approvals/requests/:id/reassign', async (request) => {
|
|
69
|
+
const id = positive(request.params.id, 'id');
|
|
70
|
+
const body = (request.body ?? {});
|
|
71
|
+
const users = Array.isArray(body.users) ? body.users.map((u) => positive(u, 'users')) : [];
|
|
72
|
+
if (body.mode !== undefined && body.mode !== 'replace' && body.mode !== 'add')
|
|
73
|
+
throw new HttpProblem(400, 'invalid-body', 'Invalid body', "mode is 'replace' or 'add'.");
|
|
74
|
+
if (typeof body.reason !== 'string' || body.reason.length > 2000)
|
|
75
|
+
throw new HttpProblem(400, 'invalid-body', 'Invalid body', 'reason is a string (at most 2000).');
|
|
76
|
+
const mode = body.mode === 'add' ? 'add' : 'replace';
|
|
77
|
+
const reason = body.reason;
|
|
78
|
+
return request.env(async (env) => reassign(env, id, users, mode, reason));
|
|
79
|
+
});
|
|
80
|
+
app.post('/api/v1/approvals/requests/:id/notify-backups', async (request) => {
|
|
81
|
+
const id = positive(request.params.id, 'id');
|
|
82
|
+
return request.env(async (env) => ({ notified: await notifyBackups(env, id) }));
|
|
83
|
+
});
|
|
50
84
|
// The Approvals app (RFC 0025): its lists, its header, approving many at once.
|
|
51
85
|
app.get('/api/v1/approvals/cards', async (request) => {
|
|
52
86
|
const list = String(request.query?.list ?? 'inbox');
|
|
@@ -10,6 +10,8 @@ import { ApiError, Badge, Button, Textarea } from '../../ui/index.js';
|
|
|
10
10
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
11
11
|
import { useId, useState } from 'react';
|
|
12
12
|
import { useTranslation } from 'react-i18next';
|
|
13
|
+
/** Whole hours between two instants (to now when `to` is null). */
|
|
14
|
+
const hoursBetween = (from, to) => Math.max(0, Math.round(((to === null ? Date.now() : Date.parse(to)) - Date.parse(from)) / 3_600_000));
|
|
13
15
|
const TONE = {
|
|
14
16
|
pending: 'warning',
|
|
15
17
|
approved: 'success',
|
|
@@ -24,9 +26,11 @@ const DOT = {
|
|
|
24
26
|
sent_back: 'border-danger bg-danger/40',
|
|
25
27
|
cancelled: 'border-muted-foreground bg-muted',
|
|
26
28
|
};
|
|
27
|
-
function Round({ requests }) {
|
|
28
|
-
const { t } = useTranslation('approvalPanel');
|
|
29
|
-
return (_jsx("ol", { className: "grid gap-3", children: requests.map((r) => (_jsxs("li", { className: "flex gap-3 text-sm", children: [_jsx("span", { "aria-hidden": "true", className: `mt-1 size-3 shrink-0 rounded-full border-2 ${DOT[r.state]}` }), _jsxs("span", { className: "grid flex-1 gap-0.5", children: [_jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: r.levelName }), _jsx(Badge, { tone: TONE[r.state], children: t(`state.${r.state}`) }), r.required > 1 ? (_jsx("span", { className: "text-xs text-muted-foreground", children: t('votes', { n: r.approvals, of: r.required }) })) : null] }), r.approverRole === '' ? null : (_jsx("span", { className: "text-xs text-muted-foreground", children: r.approverRole })), r.comment === null ? null : _jsxs("span", { className: "text-xs", children: ["\u201C", r.comment, "\u201D"] })
|
|
29
|
+
function Round({ requests, events = [], }) {
|
|
30
|
+
const { t, i18n } = useTranslation('approvalPanel');
|
|
31
|
+
return (_jsx("ol", { className: "grid gap-3", children: requests.map((r) => (_jsxs("li", { className: "flex gap-3 text-sm", children: [_jsx("span", { "aria-hidden": "true", className: `mt-1 size-3 shrink-0 rounded-full border-2 ${DOT[r.state]}` }), _jsxs("span", { className: "grid flex-1 gap-0.5", children: [_jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: r.levelName }), _jsx(Badge, { tone: TONE[r.state], children: t(`state.${r.state}`) }), r.required > 1 ? (_jsx("span", { className: "text-xs text-muted-foreground", children: t('votes', { n: r.approvals, of: r.required }) })) : null] }), r.approverRole === '' ? null : (_jsx("span", { className: "text-xs text-muted-foreground", children: r.approverRole })), r.comment === null ? null : _jsxs("span", { className: "text-xs", children: ["\u201C", r.comment, "\u201D"] }), r.state === 'pending' && r.startedAt !== null ? (_jsx("span", { className: "text-xs text-muted-foreground", children: t('since', { hours: hoursBetween(r.startedAt, null) }) })) : null, r.explanation === null && !events.some((e) => e.request === r.id) ? null : (_jsxs("details", { className: "text-xs text-muted-foreground", children: [_jsx("summary", { className: "cursor-pointer", children: t('how') }), r.explanation === null ? null : _jsx("p", { className: "mt-1", children: r.explanation }), _jsx("ul", { className: "mt-1 grid gap-0.5", "aria-label": t('log'), children: events
|
|
32
|
+
.filter((e) => e.request === r.id)
|
|
33
|
+
.map((e) => (_jsxs("li", { children: [_jsx("time", { dateTime: e.at, children: new Date(e.at).toLocaleString(i18n.language) }), ' ', "\u00B7 ", t(`event.${e.kind}`), e.note === null || e.kind === 'created' ? '' : ` — ${e.note}`] }, e.id))) })] }))] })] }, r.id))) }));
|
|
30
34
|
}
|
|
31
35
|
export function ApprovalPanel({ record, model, api, version, reload, }) {
|
|
32
36
|
const { t } = useTranslation('approvalPanel');
|
|
@@ -54,6 +58,32 @@ export function ApprovalPanel({ record, model, api, version, reload, }) {
|
|
|
54
58
|
mutationFn: ({ request, decision, }) => api.call('POST', `/api/v1/approvals/requests/${request}/${decision}`, comment.trim() === '' ? {} : { comment: comment.trim() }),
|
|
55
59
|
onSuccess: done,
|
|
56
60
|
});
|
|
61
|
+
const [reassigning, setReassigning] = useState(false);
|
|
62
|
+
const [chosen, setChosen] = useState([]);
|
|
63
|
+
const [mode, setMode] = useState('replace');
|
|
64
|
+
const [reason, setReason] = useState('');
|
|
65
|
+
const people = useQuery({
|
|
66
|
+
queryKey: ['approvals', 'people'],
|
|
67
|
+
queryFn: () => api.call('GET', '/api/v1/models/res.users?fields=id,name&limit=200&order=name'),
|
|
68
|
+
enabled: reassigning,
|
|
69
|
+
});
|
|
70
|
+
const reassign = useMutation({
|
|
71
|
+
mutationFn: (request) => api.call('POST', `/api/v1/approvals/requests/${request}/reassign`, {
|
|
72
|
+
users: chosen,
|
|
73
|
+
mode,
|
|
74
|
+
reason: reason.trim(),
|
|
75
|
+
}),
|
|
76
|
+
onSuccess: async () => {
|
|
77
|
+
setReassigning(false);
|
|
78
|
+
setChosen([]);
|
|
79
|
+
setReason('');
|
|
80
|
+
await done();
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const tell = useMutation({
|
|
84
|
+
mutationFn: (request) => api.call('POST', `/api/v1/approvals/requests/${request}/notify-backups`, {}),
|
|
85
|
+
onSuccess: done,
|
|
86
|
+
});
|
|
57
87
|
const withdraw = useMutation({
|
|
58
88
|
mutationFn: () => api.call('POST', `/api/v1/approvals/${model}/${recordId}/withdraw`),
|
|
59
89
|
onSuccess: done,
|
|
@@ -69,6 +99,14 @@ export function ApprovalPanel({ record, model, api, version, reload, }) {
|
|
|
69
99
|
const mine = new Set((inbox.data?.requests ?? []).map((r) => r.id));
|
|
70
100
|
const actionable = current.find((r) => mine.has(r.id));
|
|
71
101
|
const asked = current.some((r) => r.state === 'pending' && r.requestedBy === me.data?.id);
|
|
72
|
-
const error = decide.error ?? withdraw.error;
|
|
73
|
-
|
|
102
|
+
const error = decide.error ?? withdraw.error ?? reassign.error ?? tell.error;
|
|
103
|
+
const manage = requests.data?.manage === true;
|
|
104
|
+
// The waiting request whose turn it is (the first, when levels decide side by side).
|
|
105
|
+
const waiting = current.find((r) => r.state === 'pending' && r.startedAt !== null);
|
|
106
|
+
const mayTell = waiting !== undefined &&
|
|
107
|
+
waiting.backups.length > 0 &&
|
|
108
|
+
(manage || waiting.approvers.includes(me.data?.id ?? -1));
|
|
109
|
+
return (_jsxs("section", { "aria-labelledby": `${id}-title`, className: "rounded-lg border bg-surface p-4", children: [_jsxs("h2", { id: `${id}-title`, className: "mb-3 flex items-center gap-2 text-sm font-semibold", children: [t('title'), last > 1 ? _jsx(Badge, { tone: "neutral", children: t('round', { n: last }) }) : null] }), current.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('withdrawn') })) : (_jsx(Round, { requests: current, events: requests.data?.events ?? [] })), earlier.length === 0 ? null : (_jsxs("details", { className: "mt-3 text-sm", children: [_jsx("summary", { className: "cursor-pointer text-muted-foreground", children: t('earlier', { count: earlier.length }) }), _jsx("div", { className: "mt-2", children: _jsx(Round, { requests: earlier }) })] })), actionable === undefined ? null : (_jsxs("div", { className: "mt-4 grid gap-2", children: [_jsx("label", { htmlFor: `${id}-comment`, className: "text-sm font-medium", children: t('yourDecision', { level: actionable.levelName }) }), _jsx(Textarea, { id: `${id}-comment`, placeholder: t('commentHint'), value: comment, onChange: (e) => setComment(e.target.value) }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "primary", disabled: decide.isPending, onClick: () => decide.mutate({ request: actionable.id, decision: 'approve' }), children: t('approve') }), _jsx(Button, { disabled: decide.isPending || comment.trim() === '', onClick: () => decide.mutate({ request: actionable.id, decision: 'send-back' }), children: t('sendBack') }), _jsx(Button, { variant: "danger", disabled: decide.isPending || comment.trim() === '', onClick: () => decide.mutate({ request: actionable.id, decision: 'reject' }), children: t('reject') })] })] })), waiting !== undefined && (manage || mayTell) ? (_jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [manage ? (_jsx(Button, { variant: "ghost", onClick: () => setReassigning((v) => !v), children: t('reassign') })) : null, mayTell ? (_jsx(Button, { variant: "ghost", disabled: tell.isPending, onClick: () => tell.mutate(waiting.id), children: t('tellBackups') })) : null] })) : null, reassigning && waiting !== undefined ? (_jsxs("fieldset", { className: "mt-3 grid gap-2 rounded-md border p-3", children: [_jsx("legend", { className: "px-1 text-sm font-medium", children: t('reassignLevel', { level: waiting.levelName }) }), _jsx("label", { htmlFor: `${id}-people`, className: "text-sm", children: t('newApprovers') }), _jsx("select", { id: `${id}-people`, multiple: true, className: "min-h-24 rounded-md border bg-surface p-1 text-sm", value: chosen.map(String), onChange: (e) => setChosen([...e.target.selectedOptions].map((o) => Number(o.value))), children: (people.data?.items ?? [])
|
|
110
|
+
.filter((u) => u.id !== waiting.requestedBy)
|
|
111
|
+
.map((u) => (_jsx("option", { value: u.id, children: u.name }, u.id))) }), _jsx("div", { className: "flex flex-wrap gap-3 text-sm", children: ['replace', 'add'].map((m) => (_jsxs("label", { className: "flex items-center gap-1", children: [_jsx("input", { type: "radio", name: `${id}-mode`, checked: mode === m, onChange: () => setMode(m) }), t(`mode.${m}`)] }, m))) }), _jsx("label", { htmlFor: `${id}-reason`, className: "text-sm", children: t('reason') }), _jsx(Textarea, { id: `${id}-reason`, value: reason, onChange: (e) => setReason(e.target.value) }), _jsx("div", { children: _jsx(Button, { variant: "primary", disabled: reassign.isPending || chosen.length === 0 || reason.trim() === '', onClick: () => reassign.mutate(waiting.id), children: t('reassignConfirm') }) })] })) : null, asked ? (_jsx("div", { className: "mt-3", children: _jsx(Button, { variant: "ghost", disabled: withdraw.isPending, onClick: () => withdraw.mutate(), children: t('withdraw') }) })) : null, error === null ? null : (_jsx("p", { role: "alert", className: "mt-2 text-sm text-danger", children: error instanceof ApiError ? error.message : String(error) }))] }));
|
|
74
112
|
}
|
|
@@ -12,6 +12,32 @@ export const messages = {
|
|
|
12
12
|
sendBack: 'Send back for changes',
|
|
13
13
|
reject: 'Reject',
|
|
14
14
|
withdraw: 'Withdraw my request',
|
|
15
|
+
how: 'How the approvers were found',
|
|
16
|
+
reassign: 'Reassign',
|
|
17
|
+
reassignLevel: 'Reassign {{level}}',
|
|
18
|
+
newApprovers: 'Who decides instead',
|
|
19
|
+
mode: { replace: 'Replace the approvers', add: 'Add to them' },
|
|
20
|
+
reason: 'Why (required)',
|
|
21
|
+
reassignConfirm: 'Reassign',
|
|
22
|
+
tellBackups: 'Tell the backups',
|
|
23
|
+
log: 'Log',
|
|
24
|
+
since: 'Its turn for {{hours}} h',
|
|
25
|
+
event: {
|
|
26
|
+
created: 'Created',
|
|
27
|
+
started: 'Its turn',
|
|
28
|
+
voted: 'Voted',
|
|
29
|
+
approved: 'Approved',
|
|
30
|
+
rejected: 'Rejected',
|
|
31
|
+
sent_back: 'Sent back',
|
|
32
|
+
cancelled: 'Cancelled',
|
|
33
|
+
skipped: 'Skipped',
|
|
34
|
+
reminded: 'Reminded',
|
|
35
|
+
escalated: 'Escalated',
|
|
36
|
+
auto: 'Approved already',
|
|
37
|
+
reassigned: 'Reassigned',
|
|
38
|
+
synced: 'Synced',
|
|
39
|
+
notified: 'Backups told',
|
|
40
|
+
},
|
|
15
41
|
state: {
|
|
16
42
|
pending: 'Waiting',
|
|
17
43
|
approved: 'Approved',
|
|
@@ -32,6 +58,32 @@ export const messages = {
|
|
|
32
58
|
sendBack: 'সংশোধনের জন্য ফেরত',
|
|
33
59
|
reject: 'প্রত্যাখ্যান',
|
|
34
60
|
withdraw: 'আমার অনুরোধ প্রত্যাহার',
|
|
61
|
+
how: 'অনুমোদনকারী কীভাবে নির্ধারিত হলেন',
|
|
62
|
+
reassign: 'অন্যকে দিন',
|
|
63
|
+
reassignLevel: '{{level}} অন্যকে দিন',
|
|
64
|
+
newApprovers: 'এর বদলে কে সিদ্ধান্ত নেবেন',
|
|
65
|
+
mode: { replace: 'অনুমোদনকারী বদলে দিন', add: 'তাঁদের সঙ্গে যোগ করুন' },
|
|
66
|
+
reason: 'কারণ (আবশ্যক)',
|
|
67
|
+
reassignConfirm: 'অন্যকে দিন',
|
|
68
|
+
tellBackups: 'বিকল্পদের জানান',
|
|
69
|
+
log: 'লগ',
|
|
70
|
+
since: '{{hours}} ঘণ্টা ধরে এর পালা',
|
|
71
|
+
event: {
|
|
72
|
+
created: 'তৈরি',
|
|
73
|
+
started: 'এর পালা',
|
|
74
|
+
voted: 'ভোট',
|
|
75
|
+
approved: 'অনুমোদিত',
|
|
76
|
+
rejected: 'প্রত্যাখ্যাত',
|
|
77
|
+
sent_back: 'ফেরত পাঠানো',
|
|
78
|
+
cancelled: 'বাতিল',
|
|
79
|
+
skipped: 'বাদ দেওয়া',
|
|
80
|
+
reminded: 'মনে করানো',
|
|
81
|
+
escalated: 'ঊর্ধ্বতনে পাঠানো',
|
|
82
|
+
auto: 'আগেই অনুমোদিত',
|
|
83
|
+
reassigned: 'অন্যকে দেওয়া',
|
|
84
|
+
synced: 'মিলিয়ে নেওয়া',
|
|
85
|
+
notified: 'বিকল্পদের জানানো',
|
|
86
|
+
},
|
|
35
87
|
state: {
|
|
36
88
|
pending: 'অপেক্ষায়',
|
|
37
89
|
approved: 'অনুমোদিত',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: ebc3fbac95760b14
|
|
2
2
|
// Bindings of module `portal`: import the definition API from here to get types scoped to what it can see.
|
|
3
3
|
export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
|
|
4
4
|
import { definitions } from '../../server/orm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash:
|
|
1
|
+
// GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: ebc3fbac95760b14
|
|
2
2
|
// Schema view of module `portal` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
|
|
3
3
|
import type { F } from '../../server/orm.js';
|
|
4
4
|
|
|
@@ -508,6 +508,7 @@ export interface ResPartner {
|
|
|
508
508
|
readonly parent: F.Many2one<'res.partner'>;
|
|
509
509
|
readonly contacts: F.One2many<'res.partner', { readonly required: true }>;
|
|
510
510
|
readonly email: F.Char;
|
|
511
|
+
readonly lang: F.Selection<'en' | 'bn'>;
|
|
511
512
|
readonly phone: F.Char;
|
|
512
513
|
readonly mobile: F.Char;
|
|
513
514
|
readonly website: F.Char;
|
|
@@ -12,6 +12,9 @@ export interface PortalDocument {
|
|
|
12
12
|
readonly model: ApiModel;
|
|
13
13
|
/** The lines' model, when the document has lines. */
|
|
14
14
|
readonly lineModel: ApiModel | null;
|
|
15
|
+
/** The optional items' model and the field that points back at the document, when it has options. */
|
|
16
|
+
readonly optionModel: ApiModel | null;
|
|
17
|
+
readonly optionParent: string | null;
|
|
15
18
|
}
|
|
16
19
|
/** The documents of the loaded modules, by key. @throws {ServerConfigError} for a declaration that cannot work */
|
|
17
20
|
export declare function portalDocuments(registry: KoshRegistry): ReadonlyMap<string, PortalDocument>;
|
package/dist/portal/documents.js
CHANGED
|
@@ -50,12 +50,32 @@ export function portalDocuments(registry) {
|
|
|
50
50
|
if (!lineModel.byKey.has(field))
|
|
51
51
|
fail(key, `${lines?.target} has no field '${field}'.`, 'Declare existing line fields.');
|
|
52
52
|
}
|
|
53
|
+
let optionModel = null;
|
|
54
|
+
let optionParent = null;
|
|
55
|
+
if (spec.options !== undefined) {
|
|
56
|
+
const options = m.byKey.get(spec.options.field);
|
|
57
|
+
if (options?.kind !== 'one2many' || options.target === null)
|
|
58
|
+
fail(key, `'${spec.options.field}' is not a one2many.`, 'Name the options field.');
|
|
59
|
+
optionModel = models.get(options?.target ?? '') ?? null;
|
|
60
|
+
if (optionModel === null)
|
|
61
|
+
fail(key, `the options' model ${options?.target} is not served.`, 'Use a served model.');
|
|
62
|
+
const om = optionModel;
|
|
63
|
+
for (const field of spec.options.fields)
|
|
64
|
+
if (!om.byKey.has(field))
|
|
65
|
+
fail(key, `${om.name} has no field '${field}'.`, 'Declare existing option fields.');
|
|
66
|
+
if (!om.transitions.includes(spec.options.add))
|
|
67
|
+
fail(key, `${om.name} has no transition '${spec.options.add}'.`, 'Name the transition that adds an option.');
|
|
68
|
+
optionParent =
|
|
69
|
+
om.fields.find((f) => f.kind === 'many2one' && f.target === spec.model)?.key ?? null;
|
|
70
|
+
if (optionParent === null)
|
|
71
|
+
fail(key, `${om.name} has no many2one to ${spec.model}.`, 'Options point at their document.');
|
|
72
|
+
}
|
|
53
73
|
for (const action of spec.actions ?? [])
|
|
54
74
|
if (!m.transitions.includes(action.transition))
|
|
55
75
|
fail(key, `${spec.model} has no transition '${action.transition}' (action '${action.key}').`, `Transitions: ${m.transitions.join(', ') || 'none'}.`);
|
|
56
76
|
if (spec.pdf !== undefined && !templates.has(spec.pdf))
|
|
57
77
|
fail(key, `no document template '${spec.pdf}'.`, 'Name a template a module ships (module.key).');
|
|
58
|
-
out.set(key, { spec, model: m, lineModel });
|
|
78
|
+
out.set(key, { spec, model: m, lineModel, optionModel, optionParent });
|
|
59
79
|
}
|
|
60
80
|
return out;
|
|
61
81
|
}
|