@ketvietlab/ketsuite 0.1.3 → 0.1.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/cli.js +1 -1
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +3 -0
- package/dist/deployment.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/account_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/account_staff_channel/channel-routes.js +1 -12
- package/dist/modules/account_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/attendance/channel-routes.d.ts.map +1 -1
- package/dist/modules/attendance/channel-routes.js +1 -12
- package/dist/modules/attendance/channel-routes.js.map +1 -1
- package/dist/modules/backend/design/HANDOFF.md +2 -2
- package/dist/modules/backend/design/auth.css +12 -0
- package/dist/modules/backend/design/client/table-selection.d.mts +3 -3
- package/dist/modules/backend/design/client/table-selection.d.mts.map +1 -1
- package/dist/modules/backend/design/client/table-selection.mjs +1 -1
- package/dist/modules/backend/design/client/table-selection.mjs.map +4 -4
- package/dist/modules/backend/design/content.css +43 -0
- package/dist/modules/backend/design/design-system.css +1 -1
- package/dist/modules/backend/design/forms.css +84 -1
- package/dist/modules/backend/design/lists.css +26 -18
- package/dist/modules/backend/design/record.css +5 -2
- package/dist/modules/backend/design/responsive.css +5 -6
- package/dist/modules/backend/design/user-workflow.css +257 -0
- package/dist/modules/backend/forms.d.ts +38 -1
- package/dist/modules/backend/forms.d.ts.map +1 -1
- package/dist/modules/backend/forms.js +29 -0
- package/dist/modules/backend/forms.js.map +1 -1
- package/dist/modules/backend/index.d.ts +3 -2
- package/dist/modules/backend/index.d.ts.map +1 -1
- package/dist/modules/backend/index.js +3 -2
- package/dist/modules/backend/index.js.map +1 -1
- package/dist/modules/backend/messages.d.ts.map +1 -1
- package/dist/modules/backend/messages.js +42 -2
- package/dist/modules/backend/messages.js.map +1 -1
- package/dist/modules/channel_api/core.d.ts +30 -0
- package/dist/modules/channel_api/core.d.ts.map +1 -1
- package/dist/modules/channel_api/core.js +28 -0
- package/dist/modules/channel_api/core.js.map +1 -1
- package/dist/modules/crm/functions.d.ts +5 -0
- package/dist/modules/crm/functions.d.ts.map +1 -1
- package/dist/modules/crm/functions.js +105 -10
- package/dist/modules/crm/functions.js.map +1 -1
- package/dist/modules/crm/jobs.d.ts.map +1 -1
- package/dist/modules/crm/jobs.js +1 -0
- package/dist/modules/crm/jobs.js.map +1 -1
- package/dist/modules/crm/operations.d.ts.map +1 -1
- package/dist/modules/crm/operations.js +2 -1
- package/dist/modules/crm/operations.js.map +1 -1
- package/dist/modules/crm_backend/index.d.ts.map +1 -1
- package/dist/modules/crm_backend/index.js +5 -2
- package/dist/modules/crm_backend/index.js.map +1 -1
- package/dist/modules/crm_backend/messages.d.ts +116 -0
- package/dist/modules/crm_backend/messages.d.ts.map +1 -1
- package/dist/modules/crm_backend/messages.js +122 -6
- package/dist/modules/crm_backend/messages.js.map +1 -1
- package/dist/modules/crm_backend/routes.d.ts.map +1 -1
- package/dist/modules/crm_backend/routes.js +434 -174
- package/dist/modules/crm_backend/routes.js.map +1 -1
- package/dist/modules/crm_backend/screens/case-create.d.ts +7 -0
- package/dist/modules/crm_backend/screens/case-create.d.ts.map +1 -1
- package/dist/modules/crm_backend/screens/case-create.js +13 -4
- package/dist/modules/crm_backend/screens/case-create.js.map +1 -1
- package/dist/modules/crm_backend/screens/case-detail.d.ts +6 -0
- package/dist/modules/crm_backend/screens/case-detail.d.ts.map +1 -1
- package/dist/modules/crm_backend/screens/case-detail.js +73 -23
- package/dist/modules/crm_backend/screens/case-detail.js.map +1 -1
- package/dist/modules/crm_backend/screens/configuration.d.ts +21 -3
- package/dist/modules/crm_backend/screens/configuration.d.ts.map +1 -1
- package/dist/modules/crm_backend/screens/configuration.js +260 -55
- package/dist/modules/crm_backend/screens/configuration.js.map +1 -1
- package/dist/modules/crm_backend/screens/index.d.ts +3 -3
- package/dist/modules/crm_backend/screens/index.d.ts.map +1 -1
- package/dist/modules/crm_backend/screens/index.js +2 -2
- package/dist/modules/crm_backend/screens/index.js.map +1 -1
- package/dist/modules/crm_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/crm_staff_channel/channel-routes.js +1 -12
- package/dist/modules/crm_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/flow/membership.d.ts.map +1 -1
- package/dist/modules/flow/membership.js +7 -1
- package/dist/modules/flow/membership.js.map +1 -1
- package/dist/modules/flow/operations.d.ts +10 -4
- package/dist/modules/flow/operations.d.ts.map +1 -1
- package/dist/modules/flow/operations.js +10 -4
- package/dist/modules/flow/operations.js.map +1 -1
- package/dist/modules/flow_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/flow_staff_channel/channel-routes.js +1 -20
- package/dist/modules/flow_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/hospitality_core/messages.d.ts.map +1 -1
- package/dist/modules/hospitality_core/messages.js +44 -0
- package/dist/modules/hospitality_core/messages.js.map +1 -1
- package/dist/modules/hospitality_core/night-audit.d.ts.map +1 -1
- package/dist/modules/hospitality_core/night-audit.js +7 -0
- package/dist/modules/hospitality_core/night-audit.js.map +1 -1
- package/dist/modules/hospitality_core/operations.d.ts.map +1 -1
- package/dist/modules/hospitality_core/operations.js +150 -40
- package/dist/modules/hospitality_core/operations.js.map +1 -1
- package/dist/modules/hospitality_core/routes.d.ts.map +1 -1
- package/dist/modules/hospitality_core/routes.js +24 -2
- package/dist/modules/hospitality_core/routes.js.map +1 -1
- package/dist/modules/hospitality_core/screens/check-out-prep.d.ts +46 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.d.ts.map +1 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.js +133 -0
- package/dist/modules/hospitality_core/screens/check-out-prep.js.map +1 -0
- package/dist/modules/hospitality_core/screens/folio-detail.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/folio-detail.js +2 -4
- package/dist/modules/hospitality_core/screens/folio-detail.js.map +1 -1
- package/dist/modules/hospitality_core/screens/index.d.ts +2 -0
- package/dist/modules/hospitality_core/screens/index.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/index.js +1 -0
- package/dist/modules/hospitality_core/screens/index.js.map +1 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.js +8 -1
- package/dist/modules/hospitality_core/screens/reservation-detail.js.map +1 -1
- package/dist/modules/hospitality_core/screens/shared.d.ts +12 -0
- package/dist/modules/hospitality_core/screens/shared.d.ts.map +1 -1
- package/dist/modules/hospitality_core/screens/shared.js +25 -4
- package/dist/modules/hospitality_core/screens/shared.js.map +1 -1
- package/dist/modules/hospitality_core/services.d.ts +48 -0
- package/dist/modules/hospitality_core/services.d.ts.map +1 -1
- package/dist/modules/hospitality_core/services.js +70 -0
- package/dist/modules/hospitality_core/services.js.map +1 -1
- package/dist/modules/hospitality_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/hospitality_staff_channel/channel-routes.js +1 -12
- package/dist/modules/hospitality_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/inventory_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/inventory_staff_channel/channel-routes.js +1 -12
- package/dist/modules/inventory_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/loyalty/admin-functions.d.ts +2 -0
- package/dist/modules/loyalty/admin-functions.d.ts.map +1 -1
- package/dist/modules/loyalty/admin-functions.js +96 -20
- package/dist/modules/loyalty/admin-functions.js.map +1 -1
- package/dist/modules/loyalty/engine.d.ts +4 -1
- package/dist/modules/loyalty/engine.d.ts.map +1 -1
- package/dist/modules/loyalty/engine.js +125 -18
- package/dist/modules/loyalty/engine.js.map +1 -1
- package/dist/modules/loyalty/index.d.ts.map +1 -1
- package/dist/modules/loyalty/index.js +4 -3
- package/dist/modules/loyalty/index.js.map +1 -1
- package/dist/modules/loyalty/jobs.d.ts.map +1 -1
- package/dist/modules/loyalty/jobs.js +0 -1
- package/dist/modules/loyalty/jobs.js.map +1 -1
- package/dist/modules/loyalty/membership-functions.d.ts +5 -0
- package/dist/modules/loyalty/membership-functions.d.ts.map +1 -1
- package/dist/modules/loyalty/membership-functions.js +37 -23
- package/dist/modules/loyalty/membership-functions.js.map +1 -1
- package/dist/modules/loyalty/messages.d.ts +14 -0
- package/dist/modules/loyalty/messages.d.ts.map +1 -1
- package/dist/modules/loyalty/messages.js +14 -0
- package/dist/modules/loyalty/messages.js.map +1 -1
- package/dist/modules/loyalty/models.d.ts.map +1 -1
- package/dist/modules/loyalty/models.js +17 -0
- package/dist/modules/loyalty/models.js.map +1 -1
- package/dist/modules/loyalty/order-functions.d.ts.map +1 -1
- package/dist/modules/loyalty/order-functions.js +70 -17
- package/dist/modules/loyalty/order-functions.js.map +1 -1
- package/dist/modules/loyalty/program-design.d.ts +11 -0
- package/dist/modules/loyalty/program-design.d.ts.map +1 -0
- package/dist/modules/loyalty/program-design.js +356 -0
- package/dist/modules/loyalty/program-design.js.map +1 -0
- package/dist/modules/loyalty/stats.d.ts +0 -13
- package/dist/modules/loyalty/stats.d.ts.map +1 -1
- package/dist/modules/loyalty/stats.js +11 -6
- package/dist/modules/loyalty/stats.js.map +1 -1
- package/dist/modules/loyalty/stored-value.d.ts.map +1 -1
- package/dist/modules/loyalty/stored-value.js +7 -2
- package/dist/modules/loyalty/stored-value.js.map +1 -1
- package/dist/modules/loyalty/types.d.ts +1 -1
- package/dist/modules/loyalty/types.d.ts.map +1 -1
- package/dist/modules/loyalty_backend/design-messages.d.ts +221 -0
- package/dist/modules/loyalty_backend/design-messages.d.ts.map +1 -0
- package/dist/modules/loyalty_backend/design-messages.js +221 -0
- package/dist/modules/loyalty_backend/design-messages.js.map +1 -0
- package/dist/modules/loyalty_backend/index.js +224 -412
- package/dist/modules/loyalty_backend/index.js.map +1 -1
- package/dist/modules/loyalty_backend/messages.d.ts +642 -214
- package/dist/modules/loyalty_backend/messages.d.ts.map +1 -1
- package/dist/modules/loyalty_backend/messages.js +19 -13
- package/dist/modules/loyalty_backend/messages.js.map +1 -1
- package/dist/modules/loyalty_backend/program-workspace.d.ts +4 -0
- package/dist/modules/loyalty_backend/program-workspace.d.ts.map +1 -0
- package/dist/modules/loyalty_backend/program-workspace.js +600 -0
- package/dist/modules/loyalty_backend/program-workspace.js.map +1 -0
- package/dist/modules/loyalty_backend/screens/index.d.ts +12 -36
- package/dist/modules/loyalty_backend/screens/index.d.ts.map +1 -1
- package/dist/modules/loyalty_backend/screens/index.js +148 -292
- package/dist/modules/loyalty_backend/screens/index.js.map +1 -1
- package/dist/modules/loyalty_backend/screens/page-frame.d.ts.map +1 -1
- package/dist/modules/loyalty_backend/screens/page-frame.js +2 -2
- package/dist/modules/loyalty_backend/screens/page-frame.js.map +1 -1
- package/dist/modules/loyalty_pos/functions.d.ts.map +1 -1
- package/dist/modules/loyalty_pos/functions.js +1 -0
- package/dist/modules/loyalty_pos/functions.js.map +1 -1
- package/dist/modules/loyalty_pos/index.js +1 -1
- package/dist/modules/loyalty_pos/index.js.map +1 -1
- package/dist/modules/loyalty_sale/functions.d.ts +1 -0
- package/dist/modules/loyalty_sale/functions.d.ts.map +1 -1
- package/dist/modules/loyalty_sale/functions.js +1 -0
- package/dist/modules/loyalty_sale/functions.js.map +1 -1
- package/dist/modules/partner_backend/client/partner.css +3 -1
- package/dist/modules/partner_backend/index.d.ts.map +1 -1
- package/dist/modules/partner_backend/index.js +4 -0
- package/dist/modules/partner_backend/index.js.map +1 -1
- package/dist/modules/partner_backend/routes.d.ts.map +1 -1
- package/dist/modules/partner_backend/routes.js +6 -1
- package/dist/modules/partner_backend/routes.js.map +1 -1
- package/dist/modules/partner_backend/screens/form.d.ts +1 -0
- package/dist/modules/partner_backend/screens/form.d.ts.map +1 -1
- package/dist/modules/partner_backend/screens/form.js +1 -0
- package/dist/modules/partner_backend/screens/form.js.map +1 -1
- package/dist/modules/purchase_staff_channel/order-routes.d.ts.map +1 -1
- package/dist/modules/purchase_staff_channel/order-routes.js +1 -12
- package/dist/modules/purchase_staff_channel/order-routes.js.map +1 -1
- package/dist/modules/quality_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/quality_staff_channel/channel-routes.js +1 -12
- package/dist/modules/quality_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/sale/functions.d.ts.map +1 -1
- package/dist/modules/sale/functions.js +27 -17
- package/dist/modules/sale/functions.js.map +1 -1
- package/dist/modules/sale_staff_channel/channel-routes.d.ts.map +1 -1
- package/dist/modules/sale_staff_channel/channel-routes.js +1 -12
- package/dist/modules/sale_staff_channel/channel-routes.js.map +1 -1
- package/dist/modules/sale_staff_channel/order-routes.d.ts.map +1 -1
- package/dist/modules/sale_staff_channel/order-routes.js +1 -12
- package/dist/modules/sale_staff_channel/order-routes.js.map +1 -1
- package/dist/modules/stock_staff_channel/operation-routes.d.ts.map +1 -1
- package/dist/modules/stock_staff_channel/operation-routes.js +1 -12
- package/dist/modules/stock_staff_channel/operation-routes.js.map +1 -1
- package/dist/modules/user/authorization.d.ts +64 -1
- package/dist/modules/user/authorization.d.ts.map +1 -1
- package/dist/modules/user/authorization.js +445 -53
- package/dist/modules/user/authorization.js.map +1 -1
- package/dist/modules/user/functions.d.ts.map +1 -1
- package/dist/modules/user/functions.js +45 -23
- package/dist/modules/user/functions.js.map +1 -1
- package/dist/modules/user/index.d.ts +2 -1
- package/dist/modules/user/index.d.ts.map +1 -1
- package/dist/modules/user/index.js +1 -1
- package/dist/modules/user/index.js.map +1 -1
- package/dist/modules/user/models.d.ts.map +1 -1
- package/dist/modules/user/models.js +5 -0
- package/dist/modules/user/models.js.map +1 -1
- package/dist/modules/user/roles.d.ts.map +1 -1
- package/dist/modules/user/roles.js +2 -1
- package/dist/modules/user/roles.js.map +1 -1
- package/dist/modules/user_backend/access-routes.d.ts +31 -0
- package/dist/modules/user_backend/access-routes.d.ts.map +1 -0
- package/dist/modules/user_backend/access-routes.js +447 -0
- package/dist/modules/user_backend/access-routes.js.map +1 -0
- package/dist/modules/user_backend/index.d.ts +1 -0
- package/dist/modules/user_backend/index.d.ts.map +1 -1
- package/dist/modules/user_backend/index.js +1 -0
- package/dist/modules/user_backend/index.js.map +1 -1
- package/dist/modules/user_backend/messages.d.ts +4 -0
- package/dist/modules/user_backend/messages.d.ts.map +1 -1
- package/dist/modules/user_backend/messages.js +4 -0
- package/dist/modules/user_backend/messages.js.map +1 -1
- package/dist/modules/user_backend/routes.d.ts.map +1 -1
- package/dist/modules/user_backend/routes.js +38 -3
- package/dist/modules/user_backend/routes.js.map +1 -1
- package/dist/modules/user_backend/screens/user-form.d.ts +1 -0
- package/dist/modules/user_backend/screens/user-form.d.ts.map +1 -1
- package/dist/modules/user_backend/screens/user-form.js +28 -0
- package/dist/modules/user_backend/screens/user-form.js.map +1 -1
- package/dist/modules/website/cms.d.ts.map +1 -1
- package/dist/modules/website/cms.js +283 -6
- package/dist/modules/website/cms.js.map +1 -1
- package/dist/modules/website/index.d.ts.map +1 -1
- package/dist/modules/website/index.js +12 -0
- package/dist/modules/website/index.js.map +1 -1
- package/dist/modules/website/media-usage.d.ts +38 -0
- package/dist/modules/website/media-usage.d.ts.map +1 -0
- package/dist/modules/website/media-usage.js +87 -0
- package/dist/modules/website/media-usage.js.map +1 -0
- package/dist/modules/website/paths.js +1 -1
- package/dist/modules/website/paths.js.map +1 -1
- package/dist/modules/website/sections.d.ts.map +1 -1
- package/dist/modules/website/sections.js +12 -1
- package/dist/modules/website/sections.js.map +1 -1
- package/dist/modules/website_backend/index.js +138 -0
- package/dist/modules/website_backend/index.js.map +1 -1
- package/dist/modules/website_backend/menus.d.ts.map +1 -1
- package/dist/modules/website_backend/menus.js +11 -0
- package/dist/modules/website_backend/menus.js.map +1 -1
- package/dist/modules/website_backend/routes.d.ts.map +1 -1
- package/dist/modules/website_backend/routes.js +414 -31
- package/dist/modules/website_backend/routes.js.map +1 -1
- package/dist/modules/website_backend/screens/index.d.ts +98 -6
- package/dist/modules/website_backend/screens/index.d.ts.map +1 -1
- package/dist/modules/website_backend/screens/index.js +600 -154
- package/dist/modules/website_backend/screens/index.js.map +1 -1
- package/dist/modules/website_backend/screens/page-frame.d.ts +3 -1
- package/dist/modules/website_backend/screens/page-frame.d.ts.map +1 -1
- package/dist/modules/website_backend/screens/page-frame.js +1 -1
- package/dist/modules/website_backend/screens/page-frame.js.map +1 -1
- package/dist/modules/website_menu/functions.d.ts.map +1 -1
- package/dist/modules/website_menu/functions.js +51 -0
- package/dist/modules/website_menu/functions.js.map +1 -1
- package/dist/modules/website_menu/index.js +2 -0
- package/dist/modules/website_menu/index.js.map +1 -1
- package/dist/modules/website_search/functions.d.ts.map +1 -1
- package/dist/modules/website_search/functions.js +10 -120
- package/dist/modules/website_search/functions.js.map +1 -1
- package/dist/modules/website_search/index.js +2 -0
- package/dist/modules/website_search/index.js.map +1 -1
- package/dist/modules/website_search/jobs.d.ts +3 -0
- package/dist/modules/website_search/jobs.d.ts.map +1 -0
- package/dist/modules/website_search/jobs.js +101 -0
- package/dist/modules/website_search/jobs.js.map +1 -0
- package/dist/modules/website_search/rebuild.d.ts +33 -0
- package/dist/modules/website_search/rebuild.d.ts.map +1 -0
- package/dist/modules/website_search/rebuild.js +125 -0
- package/dist/modules/website_search/rebuild.js.map +1 -0
- package/dist/permission-catalogue.d.ts.map +1 -1
- package/dist/permission-catalogue.js +21 -1
- package/dist/permission-catalogue.js.map +1 -1
- package/dist/scaffold/index.js +1 -1
- package/dist/ui/chrome.d.ts +8 -0
- package/dist/ui/chrome.d.ts.map +1 -1
- package/dist/ui/chrome.js +9 -1
- package/dist/ui/chrome.js.map +1 -1
- package/dist/ui/client/table-selection-view.d.ts.map +1 -1
- package/dist/ui/client/table-selection-view.js +161 -5
- package/dist/ui/client/table-selection-view.js.map +1 -1
- package/dist/ui/client/user-workflow.d.ts +2 -0
- package/dist/ui/client/user-workflow.d.ts.map +1 -0
- package/dist/ui/client/user-workflow.js +141 -0
- package/dist/ui/client/user-workflow.js.map +1 -0
- package/dist/ui/form.d.ts +9 -0
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +29 -15
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/icons.d.ts.map +1 -1
- package/dist/ui/icons.js +2 -0
- package/dist/ui/icons.js.map +1 -1
- package/dist/ui/index.d.ts +7 -3
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +6 -3
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/layout.d.ts.map +1 -1
- package/dist/ui/layout.js +14 -4
- package/dist/ui/layout.js.map +1 -1
- package/dist/ui/nav.d.ts.map +1 -1
- package/dist/ui/nav.js +2 -1
- package/dist/ui/nav.js.map +1 -1
- package/dist/ui/state.d.ts +20 -1
- package/dist/ui/state.d.ts.map +1 -1
- package/dist/ui/state.js +17 -0
- package/dist/ui/state.js.map +1 -1
- package/dist/ui/table.js +1 -1
- package/dist/ui/table.js.map +1 -1
- package/dist/ui/user-workflow.d.ts +72 -0
- package/dist/ui/user-workflow.d.ts.map +1 -0
- package/dist/ui/user-workflow.js +144 -0
- package/dist/ui/user-workflow.js.map +1 -0
- package/package.json +8 -4
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { newProgramRoute, programWorkspaceRoute } from './program-workspace.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { defineModule, text } from '@ketvietlab/ketjs';
|
|
3
|
-
import { readForm, seeOther } from '../backend/forms.js';
|
|
4
|
+
import { formRefusal, readForm, seeOther } from '../backend/forms.js';
|
|
4
5
|
import { PAGE_SIZE, pageOf, pager, searchOf, withParam } from '../backend/paging.js';
|
|
5
|
-
import {
|
|
6
|
+
import { LEDGER_OPERATIONS, PROGRAM_TYPES } from '../loyalty/types.js';
|
|
7
|
+
import { tierFormSchema } from '../loyalty/admin-functions.js';
|
|
6
8
|
import { messages } from './messages.js';
|
|
7
|
-
import { dashboardScreen, ledgerScreen,
|
|
9
|
+
import { dashboardScreen, ledgerScreen, tiersScreen, orderLoyaltyScreen, portalScreen, programsScreen, walletDetailScreen, walletsScreen, } from './screens/index.js';
|
|
8
10
|
import { adminPage, choices, inLocale, optional } from '../backend/screen.js';
|
|
9
11
|
/**
|
|
10
12
|
* One filter dimension, as the chrome's dropdown wants it.
|
|
@@ -26,7 +28,10 @@ const filterMenu = (url, id, label, param, current, options) => ({
|
|
|
26
28
|
/** The chips above the table saying which filters are on, each one removable. */
|
|
27
29
|
const filterFacets = (url, active) => active
|
|
28
30
|
.filter((entry) => Boolean(entry.label))
|
|
29
|
-
.map((entry) => ({
|
|
31
|
+
.map((entry) => ({
|
|
32
|
+
label: entry.label,
|
|
33
|
+
without: withParam(url, entry.param, null),
|
|
34
|
+
}));
|
|
30
35
|
/**
|
|
31
36
|
* A window over a ledger, chosen by name rather than by date.
|
|
32
37
|
*
|
|
@@ -40,14 +45,20 @@ const periodWindow = (period) => {
|
|
|
40
45
|
const at = new Date();
|
|
41
46
|
const year = at.getUTCFullYear();
|
|
42
47
|
if (period === 'year')
|
|
43
|
-
return {
|
|
48
|
+
return {
|
|
49
|
+
from: new Date(Date.UTC(year, 0, 1)).toISOString(),
|
|
50
|
+
to: at.toISOString(),
|
|
51
|
+
};
|
|
44
52
|
if (period === 'quarter')
|
|
45
53
|
return {
|
|
46
54
|
from: new Date(Date.UTC(year, Math.floor(at.getUTCMonth() / 3) * 3, 1)).toISOString(),
|
|
47
55
|
to: at.toISOString(),
|
|
48
56
|
};
|
|
49
57
|
if (period === 'month')
|
|
50
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
from: new Date(Date.UTC(year, at.getUTCMonth(), 1)).toISOString(),
|
|
60
|
+
to: at.toISOString(),
|
|
61
|
+
};
|
|
51
62
|
return {};
|
|
52
63
|
};
|
|
53
64
|
const crossSite = (req) => {
|
|
@@ -61,9 +72,11 @@ const crossSite = (req) => {
|
|
|
61
72
|
return true;
|
|
62
73
|
}
|
|
63
74
|
};
|
|
64
|
-
const bool = (value) => value === '1' || value === 'true' || value === 'on';
|
|
65
75
|
const resultErrors = (result, _) => (result?.errors ?? []).map((error) => error.code ? _(error.code, error.params) : String(error.message ?? _('loyalty_backend.error.invalid')));
|
|
66
|
-
const options = (_, values, group) => values.map((value) => ({
|
|
76
|
+
const options = (_, values, group) => values.map((value) => ({
|
|
77
|
+
value,
|
|
78
|
+
label: _(`loyalty_backend.${group}.${value}`),
|
|
79
|
+
}));
|
|
67
80
|
const dataFor = async (ctx, url, req) => {
|
|
68
81
|
const [programs, partners, templates] = await Promise.all([
|
|
69
82
|
ctx.call('loyalty.program.list', { includeArchived: true }, url, req),
|
|
@@ -86,188 +99,6 @@ const createProgramFields = (_) => [
|
|
|
86
99
|
required: true,
|
|
87
100
|
},
|
|
88
101
|
];
|
|
89
|
-
const programFields = (_, row) => [
|
|
90
|
-
{ name: 'name', label: _('loyalty_backend.field.name'), value: String(row.name ?? ''), required: true },
|
|
91
|
-
{
|
|
92
|
-
name: 'programType',
|
|
93
|
-
label: _('loyalty_backend.field.programType'),
|
|
94
|
-
type: 'select',
|
|
95
|
-
value: String(row.programType ?? ''),
|
|
96
|
-
options: options(_, PROGRAM_TYPES, 'programType'),
|
|
97
|
-
required: true,
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: 'sequence',
|
|
101
|
-
label: _('loyalty_backend.field.sequence'),
|
|
102
|
-
type: 'number',
|
|
103
|
-
value: Number(row.sequence ?? 10),
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: 'currency',
|
|
107
|
-
label: _('loyalty_backend.field.currency'),
|
|
108
|
-
value: String(row.currency ?? ''),
|
|
109
|
-
required: true,
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
name: 'dateFrom',
|
|
113
|
-
label: _('loyalty_backend.field.dateFrom'),
|
|
114
|
-
type: 'datetime-local',
|
|
115
|
-
value: row.dateFrom ? String(row.dateFrom).slice(0, 16) : '',
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: 'dateTo',
|
|
119
|
-
label: _('loyalty_backend.field.dateTo'),
|
|
120
|
-
type: 'datetime-local',
|
|
121
|
-
value: row.dateTo ? String(row.dateTo).slice(0, 16) : '',
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
name: 'limitUsage',
|
|
125
|
-
label: _('loyalty_backend.field.limitUsage'),
|
|
126
|
-
type: 'checkbox',
|
|
127
|
-
value: Boolean(row.limitUsage),
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
name: 'maxUsage',
|
|
131
|
-
label: _('loyalty_backend.field.maxUsage'),
|
|
132
|
-
type: 'number',
|
|
133
|
-
value: row.maxUsage == null ? '' : Number(row.maxUsage),
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
name: 'appliesOn',
|
|
137
|
-
label: _('loyalty_backend.field.appliesOn'),
|
|
138
|
-
type: 'select',
|
|
139
|
-
value: String(row.appliesOn ?? ''),
|
|
140
|
-
options: options(_, PROGRAM_APPLIES_ON, 'appliesOn'),
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'trigger',
|
|
144
|
-
label: _('loyalty_backend.field.trigger'),
|
|
145
|
-
type: 'select',
|
|
146
|
-
value: String(row.trigger ?? ''),
|
|
147
|
-
options: options(_, PROGRAM_TRIGGERS, 'trigger'),
|
|
148
|
-
},
|
|
149
|
-
{ name: 'pointName', label: _('loyalty_backend.field.pointName'), value: String(row.pointName ?? '') },
|
|
150
|
-
{
|
|
151
|
-
name: 'portalVisible',
|
|
152
|
-
label: _('loyalty_backend.field.portalVisible'),
|
|
153
|
-
type: 'checkbox',
|
|
154
|
-
value: Boolean(row.portalVisible),
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: 'availableSale',
|
|
158
|
-
label: _('loyalty_backend.field.availableSale'),
|
|
159
|
-
type: 'checkbox',
|
|
160
|
-
value: Boolean(row.availableSale),
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: 'availablePos',
|
|
164
|
-
label: _('loyalty_backend.field.availablePos'),
|
|
165
|
-
type: 'checkbox',
|
|
166
|
-
value: Boolean(row.availablePos),
|
|
167
|
-
},
|
|
168
|
-
];
|
|
169
|
-
const programInput = (id, form) => ({
|
|
170
|
-
id,
|
|
171
|
-
name: form.name ?? '',
|
|
172
|
-
programType: form.programType ?? '',
|
|
173
|
-
sequence: Number(form.sequence || 10),
|
|
174
|
-
...optional(form, 'currency'),
|
|
175
|
-
...optional(form, 'dateFrom'),
|
|
176
|
-
...optional(form, 'dateTo'),
|
|
177
|
-
limitUsage: bool(form.limitUsage),
|
|
178
|
-
...(form.maxUsage ? { maxUsage: Number(form.maxUsage) } : {}),
|
|
179
|
-
...optional(form, 'appliesOn'),
|
|
180
|
-
...optional(form, 'trigger'),
|
|
181
|
-
...optional(form, 'pointName'),
|
|
182
|
-
portalVisible: bool(form.portalVisible),
|
|
183
|
-
availableSale: bool(form.availableSale),
|
|
184
|
-
availablePos: bool(form.availablePos),
|
|
185
|
-
});
|
|
186
|
-
const ruleFields = (_, products) => [
|
|
187
|
-
{ name: 'priority', label: _('loyalty_backend.field.priority'), type: 'number', value: 10 },
|
|
188
|
-
{
|
|
189
|
-
name: 'productId',
|
|
190
|
-
label: _('loyalty_backend.field.rewardProduct'),
|
|
191
|
-
type: 'select',
|
|
192
|
-
options: choices(products, true),
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: 'pointAmount',
|
|
196
|
-
label: _('loyalty_backend.field.pointAmount'),
|
|
197
|
-
type: 'decimal',
|
|
198
|
-
value: 1,
|
|
199
|
-
required: true,
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: 'pointMode',
|
|
203
|
-
label: _('loyalty_backend.field.pointMode'),
|
|
204
|
-
type: 'select',
|
|
205
|
-
options: options(_, POINT_MODES, 'pointMode'),
|
|
206
|
-
required: true,
|
|
207
|
-
},
|
|
208
|
-
{ name: 'pointSplit', label: _('loyalty_backend.field.pointSplit'), type: 'checkbox' },
|
|
209
|
-
{ name: 'minimumQuantity', label: _('loyalty_backend.field.minimumQuantity'), type: 'decimal', value: 1 },
|
|
210
|
-
{ name: 'minimumAmount', label: _('loyalty_backend.field.minimumAmount'), type: 'decimal', value: 0 },
|
|
211
|
-
{
|
|
212
|
-
name: 'taxMode',
|
|
213
|
-
label: _('loyalty_backend.field.taxMode'),
|
|
214
|
-
type: 'select',
|
|
215
|
-
options: options(_, TAX_MODES, 'taxMode'),
|
|
216
|
-
required: true,
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
name: 'mode',
|
|
220
|
-
label: _('loyalty_backend.field.mode'),
|
|
221
|
-
type: 'select',
|
|
222
|
-
options: options(_, PROGRAM_TRIGGERS, 'trigger'),
|
|
223
|
-
required: true,
|
|
224
|
-
},
|
|
225
|
-
{ name: 'code', label: _('loyalty_backend.field.code') },
|
|
226
|
-
];
|
|
227
|
-
const rewardFields = (_, products) => [
|
|
228
|
-
{ name: 'description', label: _('loyalty_backend.field.description'), required: true },
|
|
229
|
-
{
|
|
230
|
-
name: 'rewardType',
|
|
231
|
-
label: _('loyalty_backend.field.rewardType'),
|
|
232
|
-
type: 'select',
|
|
233
|
-
options: options(_, REWARD_TYPES, 'rewardType'),
|
|
234
|
-
required: true,
|
|
235
|
-
},
|
|
236
|
-
{ name: 'discount', label: _('loyalty_backend.field.discount'), type: 'decimal', value: 10 },
|
|
237
|
-
{
|
|
238
|
-
name: 'discountMode',
|
|
239
|
-
label: _('loyalty_backend.field.discountMode'),
|
|
240
|
-
type: 'select',
|
|
241
|
-
options: options(_, DISCOUNT_MODES, 'discountMode'),
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
name: 'discountApplicability',
|
|
245
|
-
label: _('loyalty_backend.field.discountApplicability'),
|
|
246
|
-
type: 'select',
|
|
247
|
-
options: options(_, DISCOUNT_APPLICABILITY, 'discountApplicability'),
|
|
248
|
-
},
|
|
249
|
-
{ name: 'discountMaximum', label: _('loyalty_backend.field.discountMaximum'), type: 'decimal' },
|
|
250
|
-
{
|
|
251
|
-
name: 'rewardProductId',
|
|
252
|
-
label: _('loyalty_backend.field.rewardProduct'),
|
|
253
|
-
type: 'select',
|
|
254
|
-
options: choices(products, true),
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
name: 'rewardProductQuantity',
|
|
258
|
-
label: _('loyalty_backend.field.rewardProductQuantity'),
|
|
259
|
-
type: 'decimal',
|
|
260
|
-
value: 1,
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
name: 'requiredPoints',
|
|
264
|
-
label: _('loyalty_backend.field.requiredPoints'),
|
|
265
|
-
type: 'decimal',
|
|
266
|
-
value: 1,
|
|
267
|
-
required: true,
|
|
268
|
-
},
|
|
269
|
-
{ name: 'clearWallet', label: _('loyalty_backend.field.clearWallet'), type: 'checkbox' },
|
|
270
|
-
];
|
|
271
102
|
const routes = {
|
|
272
103
|
'/admin/loyalty': (ctx) => async (url, req) => {
|
|
273
104
|
if (req.method !== 'GET')
|
|
@@ -289,23 +120,8 @@ const routes = {
|
|
|
289
120
|
});
|
|
290
121
|
},
|
|
291
122
|
'/admin/loyalty/programs': (ctx) => async (url, req) => {
|
|
292
|
-
if (req.method === 'POST')
|
|
293
|
-
|
|
294
|
-
return text('Forbidden', { status: 403 });
|
|
295
|
-
const form = await readForm(req);
|
|
296
|
-
const result = await ctx.call('loyalty.program.save', { ...programInput(randomUUID(), form), availableSale: true, availablePos: true }, url, req);
|
|
297
|
-
if (result.ok)
|
|
298
|
-
return seeOther(inLocale(url, `/admin/loyalty/programs/${String(result.id)}`));
|
|
299
|
-
const _ = ctx.translate(ctx.localeOf(url, req));
|
|
300
|
-
const [rows, totals] = await Promise.all([
|
|
301
|
-
ctx.call('loyalty.program.list', { includeArchived: true, limit: PAGE_SIZE }, url, req),
|
|
302
|
-
ctx.call('loyalty.program.stats', {}, url, req),
|
|
303
|
-
]);
|
|
304
|
-
return adminPage(ctx, url, req, {
|
|
305
|
-
title: 'loyalty_backend.programs.title',
|
|
306
|
-
body: (_, frame) => programsScreen(_, frame, rows, totals, createProgramFields(_), resultErrors(result, _)),
|
|
307
|
-
});
|
|
308
|
-
}
|
|
123
|
+
if (req.method === 'POST')
|
|
124
|
+
return seeOther(inLocale(url, '/admin/loyalty/programs/new'));
|
|
309
125
|
if (req.method !== 'GET')
|
|
310
126
|
return text('GET or POST', { status: 405 });
|
|
311
127
|
const page = pageOf(url);
|
|
@@ -338,7 +154,10 @@ const routes = {
|
|
|
338
154
|
...(programType ? { type: programType } : {}),
|
|
339
155
|
},
|
|
340
156
|
facets: filterFacets(url, [
|
|
341
|
-
{
|
|
157
|
+
{
|
|
158
|
+
param: 'state',
|
|
159
|
+
label: state ? _(`loyalty_backend.state.${state}`) : null,
|
|
160
|
+
},
|
|
342
161
|
{
|
|
343
162
|
param: 'type',
|
|
344
163
|
label: programType ? _(`loyalty_backend.programType.${programType}`) : null,
|
|
@@ -346,10 +165,26 @@ const routes = {
|
|
|
346
165
|
]),
|
|
347
166
|
menus: [
|
|
348
167
|
filterMenu(url, 'state', _('loyalty_backend.field.state'), 'state', state, [
|
|
349
|
-
{
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
168
|
+
{
|
|
169
|
+
value: 'draft',
|
|
170
|
+
label: _('loyalty_backend.state.draft'),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
value: 'running',
|
|
174
|
+
label: _('loyalty_backend.state.running'),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
value: 'upcoming',
|
|
178
|
+
label: _('loyalty_backend.state.upcoming'),
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
value: 'archived',
|
|
182
|
+
label: _('loyalty_backend.state.archived'),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
value: 'ended',
|
|
186
|
+
label: _('loyalty_backend.state.ended'),
|
|
187
|
+
},
|
|
353
188
|
]),
|
|
354
189
|
filterMenu(url, 'type', _('loyalty_backend.field.programType'), 'type', programType, options(_, PROGRAM_TYPES, 'programType')),
|
|
355
190
|
],
|
|
@@ -359,72 +194,8 @@ const routes = {
|
|
|
359
194
|
}, rows, totals, createProgramFields(_)),
|
|
360
195
|
});
|
|
361
196
|
},
|
|
362
|
-
'/admin/loyalty/programs/
|
|
363
|
-
|
|
364
|
-
if (req.method === 'POST') {
|
|
365
|
-
if (crossSite(req))
|
|
366
|
-
return text('Forbidden', { status: 403 });
|
|
367
|
-
const form = await readForm(req);
|
|
368
|
-
let result;
|
|
369
|
-
if (form.action === 'save-program')
|
|
370
|
-
result = await ctx.call('loyalty.program.save', programInput(params.id, form), url, req);
|
|
371
|
-
else if (form.action === 'archive' || form.action === 'restore')
|
|
372
|
-
result = await ctx.call('loyalty.program.archive', { id: params.id, active: form.action === 'restore' }, url, req);
|
|
373
|
-
else if (form.action === 'add-rule')
|
|
374
|
-
result = await ctx.call('loyalty.rule.save', {
|
|
375
|
-
id: randomUUID(),
|
|
376
|
-
programId: params.id,
|
|
377
|
-
priority: Number(form.priority || 10),
|
|
378
|
-
...optional(form, 'productId'),
|
|
379
|
-
pointAmount: form.pointAmount || '1',
|
|
380
|
-
pointMode: form.pointMode || 'order',
|
|
381
|
-
pointSplit: bool(form.pointSplit),
|
|
382
|
-
minimumQuantity: form.minimumQuantity || '1',
|
|
383
|
-
minimumAmount: form.minimumAmount || '0',
|
|
384
|
-
taxMode: form.taxMode || 'excl',
|
|
385
|
-
mode: form.mode || 'auto',
|
|
386
|
-
...optional(form, 'code'),
|
|
387
|
-
}, url, req);
|
|
388
|
-
else if (form.action === 'add-reward')
|
|
389
|
-
result = await ctx.call('loyalty.reward.save', {
|
|
390
|
-
id: randomUUID(),
|
|
391
|
-
programId: params.id,
|
|
392
|
-
description: form.description || '',
|
|
393
|
-
rewardType: form.rewardType || 'discount',
|
|
394
|
-
discount: form.discount || '0',
|
|
395
|
-
discountMode: form.discountMode || 'percent',
|
|
396
|
-
discountApplicability: form.discountApplicability || 'order',
|
|
397
|
-
...optional(form, 'discountMaximum'),
|
|
398
|
-
...optional(form, 'rewardProductId'),
|
|
399
|
-
rewardProductQuantity: form.rewardProductQuantity || '1',
|
|
400
|
-
requiredPoints: form.requiredPoints || '1',
|
|
401
|
-
clearWallet: bool(form.clearWallet),
|
|
402
|
-
}, url, req);
|
|
403
|
-
else
|
|
404
|
-
return text('unknown action', { status: 400 });
|
|
405
|
-
if (result.ok)
|
|
406
|
-
return seeOther(inLocale(url, url.pathname));
|
|
407
|
-
errors = resultErrors(result, ctx.translate(ctx.localeOf(url, req)));
|
|
408
|
-
}
|
|
409
|
-
else if (req.method !== 'GET')
|
|
410
|
-
return text('GET or POST', { status: 405 });
|
|
411
|
-
const [program, data] = await Promise.all([
|
|
412
|
-
ctx.call('loyalty.program.get', { id: params.id }, url, req),
|
|
413
|
-
dataFor(ctx, url, req),
|
|
414
|
-
]);
|
|
415
|
-
if (!program)
|
|
416
|
-
return text(ctx.translate(ctx.localeOf(url, req))('loyalty_backend.error.notFound'), { status: 404 });
|
|
417
|
-
return adminPage(ctx, url, req, {
|
|
418
|
-
title: 'loyalty_backend.program.detail',
|
|
419
|
-
body: (_, frame) => programDetailScreen(_, frame, program, {
|
|
420
|
-
programFields: programFields(_, program),
|
|
421
|
-
ruleFields: ruleFields(_, data.products),
|
|
422
|
-
rewardFields: rewardFields(_, data.products),
|
|
423
|
-
tab: url.searchParams.get('tab') ?? 'overview',
|
|
424
|
-
errors,
|
|
425
|
-
}),
|
|
426
|
-
});
|
|
427
|
-
},
|
|
197
|
+
'/admin/loyalty/programs/new': newProgramRoute,
|
|
198
|
+
'/admin/loyalty/programs/{id}': programWorkspaceRoute,
|
|
428
199
|
'/admin/loyalty/wallets': (ctx) => async (url, req) => {
|
|
429
200
|
const data = await dataFor(ctx, url, req);
|
|
430
201
|
let errors = [];
|
|
@@ -479,14 +250,29 @@ const routes = {
|
|
|
479
250
|
...(programId ? { program: programId } : {}),
|
|
480
251
|
},
|
|
481
252
|
facets: filterFacets(url, [
|
|
482
|
-
{
|
|
483
|
-
|
|
253
|
+
{
|
|
254
|
+
param: 'state',
|
|
255
|
+
label: state ? _(`loyalty_backend.state.${state}`) : null,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
param: 'program',
|
|
259
|
+
label: programId ? (programNames.get(programId) ?? null) : null,
|
|
260
|
+
},
|
|
484
261
|
]),
|
|
485
262
|
menus: [
|
|
486
263
|
filterMenu(url, 'state', _('loyalty_backend.field.state'), 'state', state, [
|
|
487
|
-
{
|
|
488
|
-
|
|
489
|
-
|
|
264
|
+
{
|
|
265
|
+
value: 'active',
|
|
266
|
+
label: _('loyalty_backend.state.running'),
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
value: 'locked',
|
|
270
|
+
label: _('loyalty_backend.state.locked'),
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
value: 'expired',
|
|
274
|
+
label: _('loyalty_backend.state.expired'),
|
|
275
|
+
},
|
|
490
276
|
]),
|
|
491
277
|
filterMenu(url, 'program', _('loyalty_backend.field.program'), 'program', programId, data.programs.map((program) => ({
|
|
492
278
|
value: String(program.id),
|
|
@@ -521,7 +307,11 @@ const routes = {
|
|
|
521
307
|
type: 'decimal',
|
|
522
308
|
value: 0,
|
|
523
309
|
},
|
|
524
|
-
{
|
|
310
|
+
{
|
|
311
|
+
name: 'expiresAt',
|
|
312
|
+
label: _('loyalty_backend.field.expiresAt'),
|
|
313
|
+
type: 'datetime-local',
|
|
314
|
+
},
|
|
525
315
|
], errors),
|
|
526
316
|
});
|
|
527
317
|
},
|
|
@@ -563,14 +353,24 @@ const routes = {
|
|
|
563
353
|
return adminPage(ctx, url, req, {
|
|
564
354
|
title: 'loyalty_backend.wallets.title',
|
|
565
355
|
body: (_, frame) => walletDetailScreen(_, frame, wallet, [
|
|
566
|
-
{
|
|
356
|
+
{
|
|
357
|
+
name: 'amount',
|
|
358
|
+
label: _('loyalty_backend.field.amount'),
|
|
359
|
+
type: 'decimal',
|
|
360
|
+
required: true,
|
|
361
|
+
},
|
|
567
362
|
{
|
|
568
363
|
name: 'sourceId',
|
|
569
364
|
label: _('loyalty_backend.field.sourceId'),
|
|
570
365
|
value: randomUUID(),
|
|
571
366
|
required: true,
|
|
572
367
|
},
|
|
573
|
-
{
|
|
368
|
+
{
|
|
369
|
+
name: 'note',
|
|
370
|
+
label: _('loyalty_backend.field.note'),
|
|
371
|
+
type: 'textarea',
|
|
372
|
+
span: 'full',
|
|
373
|
+
},
|
|
574
374
|
], url.searchParams.get('tab') ?? 'overview', errors),
|
|
575
375
|
});
|
|
576
376
|
},
|
|
@@ -616,13 +416,22 @@ const routes = {
|
|
|
616
416
|
...(walletId ? { wallet: walletId } : {}),
|
|
617
417
|
},
|
|
618
418
|
facets: filterFacets(url, [
|
|
619
|
-
{
|
|
419
|
+
{
|
|
420
|
+
param: 'period',
|
|
421
|
+
label: _(`loyalty_backend.period.${period}`),
|
|
422
|
+
},
|
|
620
423
|
{
|
|
621
424
|
param: 'operation',
|
|
622
425
|
label: operation ? _(`loyalty_backend.operation.${operation}`) : null,
|
|
623
426
|
},
|
|
624
|
-
{
|
|
625
|
-
|
|
427
|
+
{
|
|
428
|
+
param: 'program',
|
|
429
|
+
label: programId ? (programNames.get(programId) ?? null) : null,
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
param: 'wallet',
|
|
433
|
+
label: walletId ? (codes.get(walletId) ?? null) : null,
|
|
434
|
+
},
|
|
626
435
|
]),
|
|
627
436
|
menus: [
|
|
628
437
|
filterMenu(url, 'period', _('loyalty_backend.field.period'), 'period', period, PERIODS.map((value) => ({
|
|
@@ -638,148 +447,145 @@ const routes = {
|
|
|
638
447
|
},
|
|
639
448
|
pager: pager(url, page, rows.length, Number(totals.entries ?? 0)),
|
|
640
449
|
},
|
|
641
|
-
}, rows.map((row) => ({
|
|
450
|
+
}, rows.map((row) => ({
|
|
451
|
+
...row,
|
|
452
|
+
walletCode: codes.get(String(row.walletId)),
|
|
453
|
+
})), totals),
|
|
642
454
|
});
|
|
643
455
|
},
|
|
644
|
-
'/admin/loyalty/memberships': (
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
456
|
+
'/admin/loyalty/memberships': (_ctx) => async (url, req) => {
|
|
457
|
+
if (req.method !== 'GET')
|
|
458
|
+
return text('GET', { status: 405 });
|
|
459
|
+
return seeOther(inLocale(url, '/admin/loyalty/tiers'));
|
|
460
|
+
},
|
|
461
|
+
'/admin/loyalty/tiers': (ctx) => async (url, req) => {
|
|
462
|
+
if (!['GET', 'POST'].includes(req.method ?? ''))
|
|
463
|
+
return text('GET or POST', { status: 405 });
|
|
464
|
+
if (req.method === 'POST' && crossSite(req))
|
|
465
|
+
return text('Forbidden', { status: 403 });
|
|
466
|
+
const _ = ctx.translate(ctx.localeOf(url, req));
|
|
467
|
+
const refusal = formRefusal(_);
|
|
468
|
+
let submitted = {};
|
|
469
|
+
let submittedAction = '';
|
|
470
|
+
let tiers = (await ctx.call('loyalty.tier.list', { includeArchived: true }, url, req));
|
|
471
|
+
const baseHref = () => {
|
|
472
|
+
const target = new URL(inLocale(url, '/admin/loyalty/tiers'), url.origin);
|
|
473
|
+
return target.pathname + target.search;
|
|
474
|
+
};
|
|
648
475
|
if (req.method === 'POST') {
|
|
649
|
-
if (crossSite(req))
|
|
650
|
-
return text('Forbidden', { status: 403 });
|
|
651
476
|
const form = await readForm(req);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
477
|
+
submitted = form;
|
|
478
|
+
submittedAction = form.action ?? '';
|
|
479
|
+
let result = { ok: false };
|
|
480
|
+
if (submittedAction === 'tier') {
|
|
481
|
+
const checked = refusal.check(tierFormSchema, form);
|
|
482
|
+
if (checked) {
|
|
483
|
+
const existingTier = tiers.find((row) => String(row.id) === form.id);
|
|
484
|
+
result = (await ctx.call('loyalty.tier.save', {
|
|
485
|
+
id: form.id || randomUUID(),
|
|
486
|
+
name: checked.name,
|
|
487
|
+
code: checked.code,
|
|
488
|
+
sequence: checked.sequence ?? 10,
|
|
489
|
+
minimumSpend: checked.minimumSpend,
|
|
490
|
+
windowMonths: checked.windowMonths,
|
|
491
|
+
redeemPercent: String(existingTier?.redeemPercent ?? 100),
|
|
492
|
+
active: true,
|
|
493
|
+
}, url, req));
|
|
494
|
+
}
|
|
495
|
+
if (result.ok)
|
|
496
|
+
return seeOther(baseHref());
|
|
497
|
+
refusal.add(resultErrors(result, _));
|
|
498
|
+
}
|
|
499
|
+
else if (submittedAction === 'toggle') {
|
|
500
|
+
const tier = tiers.find((row) => String(row.id) === form.id);
|
|
501
|
+
if (!tier)
|
|
502
|
+
return text(_('loyalty_backend.error.notFound'), { status: 404 });
|
|
503
|
+
result = (await ctx.call('loyalty.tier.save', {
|
|
504
|
+
id: String(tier.id),
|
|
505
|
+
name: String(tier.name),
|
|
506
|
+
code: String(tier.code),
|
|
507
|
+
sequence: Number(tier.sequence ?? 10),
|
|
508
|
+
minimumSpend: String(tier.minimumSpend),
|
|
509
|
+
windowMonths: Number(tier.windowMonths ?? 12),
|
|
510
|
+
redeemPercent: String(tier.redeemPercent),
|
|
511
|
+
active: !tier.active,
|
|
512
|
+
}, url, req));
|
|
513
|
+
if (result.ok)
|
|
514
|
+
return seeOther(baseHref());
|
|
515
|
+
refusal.add(resultErrors(result, _));
|
|
516
|
+
}
|
|
517
|
+
else
|
|
518
|
+
return text('unknown action', { status: 400 });
|
|
519
|
+
tiers = (await ctx.call('loyalty.tier.list', { includeArchived: true }, url, req));
|
|
674
520
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
...(state ? { state } : {}),
|
|
684
|
-
...(tierId ? { tierId } : {}),
|
|
685
|
-
limit: PAGE_SIZE,
|
|
686
|
-
offset: (page - 1) * PAGE_SIZE,
|
|
687
|
-
}, url, req),
|
|
688
|
-
ctx.call('loyalty.membership.stats', { ...(tierId ? { tierId } : {}) }, url, req),
|
|
689
|
-
ctx.call('loyalty.membership.config.get', {}, url, req),
|
|
690
|
-
]);
|
|
691
|
-
const names = new Map(data.partners.map((partner) => [String(partner.id), String(partner.name)]));
|
|
692
|
-
const tierNames = new Map(tiers.map((tier) => [String(tier.id), String(tier.name)]));
|
|
693
|
-
return adminPage(ctx, url, req, {
|
|
694
|
-
title: 'loyalty_backend.memberships.title',
|
|
695
|
-
body: (_, frame) => membershipsScreen(_, {
|
|
696
|
-
...frame,
|
|
697
|
-
chrome: {
|
|
698
|
-
section: _('loyalty_backend.menu.app'),
|
|
699
|
-
search: {
|
|
700
|
-
name: 'q',
|
|
701
|
-
value: '',
|
|
702
|
-
placeholder: _('loyalty_backend.chrome.searchMembers'),
|
|
703
|
-
keep: { ...(state ? { state } : {}), ...(tierId ? { tier: tierId } : {}) },
|
|
704
|
-
facets: filterFacets(url, [
|
|
705
|
-
{ param: 'state', label: state ? _(`loyalty_backend.state.${state}`) : null },
|
|
706
|
-
{ param: 'tier', label: tierId ? (tierNames.get(tierId) ?? null) : null },
|
|
707
|
-
]),
|
|
708
|
-
menus: [
|
|
709
|
-
filterMenu(url, 'state', _('loyalty_backend.field.state'), 'state', state, [
|
|
710
|
-
{ value: 'active', label: _('loyalty_backend.state.active') },
|
|
711
|
-
{ value: 'dormant', label: _('loyalty_backend.state.dormant') },
|
|
712
|
-
]),
|
|
713
|
-
filterMenu(url, 'tier', _('loyalty_backend.field.tier'), 'tier', tierId, tiers.map((tier) => ({ value: String(tier.id), label: String(tier.name) }))),
|
|
714
|
-
],
|
|
715
|
-
},
|
|
716
|
-
pager: pager(url, page, memberships.length, Number(totals.total ?? 0)),
|
|
717
|
-
},
|
|
718
|
-
}, memberships.map((membership) => ({
|
|
719
|
-
...membership,
|
|
720
|
-
partnerName: names.get(String(membership.partnerId)),
|
|
721
|
-
})), totals, tiers, [
|
|
722
|
-
{ name: 'name', label: _('loyalty_backend.field.name'), required: true },
|
|
723
|
-
{ name: 'code', label: _('loyalty_backend.field.code'), required: true },
|
|
724
|
-
{ name: 'sequence', label: _('loyalty_backend.field.sequence'), type: 'number', value: 10 },
|
|
521
|
+
const tierId = url.searchParams.get('tier') ?? (submittedAction === 'tier' ? submitted.id : undefined);
|
|
522
|
+
const tier = tierId ? (tiers.find((row) => String(row.id) === tierId) ?? null) : null;
|
|
523
|
+
if (tierId && !tier && submittedAction !== 'tier')
|
|
524
|
+
return text(_('loyalty_backend.error.notFound'), { status: 404 });
|
|
525
|
+
const modal = url.searchParams.get('modal') === 'tier' || submittedAction === 'tier';
|
|
526
|
+
const held = (name, fallback, action) => submittedAction === action && Object.hasOwn(submitted, name) ? submitted[name] : fallback;
|
|
527
|
+
const tierFields = modal
|
|
528
|
+
? [
|
|
725
529
|
{
|
|
726
|
-
name: '
|
|
727
|
-
label: _('loyalty_backend.field.
|
|
728
|
-
|
|
729
|
-
value: 0,
|
|
530
|
+
name: 'name',
|
|
531
|
+
label: _('loyalty_backend.field.name'),
|
|
532
|
+
value: String(held('name', tier?.name ?? '', 'tier')),
|
|
730
533
|
required: true,
|
|
534
|
+
error: submittedAction === 'tier' ? refusal.error('name') : null,
|
|
731
535
|
},
|
|
732
536
|
{
|
|
733
|
-
name: '
|
|
734
|
-
label: _('loyalty_backend.field.
|
|
735
|
-
|
|
736
|
-
value: 100,
|
|
537
|
+
name: 'code',
|
|
538
|
+
label: _('loyalty_backend.field.code'),
|
|
539
|
+
value: String(held('code', tier?.code ?? '', 'tier')),
|
|
737
540
|
required: true,
|
|
541
|
+
error: submittedAction === 'tier' ? refusal.error('code') : null,
|
|
738
542
|
},
|
|
739
|
-
], [
|
|
740
543
|
{
|
|
741
|
-
name: '
|
|
742
|
-
label: _('loyalty_backend.field.
|
|
743
|
-
type: '
|
|
744
|
-
value: String(
|
|
745
|
-
options: choices(loyaltyPrograms),
|
|
544
|
+
name: 'minimumSpend',
|
|
545
|
+
label: _('loyalty_backend.field.minimumSpend'),
|
|
546
|
+
type: 'decimal',
|
|
547
|
+
value: String(held('minimumSpend', tier?.minimumSpend ?? 0, 'tier')),
|
|
746
548
|
required: true,
|
|
549
|
+
error: submittedAction === 'tier' ? refusal.error('minimumSpend') : null,
|
|
747
550
|
},
|
|
748
551
|
{
|
|
749
552
|
name: 'windowMonths',
|
|
750
553
|
label: _('loyalty_backend.field.windowMonths'),
|
|
751
554
|
type: 'number',
|
|
752
|
-
value:
|
|
555
|
+
value: String(held('windowMonths', tier?.windowMonths ?? 12, 'tier')),
|
|
753
556
|
required: true,
|
|
557
|
+
help: _('loyalty_backend.memberships.windowExamples'),
|
|
558
|
+
error: submittedAction === 'tier' ? refusal.error('windowMonths') : null,
|
|
754
559
|
},
|
|
755
560
|
{
|
|
756
|
-
name: '
|
|
757
|
-
label: _('loyalty_backend.field.
|
|
758
|
-
type: '
|
|
759
|
-
value: String(
|
|
760
|
-
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
name: 'minimumRedeemStep',
|
|
764
|
-
label: _('loyalty_backend.field.minimumRedeemStep'),
|
|
765
|
-
type: 'decimal',
|
|
766
|
-
value: String(config?.minimumRedeemStep ?? 1),
|
|
767
|
-
required: true,
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
name: 'fallbackCurrencyPerPoint',
|
|
771
|
-
label: _('loyalty_backend.field.fallbackCurrencyPerPoint'),
|
|
772
|
-
type: 'decimal',
|
|
773
|
-
value: String(config?.fallbackCurrencyPerPoint ?? 1),
|
|
774
|
-
required: true,
|
|
561
|
+
name: 'sequence',
|
|
562
|
+
label: _('loyalty_backend.field.sequence'),
|
|
563
|
+
type: 'number',
|
|
564
|
+
value: String(held('sequence', tier?.sequence ?? 10, 'tier')),
|
|
565
|
+
error: submittedAction === 'tier' ? refusal.error('sequence') : null,
|
|
775
566
|
},
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
567
|
+
]
|
|
568
|
+
: undefined;
|
|
569
|
+
const create = new URL(baseHref(), url.origin);
|
|
570
|
+
create.searchParams.set('modal', 'tier');
|
|
571
|
+
const closeHref = baseHref();
|
|
572
|
+
return adminPage(ctx, url, req, {
|
|
573
|
+
title: 'loyalty_backend.memberships.title',
|
|
574
|
+
body: (_, frame) => tiersScreen(_, frame, tiers, {
|
|
575
|
+
action: url.pathname + url.search,
|
|
576
|
+
closeHref,
|
|
577
|
+
createHref: create.pathname + create.search,
|
|
578
|
+
tierHref: (row) => {
|
|
579
|
+
const target = new URL(closeHref, url.origin);
|
|
580
|
+
target.searchParams.set('modal', 'tier');
|
|
581
|
+
target.searchParams.set('tier', String(row.id));
|
|
582
|
+
return target.pathname + target.search;
|
|
781
583
|
},
|
|
782
|
-
|
|
584
|
+
tierFields,
|
|
585
|
+
tierErrors: submittedAction === 'tier' ? refusal.sentences() : [],
|
|
586
|
+
tier,
|
|
587
|
+
modal,
|
|
588
|
+
}),
|
|
783
589
|
});
|
|
784
590
|
},
|
|
785
591
|
'/admin/loyalty/orders/{channel}/{id}': (ctx) => async (url, req, params) => {
|
|
@@ -795,7 +601,11 @@ const routes = {
|
|
|
795
601
|
if (form.action === 'code')
|
|
796
602
|
result = await ctx.call(`${prefix}.applyCode`, { orderId: params.id, code: form.code ?? '' }, url, req);
|
|
797
603
|
else if (form.action === 'reward')
|
|
798
|
-
result = await ctx.call(`${prefix}.applyReward`, {
|
|
604
|
+
result = await ctx.call(`${prefix}.applyReward`, {
|
|
605
|
+
orderId: params.id,
|
|
606
|
+
programId: form.programId ?? '',
|
|
607
|
+
rewardId: form.rewardId ?? '',
|
|
608
|
+
}, url, req);
|
|
799
609
|
else if (form.action === 'remove')
|
|
800
610
|
result = await ctx.call(`${prefix}.removeReward`, { orderId: params.id, programId: form.programId ?? '' }, url, req);
|
|
801
611
|
else
|
|
@@ -841,7 +651,9 @@ const routes = {
|
|
|
841
651
|
const user = (await ctx.call('user.getUser', { id: session.userId }, url, req));
|
|
842
652
|
const _ = ctx.translate(ctx.localeOf(url, req));
|
|
843
653
|
if (!user?.partnerId)
|
|
844
|
-
return text(_('loyalty_backend.error.partnerRequired'), {
|
|
654
|
+
return text(_('loyalty_backend.error.partnerRequired'), {
|
|
655
|
+
status: 403,
|
|
656
|
+
});
|
|
845
657
|
const summary = (await ctx.call('loyalty.portal.summary', { partnerId: user.partnerId }, url, req));
|
|
846
658
|
if (summary.ok !== true)
|
|
847
659
|
return text(resultErrors(summary, _).join('\n'), { status: 400 });
|
|
@@ -885,9 +697,9 @@ export default defineModule({
|
|
|
885
697
|
'loyalty.memberships': {
|
|
886
698
|
parent: 'loyalty',
|
|
887
699
|
label: 'menu.memberships',
|
|
888
|
-
path: '/admin/loyalty/
|
|
700
|
+
path: '/admin/loyalty/tiers',
|
|
889
701
|
sequence: 30,
|
|
890
|
-
needs: 'loyalty.
|
|
702
|
+
needs: 'loyalty.tier.list',
|
|
891
703
|
},
|
|
892
704
|
'loyalty.ledger': {
|
|
893
705
|
parent: 'loyalty',
|