shutro-erp 0.3.4 → 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.
- package/dist/account_asset/.kosh/bindings.d.ts +22 -0
- package/dist/account_asset/.kosh/bindings.js +15 -0
- package/dist/account_asset/.kosh/schema.d.ts +1755 -0
- package/dist/account_asset/board.d.ts +61 -0
- package/dist/account_asset/board.js +86 -0
- package/dist/account_asset/books.d.ts +42 -0
- package/dist/account_asset/books.js +398 -0
- package/dist/account_asset/demo.d.ts +7 -0
- package/dist/account_asset/demo.js +72 -0
- package/dist/account_asset/index.d.ts +9 -0
- package/dist/account_asset/index.js +335 -0
- package/dist/account_asset/module.d.ts +7622 -0
- package/dist/account_asset/module.js +230 -0
- package/dist/account_budget/.kosh/bindings.d.ts +22 -0
- package/dist/account_budget/.kosh/bindings.js +15 -0
- package/dist/account_budget/.kosh/schema.d.ts +1767 -0
- package/dist/account_budget/budget.d.ts +5 -0
- package/dist/account_budget/budget.js +142 -0
- package/dist/account_budget/demo.d.ts +6 -0
- package/dist/account_budget/demo.js +30 -0
- package/dist/account_budget/index.d.ts +4 -0
- package/dist/account_budget/index.js +223 -0
- package/dist/account_budget/module.d.ts +13863 -0
- package/dist/account_budget/module.js +167 -0
- package/dist/account_portal/.kosh/bindings.js +1 -1
- package/dist/account_portal/.kosh/schema.d.ts +176 -6
- package/dist/account_portal/module.d.ts +505 -6
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +176 -6
- package/dist/accounting/index.d.ts +1 -1
- package/dist/accounting/index.js +344 -4
- package/dist/accounting/module.d.ts +513 -5
- package/dist/accounting/module.js +55 -3
- package/dist/approvals/.kosh/bindings.d.ts +22 -0
- package/dist/approvals/.kosh/bindings.js +15 -0
- package/dist/approvals/.kosh/schema.d.ts +611 -0
- package/dist/approvals/index.d.ts +18 -0
- package/dist/approvals/index.js +302 -0
- package/dist/approvals/mails.d.ts +25 -0
- package/dist/approvals/mails.js +82 -0
- package/dist/approvals/module.d.ts +1 -0
- package/dist/approvals/module.js +12 -0
- package/dist/approvals/web/History.d.ts +4 -0
- package/dist/approvals/web/History.js +13 -0
- package/dist/approvals/web/Inbox.d.ts +9 -0
- package/dist/approvals/web/Inbox.js +114 -0
- package/dist/approvals/web/Mine.d.ts +7 -0
- package/dist/approvals/web/Mine.js +27 -0
- package/dist/approvals/web/index.d.ts +13 -0
- package/dist/approvals/web/index.js +22 -0
- package/dist/approvals/web/messages.d.ts +2 -0
- package/dist/approvals/web/messages.js +119 -0
- package/dist/approvals/web/shared.d.ts +59 -0
- package/dist/approvals/web/shared.js +46 -0
- package/dist/attendance/.kosh/bindings.d.ts +22 -0
- package/dist/attendance/.kosh/bindings.js +15 -0
- package/dist/attendance/.kosh/schema.d.ts +1048 -0
- package/dist/attendance/days.d.ts +96 -0
- package/dist/attendance/days.js +430 -0
- package/dist/attendance/demo.d.ts +7 -0
- package/dist/attendance/demo.js +58 -0
- package/dist/attendance/index.d.ts +4 -0
- package/dist/attendance/index.js +513 -0
- package/dist/attendance/module.d.ts +3969 -0
- package/dist/attendance/module.js +412 -0
- package/dist/attendance/routes.d.ts +16 -0
- package/dist/attendance/routes.js +266 -0
- package/dist/attendance/web/Check.d.ts +8 -0
- package/dist/attendance/web/Check.js +40 -0
- package/dist/attendance/web/Kiosk.d.ts +8 -0
- package/dist/attendance/web/Kiosk.js +62 -0
- package/dist/attendance/web/PinButton.d.ts +7 -0
- package/dist/attendance/web/PinButton.js +29 -0
- package/dist/attendance/web/index.d.ts +13 -0
- package/dist/attendance/web/index.js +22 -0
- package/dist/attendance/web/messages.d.ts +2 -0
- package/dist/attendance/web/messages.js +61 -0
- package/dist/calendar/.kosh/bindings.d.ts +22 -0
- package/dist/calendar/.kosh/bindings.js +15 -0
- package/dist/calendar/.kosh/schema.d.ts +795 -0
- package/dist/calendar/demo.d.ts +6 -0
- package/dist/calendar/demo.js +24 -0
- package/dist/calendar/index.d.ts +3 -0
- package/dist/calendar/index.js +291 -0
- package/dist/calendar/module.d.ts +1705 -0
- package/dist/calendar/module.js +180 -0
- package/dist/cli/init.js +80 -0
- package/dist/cli/templates.d.ts +1 -1
- package/dist/cli/templates.js +226 -0
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +78 -3
- package/dist/contacts/demo.js +30 -1
- package/dist/contacts/index.d.ts +1 -1
- package/dist/contacts/index.js +155 -9
- package/dist/contacts/module.d.ts +338 -2
- package/dist/contacts/module.js +93 -5
- package/dist/crm/.kosh/bindings.d.ts +22 -0
- package/dist/crm/.kosh/bindings.js +15 -0
- package/dist/crm/.kosh/schema.d.ts +800 -0
- package/dist/crm/demo.d.ts +6 -0
- package/dist/crm/demo.js +45 -0
- package/dist/crm/index.d.ts +3 -0
- package/dist/crm/index.js +361 -0
- package/dist/crm/module.d.ts +1845 -0
- package/dist/crm/module.js +208 -0
- package/dist/documents/.kosh/bindings.d.ts +22 -0
- package/dist/documents/.kosh/bindings.js +15 -0
- package/dist/documents/.kosh/schema.d.ts +862 -0
- package/dist/documents/demo.d.ts +6 -0
- package/dist/documents/demo.js +22 -0
- package/dist/documents/documents.d.ts +98 -0
- package/dist/documents/documents.js +318 -0
- package/dist/documents/index.d.ts +30 -0
- package/dist/documents/index.js +442 -0
- package/dist/documents/module.d.ts +2781 -0
- package/dist/documents/module.js +214 -0
- package/dist/documents/web/Versions.d.ts +7 -0
- package/dist/documents/web/Versions.js +45 -0
- package/dist/documents/web/index.d.ts +11 -0
- package/dist/documents/web/index.js +16 -0
- package/dist/documents/web/messages.d.ts +2 -0
- package/dist/documents/web/messages.js +21 -0
- package/dist/event/.kosh/bindings.d.ts +22 -0
- package/dist/event/.kosh/bindings.js +15 -0
- package/dist/event/.kosh/schema.d.ts +843 -0
- package/dist/event/demo.d.ts +6 -0
- package/dist/event/demo.js +65 -0
- package/dist/event/index.d.ts +14 -0
- package/dist/event/index.js +333 -0
- package/dist/event/mails.d.ts +15 -0
- package/dist/event/mails.js +112 -0
- package/dist/event/module.d.ts +2800 -0
- package/dist/event/module.js +323 -0
- package/dist/event/web/CheckInBox.d.ts +9 -0
- package/dist/event/web/CheckInBox.js +87 -0
- package/dist/event/web/index.d.ts +11 -0
- package/dist/event/web/index.js +16 -0
- package/dist/event/web/messages.d.ts +2 -0
- package/dist/event/web/messages.js +23 -0
- package/dist/event_account/.kosh/bindings.d.ts +22 -0
- package/dist/event_account/.kosh/bindings.js +15 -0
- package/dist/event_account/.kosh/schema.d.ts +1758 -0
- package/dist/event_account/index.d.ts +3 -0
- package/dist/event_account/index.js +28 -0
- package/dist/event_account/module.d.ts +7114 -0
- package/dist/event_account/module.js +90 -0
- package/dist/expense/.kosh/bindings.d.ts +22 -0
- package/dist/expense/.kosh/bindings.js +15 -0
- package/dist/expense/.kosh/schema.d.ts +1299 -0
- package/dist/expense/demo.d.ts +7 -0
- package/dist/expense/demo.js +69 -0
- package/dist/expense/index.d.ts +3 -0
- package/dist/expense/index.js +495 -0
- package/dist/expense/module.d.ts +4747 -0
- package/dist/expense/module.js +647 -0
- package/dist/expense_account/.kosh/bindings.d.ts +22 -0
- package/dist/expense_account/.kosh/bindings.js +15 -0
- package/dist/expense_account/.kosh/schema.d.ts +1936 -0
- package/dist/expense_account/index.d.ts +3 -0
- package/dist/expense_account/index.js +74 -0
- package/dist/expense_account/module.d.ts +9153 -0
- package/dist/expense_account/module.js +318 -0
- package/dist/helpdesk/.kosh/bindings.d.ts +22 -0
- package/dist/helpdesk/.kosh/bindings.js +15 -0
- package/dist/helpdesk/.kosh/schema.d.ts +885 -0
- package/dist/helpdesk/demo.d.ts +7 -0
- package/dist/helpdesk/demo.js +65 -0
- package/dist/helpdesk/hours.d.ts +17 -0
- package/dist/helpdesk/hours.js +94 -0
- package/dist/helpdesk/index.d.ts +10 -0
- package/dist/helpdesk/index.js +461 -0
- package/dist/helpdesk/module.d.ts +2940 -0
- package/dist/helpdesk/module.js +326 -0
- package/dist/helpdesk/routes.d.ts +12 -0
- package/dist/helpdesk/routes.js +132 -0
- package/dist/helpdesk/sla.d.ts +37 -0
- package/dist/helpdesk/sla.js +226 -0
- package/dist/hr/.kosh/bindings.d.ts +22 -0
- package/dist/hr/.kosh/bindings.js +15 -0
- package/dist/hr/.kosh/schema.d.ts +878 -0
- package/dist/hr/demo.d.ts +6 -0
- package/dist/hr/demo.js +60 -0
- package/dist/hr/index.d.ts +3 -0
- package/dist/hr/index.js +483 -0
- package/dist/hr/module.d.ts +2611 -0
- package/dist/hr/module.js +350 -0
- package/dist/l10n_bd/.kosh/bindings.js +1 -1
- package/dist/l10n_bd/.kosh/schema.d.ts +176 -6
- package/dist/l10n_bd/module.d.ts +505 -6
- package/dist/l10n_bd_payroll/.kosh/bindings.d.ts +22 -0
- package/dist/l10n_bd_payroll/.kosh/bindings.js +15 -0
- package/dist/l10n_bd_payroll/.kosh/schema.d.ts +2312 -0
- package/dist/l10n_bd_payroll/data.d.ts +47 -0
- package/dist/l10n_bd_payroll/data.js +347 -0
- package/dist/l10n_bd_payroll/index.d.ts +4 -0
- package/dist/l10n_bd_payroll/index.js +24 -0
- package/dist/l10n_bd_payroll/module.d.ts +12932 -0
- package/dist/l10n_bd_payroll/module.js +86 -0
- package/dist/l10n_in/.kosh/bindings.js +1 -1
- package/dist/l10n_in/.kosh/schema.d.ts +176 -6
- package/dist/l10n_in/module.d.ts +527 -7
- package/dist/mail/inbound/handle.js +6 -1
- package/dist/maintenance/.kosh/bindings.d.ts +22 -0
- package/dist/maintenance/.kosh/bindings.js +15 -0
- package/dist/maintenance/.kosh/schema.d.ts +911 -0
- package/dist/maintenance/demo.d.ts +6 -0
- package/dist/maintenance/demo.js +60 -0
- package/dist/maintenance/index.d.ts +3 -0
- package/dist/maintenance/index.js +422 -0
- package/dist/maintenance/module.d.ts +3545 -0
- package/dist/maintenance/module.js +446 -0
- package/dist/maintenance_stock/.kosh/bindings.d.ts +22 -0
- package/dist/maintenance_stock/.kosh/bindings.js +15 -0
- package/dist/maintenance_stock/.kosh/schema.d.ts +1446 -0
- package/dist/maintenance_stock/index.d.ts +9 -0
- package/dist/maintenance_stock/index.js +52 -0
- package/dist/maintenance_stock/module.d.ts +7675 -0
- package/dist/maintenance_stock/module.js +192 -0
- package/dist/modules-sdk/index.d.ts +1 -1
- package/dist/modules-sdk/manifest.d.ts +109 -2
- package/dist/modules-sdk/manifest.js +35 -1
- package/dist/mrp/.kosh/bindings.d.ts +22 -0
- package/dist/mrp/.kosh/bindings.js +15 -0
- package/dist/mrp/.kosh/schema.d.ts +1453 -0
- package/dist/mrp/demo.d.ts +7 -0
- package/dist/mrp/demo.js +42 -0
- package/dist/mrp/index.d.ts +3 -0
- package/dist/mrp/index.js +393 -0
- package/dist/mrp/module.d.ts +9335 -0
- package/dist/mrp/module.js +757 -0
- package/dist/mrp_account/.kosh/bindings.d.ts +22 -0
- package/dist/mrp_account/.kosh/bindings.js +15 -0
- package/dist/mrp_account/.kosh/schema.d.ts +2299 -0
- package/dist/mrp_account/index.d.ts +3 -0
- package/dist/mrp_account/index.js +22 -0
- package/dist/mrp_account/module.d.ts +23151 -0
- package/dist/mrp_account/module.js +123 -0
- package/dist/payment/.kosh/bindings.js +1 -1
- package/dist/payment/.kosh/schema.d.ts +178 -7
- package/dist/payment/index.d.ts +41 -0
- package/dist/payment/index.js +131 -74
- package/dist/payment/module.d.ts +569 -12
- package/dist/payment/module.js +19 -3
- package/dist/payment/settle.js +10 -1
- package/dist/payroll/.kosh/bindings.d.ts +22 -0
- package/dist/payroll/.kosh/bindings.js +15 -0
- package/dist/payroll/.kosh/schema.d.ts +1390 -0
- package/dist/payroll/compute.d.ts +35 -0
- package/dist/payroll/compute.js +398 -0
- package/dist/payroll/demo.d.ts +55 -0
- package/dist/payroll/demo.js +121 -0
- package/dist/payroll/formula.d.ts +62 -0
- package/dist/payroll/formula.js +320 -0
- package/dist/payroll/index.d.ts +230 -0
- package/dist/payroll/index.js +713 -0
- package/dist/payroll/module.d.ts +9390 -0
- package/dist/payroll/module.js +606 -0
- package/dist/payroll/routes.d.ts +10 -0
- package/dist/payroll/routes.js +112 -0
- package/dist/payroll_account/.kosh/bindings.d.ts +22 -0
- package/dist/payroll_account/.kosh/bindings.js +15 -0
- package/dist/payroll_account/.kosh/schema.d.ts +2308 -0
- package/dist/payroll_account/index.d.ts +3 -0
- package/dist/payroll_account/index.js +40 -0
- package/dist/payroll_account/module.d.ts +12396 -0
- package/dist/payroll_account/module.js +256 -0
- package/dist/permissions/approvals.d.ts +131 -9
- package/dist/permissions/approvals.js +662 -57
- package/dist/permissions/index.d.ts +1 -1
- package/dist/permissions/index.js +2 -2
- package/dist/permissions/models.js +191 -12
- package/dist/permissions/routes.d.ts +6 -1
- package/dist/permissions/routes.js +37 -4
- package/dist/permissions/web/ApprovalPanel.d.ts +5 -4
- package/dist/permissions/web/ApprovalPanel.js +39 -13
- package/dist/permissions/web/index.d.ts +6 -4
- package/dist/permissions/web/index.js +13 -4
- package/dist/permissions/web/messages.d.ts +2 -0
- package/dist/permissions/web/messages.js +43 -0
- package/dist/portal/.kosh/bindings.js +1 -1
- package/dist/portal/.kosh/schema.d.ts +78 -3
- package/dist/portal/index.js +1 -1
- package/dist/portal/module.d.ts +166 -2
- package/dist/pos/.kosh/bindings.d.ts +22 -0
- package/dist/pos/.kosh/bindings.js +15 -0
- package/dist/pos/.kosh/schema.d.ts +1809 -0
- package/dist/pos/accounting.d.ts +10 -0
- package/dist/pos/accounting.js +149 -0
- package/dist/pos/demo.d.ts +6 -0
- package/dist/pos/demo.js +90 -0
- package/dist/pos/index.d.ts +9 -0
- package/dist/pos/index.js +353 -0
- package/dist/pos/module.d.ts +9783 -0
- package/dist/pos/module.js +423 -0
- package/dist/pos/routes.d.ts +39 -0
- package/dist/pos/routes.js +368 -0
- package/dist/pos/web/Register.d.ts +9 -0
- package/dist/pos/web/Register.js +252 -0
- package/dist/pos/web/index.d.ts +11 -0
- package/dist/pos/web/index.js +16 -0
- package/dist/pos/web/messages.d.ts +2 -0
- package/dist/pos/web/messages.js +87 -0
- package/dist/pos_stock/.kosh/bindings.d.ts +22 -0
- package/dist/pos_stock/.kosh/bindings.js +15 -0
- package/dist/pos_stock/.kosh/schema.d.ts +2208 -0
- package/dist/pos_stock/index.d.ts +9 -0
- package/dist/pos_stock/index.js +35 -0
- package/dist/pos_stock/module.d.ts +12803 -0
- package/dist/pos_stock/module.js +115 -0
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +52 -1
- package/dist/product/index.js +94 -4
- package/dist/product/module.d.ts +45 -1
- package/dist/product/module.js +11 -3
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +725 -3
- package/dist/project/index.d.ts +3 -1
- package/dist/project/index.js +1045 -18
- package/dist/project/module.d.ts +10168 -458
- package/dist/project/module.js +1578 -14
- package/dist/project/routes.d.ts +44 -0
- package/dist/project/routes.js +314 -0
- package/dist/project/schedule.d.ts +72 -0
- package/dist/project/schedule.js +192 -0
- package/dist/project/web/Refresh.d.ts +8 -0
- package/dist/project/web/Refresh.js +29 -0
- package/dist/project/web/Schedule.d.ts +9 -0
- package/dist/project/web/Schedule.js +93 -0
- package/dist/project/web/Structure.d.ts +8 -0
- package/dist/project/web/Structure.js +86 -0
- package/dist/project/web/Workload.d.ts +8 -0
- package/dist/project/web/Workload.js +43 -0
- package/dist/project/web/index.d.ts +14 -0
- package/dist/project/web/index.js +25 -0
- package/dist/project/web/messages.d.ts +2 -0
- package/dist/project/web/messages.js +85 -0
- package/dist/project_account/.kosh/bindings.d.ts +22 -0
- package/dist/project_account/.kosh/bindings.js +15 -0
- package/dist/project_account/.kosh/schema.d.ts +2241 -0
- package/dist/project_account/index.d.ts +3 -0
- package/dist/project_account/index.js +27 -0
- package/dist/project_account/module.d.ts +11539 -0
- package/dist/project_account/module.js +76 -0
- package/dist/project_portal/.kosh/bindings.js +1 -1
- package/dist/project_portal/.kosh/schema.d.ts +725 -3
- package/dist/project_portal/module.d.ts +5137 -119
- package/dist/project_purchase/.kosh/bindings.d.ts +22 -0
- package/dist/project_purchase/.kosh/bindings.js +15 -0
- package/dist/project_purchase/.kosh/schema.d.ts +2320 -0
- package/dist/project_purchase/index.d.ts +9 -0
- package/dist/project_purchase/index.js +22 -0
- package/dist/project_purchase/module.d.ts +25147 -0
- package/dist/project_purchase/module.js +56 -0
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +150 -6
- package/dist/purchase/index.js +163 -3
- package/dist/purchase/module.d.ts +228 -5
- package/dist/purchase/module.js +30 -3
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +180 -8
- package/dist/purchase_account/index.js +27 -1
- package/dist/purchase_account/module.d.ts +591 -30
- package/dist/purchase_account/module.js +13 -2
- package/dist/purchase_portal/.kosh/bindings.js +1 -1
- package/dist/purchase_portal/.kosh/schema.d.ts +150 -6
- package/dist/purchase_portal/module.d.ts +372 -6
- package/dist/purchase_stock/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_stock/.kosh/bindings.js +15 -0
- package/dist/purchase_stock/.kosh/schema.d.ts +1479 -0
- package/dist/purchase_stock/index.d.ts +7 -0
- package/dist/purchase_stock/index.js +98 -0
- package/dist/purchase_stock/module.d.ts +7535 -0
- package/dist/purchase_stock/module.js +380 -0
- package/dist/purchase_stock_account/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_stock_account/.kosh/bindings.js +15 -0
- package/dist/purchase_stock_account/.kosh/schema.d.ts +2155 -0
- package/dist/purchase_stock_account/index.d.ts +3 -0
- package/dist/purchase_stock_account/index.js +24 -0
- package/dist/purchase_stock_account/module.d.ts +38015 -0
- package/dist/purchase_stock_account/module.js +168 -0
- package/dist/recruitment/.kosh/bindings.d.ts +22 -0
- package/dist/recruitment/.kosh/bindings.js +15 -0
- package/dist/recruitment/.kosh/schema.d.ts +991 -0
- package/dist/recruitment/demo.d.ts +6 -0
- package/dist/recruitment/demo.js +45 -0
- package/dist/recruitment/index.d.ts +3 -0
- package/dist/recruitment/index.js +385 -0
- package/dist/recruitment/module.d.ts +3249 -0
- package/dist/recruitment/module.js +194 -0
- package/dist/recruitment/web/HireButton.d.ts +9 -0
- package/dist/recruitment/web/HireButton.js +32 -0
- package/dist/recruitment/web/index.d.ts +11 -0
- package/dist/recruitment/web/index.js +16 -0
- package/dist/recruitment/web/messages.d.ts +2 -0
- package/dist/recruitment/web/messages.js +19 -0
- package/dist/reporting/documents.d.ts +5 -0
- package/dist/reporting/documents.js +28 -1
- package/dist/reporting/index.d.ts +1 -1
- package/dist/reporting/index.js +4 -2
- package/dist/reporting/models.js +9 -0
- package/dist/reporting/overviews.d.ts +16 -0
- package/dist/reporting/overviews.js +56 -0
- package/dist/reporting/routes-documents.js +1 -1
- package/dist/reporting/routes.js +138 -80
- package/dist/reporting/web/DashboardView.d.ts +11 -0
- package/dist/reporting/web/DashboardView.js +2 -2
- package/dist/reporting/web/DesignerView.js +5 -0
- package/dist/reporting/web/Overview.d.ts +9 -0
- package/dist/reporting/web/Overview.js +53 -0
- package/dist/reporting/web/index.d.ts +1 -0
- package/dist/reporting/web/index.js +4 -1
- package/dist/reporting/web/messages.js +9 -0
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +181 -6
- package/dist/sale_account/index.js +22 -1
- package/dist/sale_account/module.d.ts +639 -38
- package/dist/sale_portal/.kosh/bindings.js +1 -1
- package/dist/sale_portal/.kosh/schema.d.ts +59 -3
- package/dist/sale_portal/module.d.ts +435 -5
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +732 -3
- package/dist/sale_project/module.d.ts +5349 -208
- package/dist/sale_project/module.js +17 -1
- package/dist/sale_stock/.kosh/bindings.d.ts +22 -0
- package/dist/sale_stock/.kosh/bindings.js +15 -0
- package/dist/sale_stock/.kosh/schema.d.ts +1303 -0
- package/dist/sale_stock/index.d.ts +7 -0
- package/dist/sale_stock/index.js +84 -0
- package/dist/sale_stock/module.d.ts +7514 -0
- package/dist/sale_stock/module.js +146 -0
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +59 -3
- package/dist/sales/index.js +195 -4
- package/dist/sales/module.d.ts +356 -3
- package/dist/sales/module.js +78 -3
- package/dist/server/api/metadata.d.ts +5 -0
- package/dist/server/api/metadata.js +5 -0
- package/dist/server/api/rest.js +64 -1
- package/dist/server/api/views.js +1 -0
- package/dist/server/app-settings.d.ts +7 -0
- package/dist/server/app-settings.js +10 -0
- package/dist/server/auth/base.js +9 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/settings/app-settings.d.ts +2 -0
- package/dist/settings/app-settings.js +518 -0
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +15 -0
- package/dist/settings/people.js +29 -5
- package/dist/settings/web/AppSettingsPage.d.ts +11 -0
- package/dist/settings/web/AppSettingsPage.js +210 -0
- package/dist/settings/web/OverviewPage.js +16 -3
- package/dist/settings/web/PeoplePage.js +20 -6
- package/dist/settings/web/index.d.ts +1 -0
- package/dist/settings/web/index.js +3 -0
- package/dist/settings/web/messages.js +118 -2
- package/dist/sign/.kosh/bindings.d.ts +22 -0
- package/dist/sign/.kosh/bindings.js +15 -0
- package/dist/sign/.kosh/schema.d.ts +990 -0
- package/dist/sign/index.d.ts +14 -0
- package/dist/sign/index.js +350 -0
- package/dist/sign/module.d.ts +4478 -0
- package/dist/sign/module.js +158 -0
- package/dist/sign/sign.d.ts +82 -0
- package/dist/sign/sign.js +474 -0
- package/dist/stock/.kosh/bindings.d.ts +22 -0
- package/dist/stock/.kosh/bindings.js +15 -0
- package/dist/stock/.kosh/schema.d.ts +1222 -0
- package/dist/stock/demo.d.ts +8 -0
- package/dist/stock/demo.js +82 -0
- package/dist/stock/index.d.ts +4 -0
- package/dist/stock/index.js +903 -0
- package/dist/stock/module.d.ts +7856 -0
- package/dist/stock/module.js +1880 -0
- package/dist/stock/replenish.d.ts +38 -0
- package/dist/stock/replenish.js +176 -0
- package/dist/stock/web/Replenishment.d.ts +8 -0
- package/dist/stock/web/Replenishment.js +84 -0
- package/dist/stock/web/ScanBox.d.ts +8 -0
- package/dist/stock/web/ScanBox.js +90 -0
- package/dist/stock/web/index.d.ts +13 -0
- package/dist/stock/web/index.js +20 -0
- package/dist/stock/web/messages.d.ts +2 -0
- package/dist/stock/web/messages.js +77 -0
- package/dist/stock_account/.kosh/bindings.d.ts +22 -0
- package/dist/stock_account/.kosh/bindings.js +15 -0
- package/dist/stock_account/.kosh/schema.d.ts +2068 -0
- package/dist/stock_account/index.d.ts +3 -0
- package/dist/stock_account/index.js +140 -0
- package/dist/stock_account/module.d.ts +11523 -0
- package/dist/stock_account/module.js +339 -0
- package/dist/storage/attachments.d.ts +5 -0
- package/dist/storage/attachments.js +12 -1
- package/dist/storage/models.js +3 -0
- package/dist/storage/routes.js +13 -2
- package/dist/todo/.kosh/bindings.d.ts +22 -0
- package/dist/todo/.kosh/bindings.js +15 -0
- package/dist/todo/.kosh/schema.d.ts +732 -0
- package/dist/todo/demo.d.ts +6 -0
- package/dist/todo/demo.js +35 -0
- package/dist/todo/index.d.ts +3 -0
- package/dist/todo/index.js +187 -0
- package/dist/todo/module.d.ts +1180 -0
- package/dist/todo/module.js +130 -0
- package/dist/trade_finance/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance/.kosh/bindings.js +15 -0
- package/dist/trade_finance/.kosh/schema.d.ts +884 -0
- package/dist/trade_finance/demo.d.ts +6 -0
- package/dist/trade_finance/demo.js +63 -0
- package/dist/trade_finance/index.d.ts +3 -0
- package/dist/trade_finance/index.js +415 -0
- package/dist/trade_finance/module.d.ts +3779 -0
- package/dist/trade_finance/module.js +507 -0
- package/dist/trade_finance_account/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_account/.kosh/bindings.js +15 -0
- package/dist/trade_finance_account/.kosh/schema.d.ts +1830 -0
- package/dist/trade_finance_account/index.d.ts +3 -0
- package/dist/trade_finance_account/index.js +93 -0
- package/dist/trade_finance_account/module.d.ts +8074 -0
- package/dist/trade_finance_account/module.js +245 -0
- package/dist/trade_finance_purchase/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_purchase/.kosh/bindings.js +15 -0
- package/dist/trade_finance_purchase/.kosh/schema.d.ts +1246 -0
- package/dist/trade_finance_purchase/index.d.ts +8 -0
- package/dist/trade_finance_purchase/index.js +50 -0
- package/dist/trade_finance_purchase/module.d.ts +4257 -0
- package/dist/trade_finance_purchase/module.js +44 -0
- package/dist/trade_finance_sale/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_sale/.kosh/bindings.js +15 -0
- package/dist/trade_finance_sale/.kosh/schema.d.ts +1072 -0
- package/dist/trade_finance_sale/index.d.ts +8 -0
- package/dist/trade_finance_sale/index.js +50 -0
- package/dist/trade_finance_sale/module.d.ts +4281 -0
- package/dist/trade_finance_sale/module.js +44 -0
- package/dist/ui/api.d.ts +2 -1
- package/dist/ui/api.js +2 -1
- package/dist/ui/app.js +26 -13
- package/dist/ui/components/AppIcon.js +30 -1
- package/dist/ui/components/primitives.d.ts +2 -0
- package/dist/ui/components/primitives.js +7 -1
- package/dist/ui/i18n/messages.d.ts +27 -0
- package/dist/ui/i18n/messages.js +46 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/offline/queue.d.ts +5 -1
- package/dist/ui/offline/queue.js +2 -0
- package/dist/ui/registry.d.ts +5 -0
- package/dist/ui/shell/Home.js +55 -4
- package/dist/ui/views/FormView.js +102 -19
- package/dist/ui/views/KanbanView.d.ts +2 -0
- package/dist/ui/views/KanbanView.js +40 -3
- package/dist/ui/views/ListView.js +110 -11
- package/dist/ui/views/StatsRow.d.ts +14 -0
- package/dist/ui/views/StatsRow.js +88 -0
- package/dist/ui/views/widgets.d.ts +2 -1
- package/dist/ui/views/widgets.js +200 -1
- package/dist/views/builders.d.ts +19 -2
- package/dist/views/builders.js +21 -1
- package/dist/views/condition.d.ts +6 -0
- package/dist/views/condition.js +20 -0
- package/dist/views/documents.d.ts +8 -0
- package/dist/views/extend.d.ts +2 -2
- package/dist/views/extend.js +11 -1
- package/dist/views/index.d.ts +2 -2
- package/dist/views/index.js +2 -2
- package/dist/views/types.d.ts +52 -5
- package/dist/views/validate.js +39 -1
- package/dist/website/.kosh/bindings.d.ts +22 -0
- package/dist/website/.kosh/bindings.js +15 -0
- package/dist/website/.kosh/schema.d.ts +881 -0
- package/dist/website/demo.d.ts +6 -0
- package/dist/website/demo.js +115 -0
- package/dist/website/index.d.ts +9 -0
- package/dist/website/index.js +251 -0
- package/dist/website/module.d.ts +2987 -0
- package/dist/website/module.js +282 -0
- package/dist/website/render.d.ts +45 -0
- package/dist/website/render.js +102 -0
- package/dist/website/routes.d.ts +12 -0
- package/dist/website/routes.js +246 -0
- package/dist/website/web/PagePreview.d.ts +7 -0
- package/dist/website/web/PagePreview.js +33 -0
- package/dist/website/web/index.d.ts +11 -0
- package/dist/website/web/index.js +16 -0
- package/dist/website/web/messages.d.ts +2 -0
- package/dist/website/web/messages.js +15 -0
- package/dist/website_crm/.kosh/bindings.d.ts +22 -0
- package/dist/website_crm/.kosh/bindings.js +15 -0
- package/dist/website_crm/.kosh/schema.d.ts +967 -0
- package/dist/website_crm/index.d.ts +3 -0
- package/dist/website_crm/index.js +39 -0
- package/dist/website_crm/module.d.ts +2956 -0
- package/dist/website_crm/module.js +44 -0
- package/dist/website_sale/.kosh/bindings.d.ts +22 -0
- package/dist/website_sale/.kosh/bindings.js +15 -0
- package/dist/website_sale/.kosh/schema.d.ts +2284 -0
- package/dist/website_sale/couriers.d.ts +50 -0
- package/dist/website_sale/couriers.js +59 -0
- package/dist/website_sale/demo.d.ts +7 -0
- package/dist/website_sale/demo.js +90 -0
- package/dist/website_sale/growth.d.ts +31 -0
- package/dist/website_sale/growth.js +163 -0
- package/dist/website_sale/index.d.ts +49 -0
- package/dist/website_sale/index.js +538 -0
- package/dist/website_sale/module.d.ts +22027 -0
- package/dist/website_sale/module.js +397 -0
- package/dist/website_sale/shipments.d.ts +16 -0
- package/dist/website_sale/shipments.js +113 -0
- package/dist/website_sale/shop.d.ts +81 -0
- package/dist/website_sale/shop.js +649 -0
- package/dist/website_sale_stock/.kosh/bindings.d.ts +22 -0
- package/dist/website_sale_stock/.kosh/bindings.js +15 -0
- package/dist/website_sale_stock/.kosh/schema.d.ts +2681 -0
- package/dist/website_sale_stock/index.d.ts +9 -0
- package/dist/website_sale_stock/index.js +20 -0
- package/dist/website_sale_stock/module.d.ts +24998 -0
- package/dist/website_sale_stock/module.js +28 -0
- package/package.json +200 -2
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counter's routes (RFC 0021): what the register needs in few calls — the shop's catalogue, the cashier's open
|
|
3
|
+
* session, a sale posted whole (lines and payments, accepted once by its client id, paid at once), a session
|
|
4
|
+
* closed with its count.
|
|
5
|
+
*
|
|
6
|
+
* - `GET /api/v1/counter/shops` the shops the cashier may sell in, with their payment methods
|
|
7
|
+
* - `GET /api/v1/counter/catalogue?shop=` products for the register: price, taxes, barcode, picture
|
|
8
|
+
* - `GET /api/v1/counter/session?shop=` the cashier's open session at the shop, or null
|
|
9
|
+
* - `POST /api/v1/counter/sessions` `{ shop, openingCash }`: a session opened
|
|
10
|
+
* - `POST /api/v1/counter/orders` a sale: `{ session, clientId, partner?, lines, payments, note? }`
|
|
11
|
+
* - `POST /api/v1/counter/sessions/:id/close` `{ closingCash, differenceReason? }`: the count, then closed
|
|
12
|
+
*/
|
|
13
|
+
import { mailerFromEnv } from '../mail/index.js';
|
|
14
|
+
import { messengerFromEnv } from '../notify/index.js';
|
|
15
|
+
import { documentFields, renderDocument } from '../reporting/index.js';
|
|
16
|
+
import { HttpProblem, publicApiModels } from '../server/index.js';
|
|
17
|
+
import { orm } from '../server/orm.js';
|
|
18
|
+
const idOf = (value, what) => {
|
|
19
|
+
const id = Number(value);
|
|
20
|
+
if (!Number.isSafeInteger(id) || id <= 0)
|
|
21
|
+
throw new HttpProblem(400, 'invalid-id', 'Invalid id', `${what} is a positive integer.`);
|
|
22
|
+
return id;
|
|
23
|
+
};
|
|
24
|
+
const text = (value) => (typeof value === 'string' ? value.trim() : '');
|
|
25
|
+
export function registerCounterRoutes(context, options = {}) {
|
|
26
|
+
const { app } = context;
|
|
27
|
+
const mailer = 'mailer' in options ? options.mailer : mailerFromEnv();
|
|
28
|
+
const messenger = 'messenger' in options ? options.messenger : messengerFromEnv();
|
|
29
|
+
const models = publicApiModels(context.registry);
|
|
30
|
+
const fields = documentFields(models);
|
|
31
|
+
// `POST /api/v1/counter/orders/:id/receipt` `{ email?, phone? }`: the receipt sent — the document by email,
|
|
32
|
+
// a summary by text; what was sent is said back.
|
|
33
|
+
app.post('/api/v1/counter/orders/:id/receipt', async (request) => {
|
|
34
|
+
const id = idOf(request.params.id, 'id');
|
|
35
|
+
const body = (request.body ?? {});
|
|
36
|
+
const email = text(body.email);
|
|
37
|
+
const phone = text(body.phone);
|
|
38
|
+
if (email === '' && phone === '')
|
|
39
|
+
throw new HttpProblem(422, 'no-address', 'Nowhere to send', 'Give an email address or a phone number.');
|
|
40
|
+
const model = models.get('pos.order');
|
|
41
|
+
if (model === undefined)
|
|
42
|
+
throw new HttpProblem(404, 'unknown-model', 'Unknown model', 'pos.order');
|
|
43
|
+
return request.env(async (raw) => {
|
|
44
|
+
const env = raw;
|
|
45
|
+
const order = await orderOut(env, id);
|
|
46
|
+
const sent = [];
|
|
47
|
+
if (email !== '') {
|
|
48
|
+
if (mailer === undefined)
|
|
49
|
+
throw new HttpProblem(409, 'no-mailer', 'No email set up', 'Set SMTP_URL to send receipts by email.');
|
|
50
|
+
const [template] = (await (await env.model('report.template').search({ where: { key: 'pos.receipt' }, limit: 1 })).load([])).read(['id', 'name', 'layout', 'scope']);
|
|
51
|
+
if (template === undefined)
|
|
52
|
+
throw new HttpProblem(409, 'no-template', 'No receipt template', 'Run the apps\u2019 defaults.');
|
|
53
|
+
const rendered = await renderDocument(env, model, template, fields, { ids: [id] }, 'html');
|
|
54
|
+
await mailer.send({
|
|
55
|
+
to: email,
|
|
56
|
+
subject: `Receipt ${String(order['name'])}`,
|
|
57
|
+
text: receiptText(order),
|
|
58
|
+
html: rendered.content.toString('utf8'),
|
|
59
|
+
});
|
|
60
|
+
sent.push('email');
|
|
61
|
+
}
|
|
62
|
+
if (phone !== '') {
|
|
63
|
+
if (messenger === undefined)
|
|
64
|
+
throw new HttpProblem(409, 'no-messenger', 'No text messaging set up', 'Set up Twilio to send receipts by text.');
|
|
65
|
+
await messenger.send({ channel: 'sms', to: phone, text: receiptText(order) });
|
|
66
|
+
sent.push('sms');
|
|
67
|
+
}
|
|
68
|
+
return { sent };
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
app.get('/api/v1/counter/shops', async (request) => request.env(async (raw) => {
|
|
72
|
+
const env = raw;
|
|
73
|
+
const shops = await (await env.model('pos.shop').search({})).load({ methods: true });
|
|
74
|
+
return {
|
|
75
|
+
shops: shops.map((s) => ({
|
|
76
|
+
id: s.id,
|
|
77
|
+
name: s.name,
|
|
78
|
+
pricesIncludeTax: s.pricesIncludeTax,
|
|
79
|
+
receiptFooter: s.receiptFooter,
|
|
80
|
+
methods: [...s.methods].map((m) => ({ id: m.id, name: m.name, kind: m.kind })),
|
|
81
|
+
})),
|
|
82
|
+
};
|
|
83
|
+
}));
|
|
84
|
+
app.get('/api/v1/counter/catalogue', async (request) => request.env(async (raw) => {
|
|
85
|
+
const env = raw;
|
|
86
|
+
const products = await (await env
|
|
87
|
+
.model('product.product')
|
|
88
|
+
.search({ where: { saleOk: true }, order: ['name asc'], limit: 2000 })).load({ taxes: true, category: true, uom: true, image: true });
|
|
89
|
+
return {
|
|
90
|
+
products: products.map((p) => ({
|
|
91
|
+
id: p.id,
|
|
92
|
+
name: p.name,
|
|
93
|
+
code: p.code,
|
|
94
|
+
barcode: p.barcode,
|
|
95
|
+
price: p.listPrice.toString(),
|
|
96
|
+
category: p.category?.name ?? null,
|
|
97
|
+
uom: p.uom?.name ?? null,
|
|
98
|
+
image: p.image?.id ?? null,
|
|
99
|
+
taxes: [...p.taxes].map((t) => ({
|
|
100
|
+
id: t.id,
|
|
101
|
+
name: t.name,
|
|
102
|
+
amountType: t.amountType,
|
|
103
|
+
rate: t.rate.toString(),
|
|
104
|
+
fixedAmount: t.fixedAmount.toString(),
|
|
105
|
+
})),
|
|
106
|
+
})),
|
|
107
|
+
};
|
|
108
|
+
}));
|
|
109
|
+
app.get('/api/v1/counter/session', async (request) => request.env(async (raw) => {
|
|
110
|
+
const env = raw;
|
|
111
|
+
const shop = idOf(request.query.shop, 'shop');
|
|
112
|
+
const [session] = await env.model('pos.session').search({
|
|
113
|
+
where: { shop: shop, cashier: env.userId, state: 'open' },
|
|
114
|
+
limit: 1,
|
|
115
|
+
});
|
|
116
|
+
return { session: session === undefined ? null : sessionOut(session) };
|
|
117
|
+
}));
|
|
118
|
+
app.post('/api/v1/counter/sessions', async (request, reply) => {
|
|
119
|
+
const body = (request.body ?? {});
|
|
120
|
+
const shop = idOf(body.shop, 'shop');
|
|
121
|
+
const openingCash = text(body.openingCash) || '0';
|
|
122
|
+
const made = await request.env(async (raw) => {
|
|
123
|
+
const env = raw;
|
|
124
|
+
const created = await env
|
|
125
|
+
.model('pos.session')
|
|
126
|
+
.create([
|
|
127
|
+
{ shop: shop, openingCash: orm.toDecimal(openingCash) },
|
|
128
|
+
]);
|
|
129
|
+
await env.flush();
|
|
130
|
+
const [s] = await env.model('pos.session').browse(created.ids);
|
|
131
|
+
return s === undefined ? null : sessionOut(s);
|
|
132
|
+
}, { write: true });
|
|
133
|
+
return reply.status(201).send({ session: made });
|
|
134
|
+
});
|
|
135
|
+
app.post('/api/v1/counter/orders', async (request, reply) => {
|
|
136
|
+
const input = request.body;
|
|
137
|
+
if (input === null ||
|
|
138
|
+
typeof input !== 'object' ||
|
|
139
|
+
!Array.isArray(input.lines) ||
|
|
140
|
+
!Array.isArray(input.payments))
|
|
141
|
+
throw new HttpProblem(400, 'invalid-body', 'Invalid sale', 'Send the session, the lines and the payments.');
|
|
142
|
+
if (input.lines.length === 0)
|
|
143
|
+
throw new HttpProblem(422, 'empty-sale', 'Nothing to sell', 'Add at least one line.');
|
|
144
|
+
const session = idOf(input.session, 'session');
|
|
145
|
+
const clientId = text(input.clientId);
|
|
146
|
+
const outcome = await request.env(async (raw) => {
|
|
147
|
+
const env = raw;
|
|
148
|
+
// The same sale sent twice (a device back online): the first one, unchanged.
|
|
149
|
+
if (clientId !== '') {
|
|
150
|
+
const [known] = await env.model('pos.order').search({
|
|
151
|
+
where: { session: session, clientId },
|
|
152
|
+
limit: 1,
|
|
153
|
+
});
|
|
154
|
+
if (known !== undefined)
|
|
155
|
+
return { order: await orderOut(env, known.id), repeated: true };
|
|
156
|
+
}
|
|
157
|
+
const [current] = await env
|
|
158
|
+
.model('pos.session')
|
|
159
|
+
.browse([session])
|
|
160
|
+
.load({ shop: true });
|
|
161
|
+
if (current === undefined)
|
|
162
|
+
throw new HttpProblem(404, 'missing-record', 'Not found', 'No such session.');
|
|
163
|
+
const products = await env
|
|
164
|
+
.model('product.product')
|
|
165
|
+
.browse(input.lines.map((l) => idOf(l.product, 'product')))
|
|
166
|
+
.load({ taxes: true });
|
|
167
|
+
const byId = new Map(products.map((p) => [p.id, p]));
|
|
168
|
+
const created = await env.model('pos.order').create([
|
|
169
|
+
{
|
|
170
|
+
session: session,
|
|
171
|
+
pricesIncludeTax: current.shop.pricesIncludeTax === true,
|
|
172
|
+
...(clientId === '' ? {} : { clientId }),
|
|
173
|
+
...(input.partner === undefined || input.partner === null
|
|
174
|
+
? {}
|
|
175
|
+
: { partner: idOf(input.partner, 'partner') }),
|
|
176
|
+
...(text(input.note) === '' ? {} : { note: text(input.note) }),
|
|
177
|
+
lines: {
|
|
178
|
+
create: input.lines.map((l) => {
|
|
179
|
+
const product = byId.get(idOf(l.product, 'product'));
|
|
180
|
+
if (product === undefined)
|
|
181
|
+
throw new HttpProblem(422, 'unknown-product', 'Unknown product', `No product ${String(l.product)}.`);
|
|
182
|
+
return {
|
|
183
|
+
product: product.id,
|
|
184
|
+
name: product.name,
|
|
185
|
+
quantity: orm.toDecimal(text(l.quantity) || '1'),
|
|
186
|
+
priceUnit: orm.toDecimal(text(l.priceUnit) || product.listPrice.toString()),
|
|
187
|
+
discount: orm.toDecimal(text(l.discount) || '0'),
|
|
188
|
+
taxes: { set: [...product.taxes].map((t) => t.id) },
|
|
189
|
+
};
|
|
190
|
+
}),
|
|
191
|
+
},
|
|
192
|
+
payments: {
|
|
193
|
+
create: input.payments.map((p) => ({
|
|
194
|
+
method: idOf(p.method, 'method'),
|
|
195
|
+
amount: orm.toDecimal(text(p.amount) || '0'),
|
|
196
|
+
...(text(p.reference) === '' ? {} : { reference: text(p.reference) }),
|
|
197
|
+
})),
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
await env.flush();
|
|
202
|
+
await env.model('pos.order').browse(created.ids).pay();
|
|
203
|
+
await env.flush();
|
|
204
|
+
return { order: await orderOut(env, created.ids[0]), repeated: false };
|
|
205
|
+
}, { write: true });
|
|
206
|
+
return reply.status(outcome.repeated ? 200 : 201).send(outcome);
|
|
207
|
+
});
|
|
208
|
+
// `POST /api/v1/counter/orders/:id/refund` `{ session, reason, lines?: [{ product, quantity }], method? }`:
|
|
209
|
+
// the sale (or part of it) taken back — a negative sale in the open session, paid back by the method given
|
|
210
|
+
// (the shop's cash by default), linked to the original. Cashiers refund the sales of their own open session;
|
|
211
|
+
// `pos.refund_any` (managers) refunds any sale.
|
|
212
|
+
app.post('/api/v1/counter/orders/:id/refund', async (request, reply) => {
|
|
213
|
+
const id = idOf(request.params.id, 'id');
|
|
214
|
+
const body = (request.body ?? {});
|
|
215
|
+
const session = idOf(body.session, 'session');
|
|
216
|
+
const reason = text(body.reason);
|
|
217
|
+
if (reason === '')
|
|
218
|
+
throw new HttpProblem(422, 'reason-missing', 'Say why', 'A refund needs its reason.');
|
|
219
|
+
const made = await request.env(async (raw) => {
|
|
220
|
+
const env = raw;
|
|
221
|
+
const [original] = await env
|
|
222
|
+
.model('pos.order')
|
|
223
|
+
.browse([id])
|
|
224
|
+
.load({ lines: { taxes: true }, payments: { method: true }, session: true });
|
|
225
|
+
if (original === undefined || original.state !== 'paid')
|
|
226
|
+
throw new HttpProblem(404, 'missing-record', 'Not found', 'No paid sale with this id.');
|
|
227
|
+
const [current] = await env
|
|
228
|
+
.model('pos.session')
|
|
229
|
+
.browse([session])
|
|
230
|
+
.load({ shop: { methods: true } });
|
|
231
|
+
if (current === undefined || current.state !== 'open')
|
|
232
|
+
throw new HttpProblem(409, 'session-closed', 'Session closed', 'Refund in an open session.');
|
|
233
|
+
const mine = original.session.id === current.id;
|
|
234
|
+
let may = mine;
|
|
235
|
+
if (!may) {
|
|
236
|
+
try {
|
|
237
|
+
may = env.access.has('pos.refund_any');
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
may = false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!may)
|
|
244
|
+
throw new HttpProblem(403, 'refund-forbidden', 'Not yours to refund', 'Cashiers refund the sales of their own open session; a manager refunds any sale.');
|
|
245
|
+
const wanted = new Map();
|
|
246
|
+
for (const l of body.lines ?? [])
|
|
247
|
+
wanted.set(idOf(l.product, 'product'), text(l.quantity) || '0');
|
|
248
|
+
const lines = [...original.lines]
|
|
249
|
+
.map((l) => {
|
|
250
|
+
const qty = wanted.size === 0 ? l.quantity : orm.toDecimal(wanted.get(l.product.id) ?? '0');
|
|
251
|
+
return { line: l, qty };
|
|
252
|
+
})
|
|
253
|
+
.filter(({ qty }) => qty.gt(0));
|
|
254
|
+
if (lines.length === 0)
|
|
255
|
+
throw new HttpProblem(422, 'nothing-to-refund', 'Nothing to refund', 'Give the quantities taken back.');
|
|
256
|
+
for (const { line, qty } of lines)
|
|
257
|
+
if (qty.gt(line.quantity))
|
|
258
|
+
throw new HttpProblem(422, 'too-much', 'More than sold', `${line.name}: ${qty.toString()} of ${line.quantity.toString()} sold.`);
|
|
259
|
+
const methodId = body.method === undefined
|
|
260
|
+
? ([...current.shop.methods].find((mm) => mm.kind === 'cash') ??
|
|
261
|
+
[...current.shop.methods][0])?.id
|
|
262
|
+
: idOf(body.method, 'method');
|
|
263
|
+
if (methodId === undefined)
|
|
264
|
+
throw new HttpProblem(409, 'no-method', 'No payment method', 'The shop has no payment method.');
|
|
265
|
+
const created = await env.model('pos.order').create([
|
|
266
|
+
{
|
|
267
|
+
session: session,
|
|
268
|
+
pricesIncludeTax: original.pricesIncludeTax,
|
|
269
|
+
partner: original.partner?.id ?? null,
|
|
270
|
+
refundOf: original.id,
|
|
271
|
+
refundReason: reason,
|
|
272
|
+
note: `Refund of ${original.name}: ${reason}`,
|
|
273
|
+
lines: {
|
|
274
|
+
create: lines.map(({ line, qty }) => ({
|
|
275
|
+
product: line.product.id,
|
|
276
|
+
name: line.name,
|
|
277
|
+
quantity: qty.neg(),
|
|
278
|
+
priceUnit: line.priceUnit,
|
|
279
|
+
discount: line.discount,
|
|
280
|
+
taxes: { set: [...line.taxes].map((t) => t.id) },
|
|
281
|
+
})),
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
]);
|
|
285
|
+
await env.flush();
|
|
286
|
+
const [draft] = await env.model('pos.order').browse(created.ids);
|
|
287
|
+
const total = draft?.total ?? orm.toDecimal(0);
|
|
288
|
+
await env
|
|
289
|
+
.model('pos.order.payment')
|
|
290
|
+
.create([{ order: created.ids[0], method: methodId, amount: total }]);
|
|
291
|
+
await env.flush();
|
|
292
|
+
await env.model('pos.order').browse(created.ids).pay();
|
|
293
|
+
await env.flush();
|
|
294
|
+
return orderOut(env, created.ids[0]);
|
|
295
|
+
}, { write: true });
|
|
296
|
+
return reply.status(201).send({ order: made });
|
|
297
|
+
});
|
|
298
|
+
app.post('/api/v1/counter/sessions/:id/close', async (request) => {
|
|
299
|
+
const id = idOf(request.params.id, 'id');
|
|
300
|
+
const body = (request.body ?? {});
|
|
301
|
+
const closingCash = text(body.closingCash);
|
|
302
|
+
if (closingCash === '')
|
|
303
|
+
throw new HttpProblem(422, 'count-missing', 'Count the cash', 'Send the cash counted in the drawer.');
|
|
304
|
+
return request.env(async (raw) => {
|
|
305
|
+
const env = raw;
|
|
306
|
+
const set = env.model('pos.session').browse([id]);
|
|
307
|
+
await set.write({
|
|
308
|
+
closingCash: orm.toDecimal(closingCash),
|
|
309
|
+
...(text(body.differenceReason) === ''
|
|
310
|
+
? {}
|
|
311
|
+
: { differenceReason: text(body.differenceReason) }),
|
|
312
|
+
});
|
|
313
|
+
await env.flush();
|
|
314
|
+
await set.close();
|
|
315
|
+
await env.flush();
|
|
316
|
+
const [s] = await env.model('pos.session').browse([id]);
|
|
317
|
+
return { session: s === undefined ? null : sessionOut(s) };
|
|
318
|
+
}, { write: true });
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
const textOrNull = (v) => v === null || v === undefined ? null : String(v);
|
|
322
|
+
/** The receipt in a few lines of text (a text message, an email's plain part). */
|
|
323
|
+
function receiptText(order) {
|
|
324
|
+
const lines = order['lines'].map((l) => `${l.name} × ${l.quantity}: ${l.subtotal}`);
|
|
325
|
+
return [
|
|
326
|
+
`Receipt ${String(order['name'])}`,
|
|
327
|
+
...lines,
|
|
328
|
+
`Total ${String(order['total'])} (taxes ${String(order['amountTax'])})`,
|
|
329
|
+
`Paid ${String(order['amountPaid'])}, change ${String(order['change'])}`,
|
|
330
|
+
].join(NEWLINE);
|
|
331
|
+
}
|
|
332
|
+
const NEWLINE = String.fromCharCode(10);
|
|
333
|
+
function sessionOut(raw) {
|
|
334
|
+
const s = raw;
|
|
335
|
+
return {
|
|
336
|
+
id: s['id'],
|
|
337
|
+
name: String(s['name'] ?? ''),
|
|
338
|
+
state: String(s['state'] ?? ''),
|
|
339
|
+
openingCash: String(s['openingCash'] ?? '0'),
|
|
340
|
+
expectedCash: textOrNull(s['expectedCash']),
|
|
341
|
+
closingCash: textOrNull(s['closingCash']),
|
|
342
|
+
closingDifference: textOrNull(s['closingDifference']),
|
|
343
|
+
orderCount: typeof s['orderCount'] === 'number' ? s['orderCount'] : 0,
|
|
344
|
+
total: String(s['total'] ?? '0'),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
async function orderOut(env, id) {
|
|
348
|
+
const [o] = await env.model('pos.order').browse([id]).load({ lines: true, payments: true });
|
|
349
|
+
if (o === undefined)
|
|
350
|
+
throw new HttpProblem(404, 'missing-record', 'Not found', 'No such sale.');
|
|
351
|
+
return {
|
|
352
|
+
id: o.id,
|
|
353
|
+
name: o.name,
|
|
354
|
+
state: o.state,
|
|
355
|
+
total: o.total?.toString() ?? '0',
|
|
356
|
+
amountTax: o.amountTax?.toString() ?? '0',
|
|
357
|
+
amountPaid: o.amountPaid?.toString() ?? '0',
|
|
358
|
+
change: o.change?.toString() ?? '0',
|
|
359
|
+
lines: [...o.lines].map((l) => ({
|
|
360
|
+
product: l.product.id,
|
|
361
|
+
name: l.name,
|
|
362
|
+
quantity: l.quantity.toString(),
|
|
363
|
+
priceUnit: l.priceUnit.toString(),
|
|
364
|
+
subtotal: l.subtotal?.toString() ?? '0',
|
|
365
|
+
})),
|
|
366
|
+
payments: [...o.payments].map((p) => ({ method: p.method.id, amount: p.amount.toString() })),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The register (RFC 0021, Counter): a page for a tablet or a phone. The shop's products as tiles with pictures,
|
|
3
|
+
* search and a barcode box (a scanner types into it; Enter adds the match), the cart, the payment screen with
|
|
4
|
+
* several methods and the change, the receipt to print, the session opened with its cash and closed with its
|
|
5
|
+
* count. Totals here are the register's own arithmetic for the cashier's eye; the server computes the sale.
|
|
6
|
+
*/
|
|
7
|
+
import { type PageProps } from '../../ui/index.js';
|
|
8
|
+
import { type ReactElement } from 'react';
|
|
9
|
+
export declare function Register({ api }: PageProps): ReactElement;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The register (RFC 0021, Counter): a page for a tablet or a phone. The shop's products as tiles with pictures,
|
|
4
|
+
* search and a barcode box (a scanner types into it; Enter adds the match), the cart, the payment screen with
|
|
5
|
+
* several methods and the change, the receipt to print, the session opened with its cash and closed with its
|
|
6
|
+
* count. Totals here are the register's own arithmetic for the cashier's eye; the server computes the sale.
|
|
7
|
+
*/
|
|
8
|
+
import { Alert, Button, Dialog, Input, Label, NetworkError, Spinner, useKosh, } from '../../ui/index.js';
|
|
9
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
10
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
11
|
+
import { useTranslation } from 'react-i18next';
|
|
12
|
+
const money = (n) => n.toFixed(2);
|
|
13
|
+
/** The line's amount as the customer pays it (taxes added when the shop's prices do not include them). */
|
|
14
|
+
function lineAmount(line, pricesIncludeTax) {
|
|
15
|
+
const gross = Number(line.product.price) * line.quantity;
|
|
16
|
+
if (pricesIncludeTax)
|
|
17
|
+
return gross;
|
|
18
|
+
const percent = line.product.taxes
|
|
19
|
+
.filter((t) => t.amountType === 'percent')
|
|
20
|
+
.reduce((sum, t) => sum + Number(t.rate), 0);
|
|
21
|
+
const fixed = line.product.taxes
|
|
22
|
+
.filter((t) => t.amountType === 'fixed')
|
|
23
|
+
.reduce((sum, t) => sum + Number(t.fixedAmount) * line.quantity, 0);
|
|
24
|
+
return gross + (gross * percent) / 100 + fixed;
|
|
25
|
+
}
|
|
26
|
+
export function Register({ api }) {
|
|
27
|
+
const { t } = useTranslation('pos');
|
|
28
|
+
const { format, offline } = useKosh();
|
|
29
|
+
const queryClient = useQueryClient();
|
|
30
|
+
const [queued, setQueued] = useState(0);
|
|
31
|
+
const shops = useQuery({
|
|
32
|
+
queryKey: ['counter', 'shops'],
|
|
33
|
+
queryFn: () => api.call('GET', '/api/v1/counter/shops').then((r) => r.shops),
|
|
34
|
+
});
|
|
35
|
+
const [shopId, setShopId] = useState(null);
|
|
36
|
+
const shop = (shops.data ?? []).find((s) => s.id === shopId) ??
|
|
37
|
+
(shops.data?.length === 1 ? shops.data[0] : undefined);
|
|
38
|
+
const session = useQuery({
|
|
39
|
+
queryKey: ['counter', 'session', shop?.id],
|
|
40
|
+
queryFn: () => api
|
|
41
|
+
.call('GET', `/api/v1/counter/session?shop=${shop?.id ?? 0}`)
|
|
42
|
+
.then((r) => r.session),
|
|
43
|
+
enabled: shop !== undefined,
|
|
44
|
+
});
|
|
45
|
+
const catalogue = useQuery({
|
|
46
|
+
queryKey: ['counter', 'catalogue', shop?.id],
|
|
47
|
+
queryFn: () => api
|
|
48
|
+
.call('GET', `/api/v1/counter/catalogue?shop=${shop?.id ?? 0}`)
|
|
49
|
+
.then((r) => r.products),
|
|
50
|
+
enabled: shop !== undefined,
|
|
51
|
+
});
|
|
52
|
+
const [query, setQuery] = useState('');
|
|
53
|
+
const [cart, setCart] = useState([]);
|
|
54
|
+
const [paying, setPaying] = useState(false);
|
|
55
|
+
const [closing, setClosing] = useState(false);
|
|
56
|
+
const [opening, setOpening] = useState('0');
|
|
57
|
+
const [closingCash, setClosingCash] = useState('');
|
|
58
|
+
const [reason, setReason] = useState('');
|
|
59
|
+
const [payments, setPayments] = useState({});
|
|
60
|
+
const [lastSale, setLastSale] = useState(null);
|
|
61
|
+
const [error, setError] = useState(null);
|
|
62
|
+
const clientId = useRef(crypto.randomUUID());
|
|
63
|
+
const search = useRef(null);
|
|
64
|
+
const products = catalogue.data ?? [];
|
|
65
|
+
const shown = useMemo(() => {
|
|
66
|
+
const q = query.trim().toLowerCase();
|
|
67
|
+
if (q === '')
|
|
68
|
+
return products.slice(0, 200);
|
|
69
|
+
return products
|
|
70
|
+
.filter((p) => p.name.toLowerCase().includes(q) ||
|
|
71
|
+
(p.code ?? '').toLowerCase().includes(q) ||
|
|
72
|
+
(p.barcode ?? '').toLowerCase() === q)
|
|
73
|
+
.slice(0, 200);
|
|
74
|
+
}, [products, query]);
|
|
75
|
+
const total = cart.reduce((sum, l) => sum + lineAmount(l, shop?.pricesIncludeTax ?? true), 0);
|
|
76
|
+
const paid = Object.values(payments).reduce((sum, v) => sum + (Number(v) || 0), 0);
|
|
77
|
+
const change = Math.max(0, paid - total);
|
|
78
|
+
const add = (p) => {
|
|
79
|
+
setCart((lines) => {
|
|
80
|
+
const i = lines.findIndex((l) => l.product.id === p.id);
|
|
81
|
+
if (i < 0)
|
|
82
|
+
return [...lines, { product: p, quantity: 1 }];
|
|
83
|
+
const next = [...lines];
|
|
84
|
+
next[i] = { product: p, quantity: (next[i]?.quantity ?? 0) + 1 };
|
|
85
|
+
return next;
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const setQuantity = (id, quantity) => setCart((lines) => lines
|
|
89
|
+
.map((l) => (l.product.id === id ? { product: l.product, quantity } : l))
|
|
90
|
+
.filter((l) => l.quantity > 0 || l.quantity === 0));
|
|
91
|
+
const removeLine = (id) => setCart((lines) => lines.filter((l) => l.product.id !== id));
|
|
92
|
+
// A scanner types the barcode and sends Enter: the exact match goes to the cart, the box clears.
|
|
93
|
+
const onSearchKey = (key) => {
|
|
94
|
+
if (key !== 'Enter')
|
|
95
|
+
return;
|
|
96
|
+
const q = query.trim().toLowerCase();
|
|
97
|
+
const hit = products.find((p) => (p.barcode ?? '').toLowerCase() === q) ??
|
|
98
|
+
(shown.length === 1 ? shown[0] : undefined);
|
|
99
|
+
if (hit !== undefined) {
|
|
100
|
+
add(hit);
|
|
101
|
+
setQuery('');
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
search.current?.focus();
|
|
106
|
+
}, []);
|
|
107
|
+
const open = useMutation({
|
|
108
|
+
mutationFn: () => api.call('POST', '/api/v1/counter/sessions', {
|
|
109
|
+
shop: shop?.id,
|
|
110
|
+
openingCash: opening,
|
|
111
|
+
}),
|
|
112
|
+
onSuccess: () => void queryClient.invalidateQueries({ queryKey: ['counter', 'session'] }),
|
|
113
|
+
onError: (e) => setError(e instanceof Error ? e.message : String(e)),
|
|
114
|
+
});
|
|
115
|
+
const sell = useMutation({
|
|
116
|
+
mutationFn: async () => {
|
|
117
|
+
const body = {
|
|
118
|
+
session: session.data?.id,
|
|
119
|
+
clientId: clientId.current,
|
|
120
|
+
lines: cart.map((l) => ({ product: l.product.id, quantity: String(l.quantity) })),
|
|
121
|
+
payments: Object.entries(payments)
|
|
122
|
+
.filter(([, amount]) => Number(amount) > 0)
|
|
123
|
+
.map(([method, amount]) => ({ method: Number(method), amount })),
|
|
124
|
+
};
|
|
125
|
+
try {
|
|
126
|
+
const r = await api.call('POST', '/api/v1/counter/orders', body);
|
|
127
|
+
return { order: r.order, queued: false };
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
// Offline: the sale waits on the device, sent in order when the network is back, accepted once by
|
|
131
|
+
// its device reference (offline/queue.ts).
|
|
132
|
+
if (!(error instanceof NetworkError) || offline === undefined)
|
|
133
|
+
throw error;
|
|
134
|
+
await offline.add({
|
|
135
|
+
kind: 'request',
|
|
136
|
+
method: 'POST',
|
|
137
|
+
path: '/api/v1/counter/orders',
|
|
138
|
+
model: 'pos.order',
|
|
139
|
+
modelLabel: t('cart'),
|
|
140
|
+
recordId: null,
|
|
141
|
+
title: `${t('cart')} ${format.decimal(money(total))}`,
|
|
142
|
+
values: body,
|
|
143
|
+
etag: null,
|
|
144
|
+
});
|
|
145
|
+
return { order: null, queued: true };
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
onSuccess: (r) => {
|
|
149
|
+
if (r.queued)
|
|
150
|
+
setQueued((n) => n + 1);
|
|
151
|
+
setLastSale(r.order);
|
|
152
|
+
setCart([]);
|
|
153
|
+
setPayments({});
|
|
154
|
+
setPaying(false);
|
|
155
|
+
setError(null);
|
|
156
|
+
clientId.current = crypto.randomUUID();
|
|
157
|
+
void queryClient.invalidateQueries({ queryKey: ['counter', 'session'] });
|
|
158
|
+
search.current?.focus();
|
|
159
|
+
},
|
|
160
|
+
onError: (e) => setError(e instanceof Error ? e.message : String(e)),
|
|
161
|
+
});
|
|
162
|
+
const close = useMutation({
|
|
163
|
+
mutationFn: () => api.call('POST', `/api/v1/counter/sessions/${session.data?.id}/close`, {
|
|
164
|
+
closingCash,
|
|
165
|
+
differenceReason: reason,
|
|
166
|
+
}),
|
|
167
|
+
onSuccess: (r) => {
|
|
168
|
+
setClosing(false);
|
|
169
|
+
setLastSale(null);
|
|
170
|
+
setError(null);
|
|
171
|
+
void queryClient.invalidateQueries({ queryKey: ['counter', 'session'] });
|
|
172
|
+
setClosedInfo(r.session);
|
|
173
|
+
},
|
|
174
|
+
onError: (e) => setError(e instanceof Error ? e.message : String(e)),
|
|
175
|
+
});
|
|
176
|
+
const [closedInfo, setClosedInfo] = useState(null);
|
|
177
|
+
const [sendTo, setSendTo] = useState('');
|
|
178
|
+
const [refundReason, setRefundReason] = useState('');
|
|
179
|
+
// The whole last sale taken back, paid back in cash, with its reason.
|
|
180
|
+
const refund = useMutation({
|
|
181
|
+
mutationFn: (id) => api.call('POST', `/api/v1/counter/orders/${id}/refund`, {
|
|
182
|
+
session: session.data?.id,
|
|
183
|
+
reason: refundReason.trim(),
|
|
184
|
+
}),
|
|
185
|
+
onSuccess: (r) => {
|
|
186
|
+
setLastSale(r.order);
|
|
187
|
+
setRefundReason('');
|
|
188
|
+
setError(null);
|
|
189
|
+
void queryClient.invalidateQueries({ queryKey: ['counter', 'session'] });
|
|
190
|
+
},
|
|
191
|
+
onError: (e) => setError(e instanceof Error ? e.message : String(e)),
|
|
192
|
+
});
|
|
193
|
+
const [sentNote, setSentNote] = useState(null);
|
|
194
|
+
// The receipt by email or text: an address with an @ is an email, anything else a phone number.
|
|
195
|
+
const sendReceipt = useMutation({
|
|
196
|
+
mutationFn: (id) => api.call('POST', `/api/v1/counter/orders/${id}/receipt`, {
|
|
197
|
+
...(sendTo.includes('@') ? { email: sendTo.trim() } : { phone: sendTo.trim() }),
|
|
198
|
+
}),
|
|
199
|
+
onSuccess: (r) => {
|
|
200
|
+
setSentNote(t('sent', { how: r.sent.join(', ') }));
|
|
201
|
+
setSendTo('');
|
|
202
|
+
},
|
|
203
|
+
onError: (e) => setError(e instanceof Error ? e.message : String(e)),
|
|
204
|
+
});
|
|
205
|
+
const printReceipt = async (sale) => {
|
|
206
|
+
try {
|
|
207
|
+
const { templates } = await api.call('GET', '/api/v1/models/pos.order/documents');
|
|
208
|
+
const template = templates.find((tpl) => tpl.name === 'Receipt') ??
|
|
209
|
+
templates.find((tpl) => tpl.scope === 'record');
|
|
210
|
+
if (template === undefined)
|
|
211
|
+
throw new Error(t('noReceiptTemplate'));
|
|
212
|
+
const file = await api.download(`/api/v1/models/pos.order/documents/${template.id}`, {
|
|
213
|
+
ids: [sale.id],
|
|
214
|
+
});
|
|
215
|
+
const url = URL.createObjectURL(file.blob);
|
|
216
|
+
window.open(url, '_blank', 'noopener');
|
|
217
|
+
setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
if (shops.isPending)
|
|
224
|
+
return _jsx(Spinner, { label: t('loading') });
|
|
225
|
+
if ((shops.data ?? []).length === 0)
|
|
226
|
+
return _jsx(Alert, { tone: "info", children: t('noShops') });
|
|
227
|
+
if (shop === undefined)
|
|
228
|
+
return (_jsxs("div", { className: "grid gap-3", children: [_jsx("h2", { className: "text-base font-semibold", children: t('chooseShop') }), _jsx("div", { className: "grid grid-cols-[repeat(auto-fill,minmax(12rem,1fr))] gap-3", children: (shops.data ?? []).map((s) => (_jsx(Button, { size: "sm", variant: "secondary", onClick: () => setShopId(s.id), children: s.name }, s.id))) })] }));
|
|
229
|
+
if (session.isPending)
|
|
230
|
+
return _jsx(Spinner, { label: t('loading') });
|
|
231
|
+
if (session.data === null || session.data === undefined)
|
|
232
|
+
return (_jsxs("div", { className: "mx-auto grid max-w-sm gap-3", children: [_jsx("h2", { className: "text-base font-semibold", children: t('openSession', { shop: shop.name }) }), closedInfo === null ? null : (_jsx(Alert, { tone: "info", children: t('closedSummary', {
|
|
233
|
+
name: closedInfo.name,
|
|
234
|
+
total: format.decimal(closedInfo.total),
|
|
235
|
+
difference: format.decimal(closedInfo.closingDifference ?? '0'),
|
|
236
|
+
}) })), _jsxs("div", { className: "grid gap-1", children: [_jsx(Label, { htmlFor: "counter-opening", children: t('openingCash') }), _jsx(Input, { id: "counter-opening", inputMode: "decimal", value: opening, onChange: (e) => setOpening(e.target.value) })] }), error === null ? null : _jsx(Alert, { children: error }), _jsx(Button, { variant: "primary", busy: open.isPending, onClick: () => open.mutate(), children: t('open') })] }));
|
|
237
|
+
const current = session.data;
|
|
238
|
+
return (_jsxs("div", { className: "grid gap-3 lg:grid-cols-[1fr_22rem]", children: [_jsxs("section", { "aria-label": t('products'), className: "grid content-start gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { ref: search, "aria-label": t('search'), placeholder: t('searchHint'), value: query, onChange: (e) => setQuery(e.target.value), onKeyDown: (e) => onSearchKey(e.key), className: "max-w-md" }), _jsx("span", { className: "ms-auto text-xs text-muted-foreground", children: t('sessionLine', {
|
|
239
|
+
name: current.name,
|
|
240
|
+
count: current.orderCount,
|
|
241
|
+
total: format.decimal(current.total),
|
|
242
|
+
}) }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setClosing(true), children: t('closeSession') })] }), catalogue.isPending ? _jsx(Spinner, { label: t('loading') }) : null, _jsx("ul", { className: "grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-2", children: shown.map((p) => (_jsx("li", { children: _jsxs("button", { type: "button", className: "grid w-full gap-1 rounded-lg border bg-surface p-2 text-start hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring", onClick: () => add(p), children: [p.image === null ? (_jsx("span", { className: "aspect-square w-full rounded bg-muted", "aria-hidden": true })) : (_jsx("img", { src: `/api/v1/attachments/${p.image}/download?inline=true&w=256`, alt: "", className: "aspect-square w-full rounded object-cover" })), _jsx("span", { className: "line-clamp-2 text-sm font-medium", children: p.name }), _jsx("span", { className: "tabular-nums text-xs text-muted-foreground", children: format.decimal(p.price) })] }) }, p.id))) })] }), _jsxs("aside", { "aria-label": t('cart'), className: "grid content-start gap-3 rounded-lg border bg-surface p-3", children: [_jsx("h2", { className: "text-sm font-semibold", children: t('cart') }), cart.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('emptyCart') })) : null, _jsx("ul", { className: "grid gap-2", children: cart.map((l) => (_jsxs("li", { className: "grid grid-cols-[1fr_auto_auto] items-center gap-2 text-sm", children: [_jsx("span", { className: "truncate", children: l.product.name }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx(Button, { size: "icon", variant: "ghost", "aria-label": t('less', { name: l.product.name }), onClick: () => l.quantity <= 1
|
|
243
|
+
? removeLine(l.product.id)
|
|
244
|
+
: setQuantity(l.product.id, l.quantity - 1), children: "\u2212" }), _jsx("input", { "aria-label": t('quantityOf', { name: l.product.name }), inputMode: "decimal", className: "w-14 rounded border bg-surface px-1 text-center tabular-nums", value: String(l.quantity), onChange: (e) => {
|
|
245
|
+
const q = Number(e.target.value);
|
|
246
|
+
if (Number.isFinite(q) && q >= 0)
|
|
247
|
+
setQuantity(l.product.id, q);
|
|
248
|
+
} }), _jsx(Button, { size: "icon", variant: "ghost", "aria-label": t('more', { name: l.product.name }), onClick: () => setQuantity(l.product.id, l.quantity + 1), children: "+" })] }), _jsx("span", { className: "tabular-nums", children: format.decimal(money(lineAmount(l, shop.pricesIncludeTax))) })] }, l.product.id))) }), _jsxs("div", { className: "flex items-baseline justify-between border-t pt-2", children: [_jsx("span", { className: "text-sm font-semibold", children: t('total') }), _jsx("span", { className: "text-xl font-semibold tabular-nums", "aria-live": "polite", children: format.decimal(money(total)) })] }), _jsx(Button, { variant: "primary", disabled: cart.length === 0, onClick: () => setPaying(true), children: t('pay') }), queued === 0 ? null : (_jsx("p", { role: "status", className: "text-xs text-muted-foreground", children: t('queued', { count: queued }) })), lastSale === null ? null : (_jsxs("div", { role: "status", className: "grid gap-1 rounded-md border bg-muted/40 p-2 text-sm", children: [_jsx("span", { children: t('soldLine', { name: lastSale.name, total: format.decimal(lastSale.total) }) }), Number(lastSale.change) > 0 ? (_jsx("span", { className: "font-semibold", children: t('changeLine', { change: format.decimal(lastSale.change) }) })) : null, _jsx(Button, { size: "sm", variant: "secondary", onClick: () => void printReceipt(lastSale), children: t('printReceipt') }), _jsxs("div", { className: "grid grid-cols-[1fr_auto] gap-1", children: [_jsx(Input, { "aria-label": t('sendTo'), placeholder: t('sendToHint'), value: sendTo, onChange: (e) => setSendTo(e.target.value) }), _jsx(Button, { size: "sm", variant: "ghost", busy: sendReceipt.isPending, disabled: sendTo.trim() === '', onClick: () => sendReceipt.mutate(lastSale.id), children: t('send') })] }), sentNote === null ? null : (_jsx("span", { className: "text-xs text-muted-foreground", children: sentNote })), _jsxs("div", { className: "grid grid-cols-[1fr_auto] gap-1", children: [_jsx(Input, { "aria-label": t('refundReason'), placeholder: t('refundReasonHint'), value: refundReason, onChange: (e) => setRefundReason(e.target.value) }), _jsx(Button, { size: "sm", variant: "ghost", busy: refund.isPending, disabled: refundReason.trim() === '', onClick: () => refund.mutate(lastSale.id), children: t('refund') })] })] })), error === null ? null : _jsx(Alert, { children: error })] }), _jsx(Dialog, { open: paying, onOpenChange: setPaying, title: t('payTitle', { total: format.decimal(money(total)) }), children: _jsxs("div", { className: "grid gap-3", children: [shop.methods.map((m) => (_jsxs("div", { className: "grid grid-cols-[1fr_8rem] items-center gap-2", children: [_jsx(Label, { htmlFor: `pay-${m.id}`, children: m.name }), _jsx(Input, { id: `pay-${m.id}`, inputMode: "decimal", value: payments[m.id] ?? '', onFocus: () => {
|
|
249
|
+
if ((payments[m.id] ?? '') === '' && paid < total)
|
|
250
|
+
setPayments({ ...payments, [m.id]: money(total - paid) });
|
|
251
|
+
}, onChange: (e) => setPayments({ ...payments, [m.id]: e.target.value }) })] }, m.id))), _jsxs("div", { className: "flex justify-between text-sm", children: [_jsx("span", { children: t('paid') }), _jsx("span", { className: "tabular-nums", children: format.decimal(money(paid)) })] }), _jsxs("div", { className: "flex justify-between text-sm font-semibold", children: [_jsx("span", { children: t('change') }), _jsx("span", { className: "tabular-nums", children: format.decimal(money(change)) })] }), error === null ? null : _jsx(Alert, { children: error }), _jsx(Button, { variant: "primary", disabled: paid + 0.004 < total, busy: sell.isPending, onClick: () => sell.mutate(), children: t('confirmSale') })] }) }), _jsx(Dialog, { open: closing, onOpenChange: setClosing, title: t('closeTitle', { name: current.name }), children: _jsxs("div", { className: "grid gap-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: t('expectedCash', { amount: format.decimal(current.expectedCash ?? '0') }) }), _jsxs("div", { className: "grid gap-1", children: [_jsx(Label, { htmlFor: "counter-closing", children: t('closingCash') }), _jsx(Input, { id: "counter-closing", inputMode: "decimal", value: closingCash, onChange: (e) => setClosingCash(e.target.value) })] }), _jsxs("div", { className: "grid gap-1", children: [_jsx(Label, { htmlFor: "counter-reason", children: t('differenceReason') }), _jsx(Input, { id: "counter-reason", value: reason, onChange: (e) => setReason(e.target.value) })] }), error === null ? null : _jsx(Alert, { children: error }), _jsx(Button, { variant: "primary", busy: close.isPending, onClick: () => close.mutate(), children: t('closeSession') })] }) })] }));
|
|
252
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* The web contribution of `shutro-erp/pos`: the register page.
|
|
4
|
+
*/
|
|
5
|
+
import { type Registry } from '../../ui/index.js';
|
|
6
|
+
import type { i18n } from 'i18next';
|
|
7
|
+
import { messages } from './messages.js';
|
|
8
|
+
export { Register } from './Register.js';
|
|
9
|
+
/** Registers the page (and the messages in `i18n`, when given). */
|
|
10
|
+
export declare function registerPosWeb(registry: Registry, i18n?: i18n): Registry;
|
|
11
|
+
export { messages as posMessages };
|