@open-mercato/core 0.10.1-develop.7716.1.cefe7975df → 0.10.1-develop.7721.1.b5c4a00a68
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/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/staff_support_routing_rule/index.js +21 -0
- package/dist/generated/entities/staff_support_routing_rule/index.js.map +7 -0
- package/dist/generated/entities/staff_support_ticket/index.js +37 -0
- package/dist/generated/entities/staff_support_ticket/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project/index.js +2 -0
- package/dist/generated/entities/staff_time_project/index.js.map +2 -2
- package/dist/generated/entities/staff_time_project_member/index.js +2 -0
- package/dist/generated/entities/staff_time_project_member/index.js.map +2 -2
- package/dist/generated/entities/staff_time_task/index.js +4 -0
- package/dist/generated/entities/staff_time_task/index.js.map +2 -2
- package/dist/generated/entities/staff_time_task_comment/index.js +9 -1
- package/dist/generated/entities/staff_time_task_comment/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +2 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +38 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/auth.js +1 -0
- package/dist/helpers/integration/auth.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js +20 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
- package/dist/modules/catalog/api/bulk-delete/route.js +12 -2
- package/dist/modules/catalog/api/bulk-delete/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js +17 -3
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
- package/dist/modules/communication_channels/lib/test-seed.js +38 -5
- package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
- package/dist/modules/messages/commands/messages.js +2 -3
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
- package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js +299 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js.map +7 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js +91 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js.map +7 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/route.js +146 -0
- package/dist/modules/staff/api/portal/support-tickets/[id]/route.js.map +7 -0
- package/dist/modules/staff/api/portal/support-tickets/options/route.js +60 -0
- package/dist/modules/staff/api/portal/support-tickets/options/route.js.map +7 -0
- package/dist/modules/staff/api/portal/support-tickets/route.js +220 -0
- package/dist/modules/staff/api/portal/support-tickets/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/route.js +7 -1
- package/dist/modules/staff/api/timesheets/settings/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js +122 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js +180 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/route.js +147 -0
- package/dist/modules/staff/api/timesheets/support-routing-rules/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/support-tickets/options/route.js +133 -0
- package/dist/modules/staff/api/timesheets/support-tickets/options/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js +33 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +25 -7
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js +32 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js +94 -47
- package/dist/modules/staff/api/timesheets/tasks/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +3 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +3 -0
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +1 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +54 -4
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +21 -5
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +319 -254
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/support/page.js +363 -0
- package/dist/modules/staff/backend/staff/time-tracking/support/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js.map +7 -0
- package/dist/modules/staff/cli.js +27 -0
- package/dist/modules/staff/cli.js.map +2 -2
- package/dist/modules/staff/commands/index.js +3 -0
- package/dist/modules/staff/commands/index.js.map +2 -2
- package/dist/modules/staff/commands/shared.js +10 -1
- package/dist/modules/staff/commands/shared.js.map +2 -2
- package/dist/modules/staff/commands/support-routing-rules.js +505 -0
- package/dist/modules/staff/commands/support-routing-rules.js.map +7 -0
- package/dist/modules/staff/commands/support-tickets.js +513 -0
- package/dist/modules/staff/commands/support-tickets.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-projects.js +72 -14
- package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-task-assignment.js +270 -0
- package/dist/modules/staff/commands/timesheets-task-assignment.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js +58 -12
- package/dist/modules/staff/commands/timesheets-task-comments.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-tasks.js +91 -9
- package/dist/modules/staff/commands/timesheets-tasks.js.map +3 -3
- package/dist/modules/staff/data/enrichers.js +141 -0
- package/dist/modules/staff/data/enrichers.js.map +2 -2
- package/dist/modules/staff/data/entities.js +143 -2
- package/dist/modules/staff/data/entities.js.map +2 -2
- package/dist/modules/staff/data/extensions.js +8 -0
- package/dist/modules/staff/data/extensions.js.map +2 -2
- package/dist/modules/staff/data/validators.js +34 -5
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/encryption.js +6 -0
- package/dist/modules/staff/encryption.js.map +2 -2
- package/dist/modules/staff/events.js +25 -0
- package/dist/modules/staff/events.js.map +2 -2
- package/dist/modules/staff/events.payloads.js +54 -0
- package/dist/modules/staff/events.payloads.js.map +2 -2
- package/dist/modules/staff/extension-points.js +1 -0
- package/dist/modules/staff/extension-points.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js +468 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js +12 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js +316 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js +12 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js +190 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js +19 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js.map +7 -0
- package/dist/modules/staff/index.js +1 -1
- package/dist/modules/staff/index.js.map +2 -2
- package/dist/modules/staff/lib/crud.js +2 -0
- package/dist/modules/staff/lib/crud.js.map +2 -2
- package/dist/modules/staff/lib/support/attachments.js +36 -0
- package/dist/modules/staff/lib/support/attachments.js.map +7 -0
- package/dist/modules/staff/lib/support/dictionaries.js +87 -0
- package/dist/modules/staff/lib/support/dictionaries.js.map +7 -0
- package/dist/modules/staff/lib/support/notifications.js +111 -0
- package/dist/modules/staff/lib/support/notifications.js.map +7 -0
- package/dist/modules/staff/lib/support/portalAttachments.js +100 -0
- package/dist/modules/staff/lib/support/portalAttachments.js.map +7 -0
- package/dist/modules/staff/lib/support/portalBroadcast.js +117 -0
- package/dist/modules/staff/lib/support/portalBroadcast.js.map +7 -0
- package/dist/modules/staff/lib/support/portalRoute.js +99 -0
- package/dist/modules/staff/lib/support/portalRoute.js.map +7 -0
- package/dist/modules/staff/lib/support/portalStatus.js +12 -0
- package/dist/modules/staff/lib/support/portalStatus.js.map +7 -0
- package/dist/modules/staff/lib/support/portalTickets.js +148 -0
- package/dist/modules/staff/lib/support/portalTickets.js.map +7 -0
- package/dist/modules/staff/lib/support/routing.js +81 -0
- package/dist/modules/staff/lib/support/routing.js.map +7 -0
- package/dist/modules/staff/lib/support/routingRuleDraft.js +25 -0
- package/dist/modules/staff/lib/support/routingRuleDraft.js.map +7 -0
- package/dist/modules/staff/lib/support/seedDefaults.js +114 -0
- package/dist/modules/staff/lib/support/seedDefaults.js.map +7 -0
- package/dist/modules/staff/lib/support/supportActivity.js +26 -0
- package/dist/modules/staff/lib/support/supportActivity.js.map +7 -0
- package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js +23 -0
- package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js.map +7 -0
- package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js +112 -0
- package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js.map +7 -0
- package/dist/modules/staff/lib/support-ui/portalPendingUploads.js +45 -0
- package/dist/modules/staff/lib/support-ui/portalPendingUploads.js.map +7 -0
- package/dist/modules/staff/lib/support-ui/portalSupport.js +51 -0
- package/dist/modules/staff/lib/support-ui/portalSupport.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/access.js +3 -39
- package/dist/modules/staff/lib/time-tracking/access.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/listProjectAccess.js +54 -0
- package/dist/modules/staff/lib/time-tracking/listProjectAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js +92 -2
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/projectAssignment.js +112 -0
- package/dist/modules/staff/lib/time-tracking/projectAssignment.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js +14 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/settings.js +28 -1
- package/dist/modules/staff/lib/time-tracking/settings.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js +143 -0
- package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/workRecipients.js +92 -0
- package/dist/modules/staff/lib/time-tracking/workRecipients.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +32 -3
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +29 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +4 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +141 -1
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js +153 -0
- package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js +67 -0
- package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +3 -1
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +80 -17
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +230 -41
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +21 -1
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +16 -4
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +3 -1
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js +68 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js +50 -0
- package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +13 -1
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +36 -2
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +2 -2
- package/dist/modules/staff/migrations/Migration20261009180543_staff.js +42 -0
- package/dist/modules/staff/migrations/Migration20261009180543_staff.js.map +7 -0
- package/dist/modules/staff/notifications.js +92 -0
- package/dist/modules/staff/notifications.js.map +2 -2
- package/dist/modules/staff/setup.js +6 -2
- package/dist/modules/staff/setup.js.map +2 -2
- package/dist/modules/staff/subscribers/support-portal-comment-created.js +26 -0
- package/dist/modules/staff/subscribers/support-portal-comment-created.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-comment-deleted.js +26 -0
- package/dist/modules/staff/subscribers/support-portal-comment-deleted.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-comment-updated.js +26 -0
- package/dist/modules/staff/subscribers/support-portal-comment-updated.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-status-column-updated.js +24 -0
- package/dist/modules/staff/subscribers/support-portal-status-column-updated.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-task-status-changed.js +25 -0
- package/dist/modules/staff/subscribers/support-portal-task-status-changed.js.map +7 -0
- package/dist/modules/staff/subscribers/support-portal-task-updated.js +25 -0
- package/dist/modules/staff/subscribers/support-portal-task-updated.js.map +7 -0
- package/dist/modules/staff/subscribers/support-ticket-created-notification.js +25 -0
- package/dist/modules/staff/subscribers/support-ticket-created-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js +25 -0
- package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js +26 -0
- package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js.map +7 -0
- package/dist/modules/staff/subscribers/time-task-assignee-notification.js +83 -0
- package/dist/modules/staff/subscribers/time-task-assignee-notification.js.map +7 -0
- package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js +112 -0
- package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js.map +7 -0
- package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js +17 -0
- package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js.map +7 -0
- package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js +870 -0
- package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js.map +7 -0
- package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js +17 -0
- package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js.map +7 -0
- package/dist/modules/staff/widgets/injection-table.js +16 -1
- package/dist/modules/staff/widgets/injection-table.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +4 -18
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js +4 -18
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +4 -18
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
- package/dist/modules/wms/lib/inventoryCsvExport.js +19 -0
- package/dist/modules/wms/lib/inventoryCsvExport.js.map +7 -0
- package/generated/entities/staff_support_routing_rule/index.ts +9 -0
- package/generated/entities/staff_support_ticket/index.ts +17 -0
- package/generated/entities/staff_time_project/index.ts +1 -0
- package/generated/entities/staff_time_project_member/index.ts +1 -0
- package/generated/entities/staff_time_task/index.ts +2 -0
- package/generated/entities/staff_time_task_comment/index.ts +4 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +38 -0
- package/package.json +8 -8
- package/src/helpers/integration/auth.ts +1 -1
- package/src/helpers/integration/communicationChannelsFixtures.ts +60 -2
- package/src/modules/catalog/api/bulk-delete/route.ts +12 -2
- package/src/modules/communication_channels/api/post/test-seed/route.ts +29 -12
- package/src/modules/communication_channels/lib/test-seed.ts +49 -10
- package/src/modules/messages/commands/messages.ts +8 -10
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +2 -2
- package/src/modules/staff/AGENTS.md +22 -6
- package/src/modules/staff/api/portal/support-tickets/[id]/attachments/route.ts +327 -0
- package/src/modules/staff/api/portal/support-tickets/[id]/messages/route.ts +94 -0
- package/src/modules/staff/api/portal/support-tickets/[id]/route.ts +160 -0
- package/src/modules/staff/api/portal/support-tickets/options/route.ts +60 -0
- package/src/modules/staff/api/portal/support-tickets/route.ts +253 -0
- package/src/modules/staff/api/timesheets/settings/route.ts +7 -1
- package/src/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.ts +131 -0
- package/src/modules/staff/api/timesheets/support-routing-rules/preview/route.ts +248 -0
- package/src/modules/staff/api/timesheets/support-routing-rules/route.ts +150 -0
- package/src/modules/staff/api/timesheets/support-tickets/options/route.ts +181 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/claim/route.ts +31 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +25 -7
- package/src/modules/staff/api/timesheets/tasks/[id]/release/route.ts +30 -0
- package/src/modules/staff/api/timesheets/tasks/route.ts +119 -67
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +3 -0
- package/src/modules/staff/api/timesheets/time-projects/route.ts +3 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +1 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +67 -2
- package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +22 -3
- package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +405 -308
- package/src/modules/staff/backend/staff/time-tracking/support/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/support/page.tsx +428 -0
- package/src/modules/staff/cli.ts +28 -0
- package/src/modules/staff/commands/index.ts +3 -0
- package/src/modules/staff/commands/shared.ts +22 -1
- package/src/modules/staff/commands/support-routing-rules.ts +595 -0
- package/src/modules/staff/commands/support-tickets.ts +580 -0
- package/src/modules/staff/commands/timesheets-projects.ts +84 -13
- package/src/modules/staff/commands/timesheets-task-assignment.ts +319 -0
- package/src/modules/staff/commands/timesheets-task-comments.ts +70 -15
- package/src/modules/staff/commands/timesheets-tasks.ts +106 -15
- package/src/modules/staff/data/enrichers.ts +203 -0
- package/src/modules/staff/data/entities.ts +133 -2
- package/src/modules/staff/data/extensions.ts +9 -1
- package/src/modules/staff/data/validators.ts +36 -0
- package/src/modules/staff/encryption.ts +6 -0
- package/src/modules/staff/events.payloads.ts +87 -1
- package/src/modules/staff/events.ts +26 -0
- package/src/modules/staff/extension-points.ts +1 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.tsx +553 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.tsx +346 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/page.meta.ts +17 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/support/page.tsx +253 -0
- package/src/modules/staff/i18n/de.json +291 -0
- package/src/modules/staff/i18n/en.json +291 -0
- package/src/modules/staff/i18n/es.json +291 -0
- package/src/modules/staff/i18n/ko.json +291 -0
- package/src/modules/staff/i18n/pl.json +291 -0
- package/src/modules/staff/index.ts +1 -1
- package/src/modules/staff/lib/crud.ts +2 -1
- package/src/modules/staff/lib/support/attachments.ts +43 -0
- package/src/modules/staff/lib/support/dictionaries.ts +120 -0
- package/src/modules/staff/lib/support/notifications.ts +138 -0
- package/src/modules/staff/lib/support/portalAttachments.ts +140 -0
- package/src/modules/staff/lib/support/portalBroadcast.ts +161 -0
- package/src/modules/staff/lib/support/portalRoute.ts +108 -0
- package/src/modules/staff/lib/support/portalStatus.ts +15 -0
- package/src/modules/staff/lib/support/portalTickets.ts +233 -0
- package/src/modules/staff/lib/support/routing.ts +117 -0
- package/src/modules/staff/lib/support/routingRuleDraft.ts +45 -0
- package/src/modules/staff/lib/support/seedDefaults.ts +151 -0
- package/src/modules/staff/lib/support/supportActivity.ts +39 -0
- package/src/modules/staff/lib/support-ui/PortalSupportNotLinked.tsx +20 -0
- package/src/modules/staff/lib/support-ui/portalAttachmentValidation.ts +125 -0
- package/src/modules/staff/lib/support-ui/portalPendingUploads.ts +49 -0
- package/src/modules/staff/lib/support-ui/portalSupport.ts +116 -0
- package/src/modules/staff/lib/time-tracking/access.ts +2 -62
- package/src/modules/staff/lib/time-tracking/listProjectAccess.ts +78 -0
- package/src/modules/staff/lib/time-tracking/portalRecipients.ts +173 -5
- package/src/modules/staff/lib/time-tracking/projectAssignment.ts +172 -0
- package/src/modules/staff/lib/time-tracking/settingKeys.ts +11 -0
- package/src/modules/staff/lib/time-tracking/settings.ts +46 -1
- package/src/modules/staff/lib/time-tracking/taskAssignmentRoute.ts +165 -0
- package/src/modules/staff/lib/time-tracking/workRecipients.ts +183 -0
- package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +36 -1
- package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +30 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +9 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +185 -6
- package/src/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.tsx +188 -0
- package/src/modules/staff/lib/time-tracking-ui/SupportRequestPanel.tsx +102 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +2 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +105 -22
- package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +278 -43
- package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +21 -0
- package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +16 -2
- package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +4 -0
- package/src/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.ts +117 -0
- package/src/modules/staff/lib/time-tracking-ui/settingsTabs.ts +17 -0
- package/src/modules/staff/lib/time-tracking-ui/supportTicketData.ts +71 -0
- package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +15 -0
- package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +48 -1
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +714 -0
- package/src/modules/staff/migrations/Migration20261009180543_staff.ts +50 -0
- package/src/modules/staff/notifications.ts +92 -0
- package/src/modules/staff/setup.ts +6 -2
- package/src/modules/staff/subscribers/support-portal-comment-created.ts +35 -0
- package/src/modules/staff/subscribers/support-portal-comment-deleted.ts +35 -0
- package/src/modules/staff/subscribers/support-portal-comment-updated.ts +35 -0
- package/src/modules/staff/subscribers/support-portal-status-column-updated.ts +36 -0
- package/src/modules/staff/subscribers/support-portal-task-status-changed.ts +34 -0
- package/src/modules/staff/subscribers/support-portal-task-updated.ts +34 -0
- package/src/modules/staff/subscribers/support-ticket-created-notification.ts +27 -0
- package/src/modules/staff/subscribers/support-ticket-customer-replied-notification.ts +27 -0
- package/src/modules/staff/subscribers/support-ticket-unnotified-clear.ts +29 -0
- package/src/modules/staff/subscribers/time-task-assignee-notification.ts +102 -0
- package/src/modules/staff/widgets/injection/portal-support-tickets/widget.client.tsx +151 -0
- package/src/modules/staff/widgets/injection/portal-support-tickets/widget.ts +16 -0
- package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.tsx +1058 -0
- package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.ts +17 -0
- package/src/modules/staff/widgets/injection-table.ts +15 -0
- package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +4 -20
- package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +4 -20
- package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +4 -19
- package/src/modules/wms/lib/inventoryCsvExport.ts +18 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
|
+
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
8
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
9
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
10
|
+
import { StaffTimeProject } from "../../../../data/entities.js";
|
|
11
|
+
import { resolveAssignmentGraceDaysFromContainer } from "../../../../lib/time-tracking/projectAssignment.js";
|
|
12
|
+
import { projectReachesRecipientBeforeManagers } from "../../../../lib/time-tracking/workRecipients.js";
|
|
13
|
+
import { loadSupportTicketCategories } from "../../../../lib/support/dictionaries.js";
|
|
14
|
+
import {
|
|
15
|
+
loadSupportRoutingRules,
|
|
16
|
+
loadUsableSupportProjectIds,
|
|
17
|
+
resolveSupportRouteFromRules
|
|
18
|
+
} from "../../../../lib/support/routing.js";
|
|
19
|
+
const logger = createLogger("staff").child({ component: "api/timesheets/support-routing-rules/preview" });
|
|
20
|
+
const metadata = {
|
|
21
|
+
GET: { requireAuth: true, requireFeatures: ["staff.timesheets.settings.manage"] }
|
|
22
|
+
};
|
|
23
|
+
async function countUnnotifiedTicketsByProject(em, scope) {
|
|
24
|
+
const rows = await em.getKysely().selectFrom("staff_support_tickets").innerJoin("staff_time_tasks", "staff_time_tasks.id", "staff_support_tickets.task_id").select(["staff_time_tasks.time_project_id as time_project_id"]).select((builder) => builder.fn.countAll().as("ticket_count")).where("staff_support_tickets.tenant_id", "=", scope.tenantId).where("staff_support_tickets.organization_id", "=", scope.organizationId).where("staff_support_tickets.deleted_at", "is", null).where("staff_support_tickets.unnotified_at", "is not", null).where("staff_time_tasks.tenant_id", "=", scope.tenantId).where("staff_time_tasks.organization_id", "=", scope.organizationId).where("staff_time_tasks.source", "=", "portal").where("staff_time_tasks.deleted_at", "is", null).groupBy("staff_time_tasks.time_project_id").execute();
|
|
25
|
+
const counts = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
const count = Number(row.ticket_count);
|
|
28
|
+
if (Number.isFinite(count) && count > 0) counts.set(row.time_project_id, count);
|
|
29
|
+
}
|
|
30
|
+
return counts;
|
|
31
|
+
}
|
|
32
|
+
async function GET(req) {
|
|
33
|
+
try {
|
|
34
|
+
const container = await createRequestContainer();
|
|
35
|
+
const auth = await getAuthFromRequest(req);
|
|
36
|
+
const { translate } = await resolveTranslations();
|
|
37
|
+
if (!auth) throw new CrudHttpError(401, { error: translate("staff.errors.unauthorized", "Unauthorized") });
|
|
38
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
39
|
+
const tenantId = scope?.tenantId ?? auth.tenantId ?? null;
|
|
40
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null;
|
|
41
|
+
if (!tenantId || !organizationId) {
|
|
42
|
+
throw new CrudHttpError(400, { error: translate("staff.errors.missingScope", "Missing tenant or organization scope.") });
|
|
43
|
+
}
|
|
44
|
+
const supportScope = { tenantId, organizationId };
|
|
45
|
+
const em = container.resolve("em").fork();
|
|
46
|
+
const [categories, rules] = await Promise.all([
|
|
47
|
+
loadSupportTicketCategories(em, supportScope),
|
|
48
|
+
loadSupportRoutingRules(em, supportScope)
|
|
49
|
+
]);
|
|
50
|
+
const projectIds = rules.map((rule) => rule.timeProjectId);
|
|
51
|
+
const usable = await loadUsableSupportProjectIds(em, supportScope, projectIds);
|
|
52
|
+
const projects = projectIds.length ? await em.find(
|
|
53
|
+
StaffTimeProject,
|
|
54
|
+
{ id: { $in: Array.from(new Set(projectIds)) }, tenantId, organizationId },
|
|
55
|
+
{ fields: ["id", "name", "code", "status", "deletedAt", "ownerUserId", "defaultAssigneeStaffMemberId"] }
|
|
56
|
+
) : [];
|
|
57
|
+
const projectById = new Map(projects.map((project) => [project.id, project]));
|
|
58
|
+
const graceDays = await resolveAssignmentGraceDaysFromContainer(container, tenantId);
|
|
59
|
+
const [unnotifiedByProject, reachability] = await Promise.all([
|
|
60
|
+
countUnnotifiedTicketsByProject(em.fork(), supportScope),
|
|
61
|
+
Promise.all(
|
|
62
|
+
projects.map(async (project) => {
|
|
63
|
+
if (project.deletedAt) return [project.id, true];
|
|
64
|
+
const reaches = await projectReachesRecipientBeforeManagers(em.fork(), supportScope, project, { graceDays });
|
|
65
|
+
return [project.id, reaches];
|
|
66
|
+
})
|
|
67
|
+
)
|
|
68
|
+
]);
|
|
69
|
+
const reachesById = new Map(reachability);
|
|
70
|
+
let unnotifiedTicketCount = 0;
|
|
71
|
+
for (const count of unnotifiedByProject.values()) unnotifiedTicketCount += count;
|
|
72
|
+
const categoryValues = new Set(categories.map((category) => category.value));
|
|
73
|
+
const fallbackRule = rules.find((rule) => rule.categoryValue === null) ?? null;
|
|
74
|
+
const describeProject = (id) => {
|
|
75
|
+
const project = projectById.get(id);
|
|
76
|
+
return {
|
|
77
|
+
id,
|
|
78
|
+
name: project?.name ?? null,
|
|
79
|
+
code: project?.code ?? null,
|
|
80
|
+
status: project && !project.deletedAt ? project.status : "deleted",
|
|
81
|
+
usable: usable.has(id),
|
|
82
|
+
nobodyNotified: usable.has(id) && reachesById.get(id) === false,
|
|
83
|
+
unnotifiedTicketCount: unnotifiedByProject.get(id) ?? 0
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const ruleVersion = (rule) => rule.updatedAt instanceof Date ? rule.updatedAt.toISOString() : null;
|
|
87
|
+
const ruleViews = rules.map((rule) => {
|
|
88
|
+
let problem = null;
|
|
89
|
+
if (rule.categoryValue !== null) {
|
|
90
|
+
if (!categoryValues.has(rule.categoryValue)) problem = "category_missing";
|
|
91
|
+
else if (!rule.isActive) problem = "paused";
|
|
92
|
+
else if (!usable.has(rule.timeProjectId)) problem = "project_unusable";
|
|
93
|
+
} else if (!usable.has(rule.timeProjectId)) {
|
|
94
|
+
problem = "project_unusable";
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
id: rule.id,
|
|
98
|
+
categoryValue: rule.categoryValue,
|
|
99
|
+
isActive: rule.isActive,
|
|
100
|
+
isFallback: rule.categoryValue === null,
|
|
101
|
+
project: describeProject(rule.timeProjectId),
|
|
102
|
+
problem,
|
|
103
|
+
updatedAt: ruleVersion(rule)
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
const categoryViews = categories.map((category) => {
|
|
107
|
+
const route = resolveSupportRouteFromRules(rules, category.value, usable);
|
|
108
|
+
return {
|
|
109
|
+
value: category.value,
|
|
110
|
+
label: category.label,
|
|
111
|
+
route: route ? { via: route.via, project: describeProject(route.timeProjectId) } : null
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
return NextResponse.json({
|
|
115
|
+
fallback: fallbackRule ? { ruleId: fallbackRule.id, project: describeProject(fallbackRule.timeProjectId), updatedAt: ruleVersion(fallbackRule) } : null,
|
|
116
|
+
rules: ruleViews,
|
|
117
|
+
categories: categoryViews,
|
|
118
|
+
unnotifiedTicketCount
|
|
119
|
+
});
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
122
|
+
const { translate } = await resolveTranslations();
|
|
123
|
+
logger.error("staff.support.routing preview failed", { err });
|
|
124
|
+
getTelemetryRuntime()?.reportError(err, { module: "staff", code: "staff.support_routing_preview_failed" });
|
|
125
|
+
return NextResponse.json({ error: translate("staff.errors.internal", "Internal server error") }, { status: 500 });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const projectSchema = z.object({
|
|
129
|
+
id: z.string().uuid(),
|
|
130
|
+
name: z.string().nullable(),
|
|
131
|
+
code: z.string().nullable(),
|
|
132
|
+
status: z.string(),
|
|
133
|
+
usable: z.boolean(),
|
|
134
|
+
nobodyNotified: z.boolean().optional().describe("True for a usable project with no active assigner, no usable default assignee and no owner: new-ticket notifications fall through to the organization project managers"),
|
|
135
|
+
unnotifiedTicketCount: z.number().int().optional().describe("Live portal tickets in this project whose support notification reached nobody (`unnotified_at` set)")
|
|
136
|
+
});
|
|
137
|
+
const ruleVersionSchema = z.string().nullable().describe("The rule `updated_at` read in the same snapshot as its values; send it as the optimistic-lock expected version when editing or deleting the rule");
|
|
138
|
+
const previewSchema = z.object({
|
|
139
|
+
fallback: z.object({ ruleId: z.string().uuid(), project: projectSchema, updatedAt: ruleVersionSchema }).nullable(),
|
|
140
|
+
rules: z.array(
|
|
141
|
+
z.object({
|
|
142
|
+
id: z.string().uuid(),
|
|
143
|
+
categoryValue: z.string().nullable(),
|
|
144
|
+
isActive: z.boolean(),
|
|
145
|
+
isFallback: z.boolean(),
|
|
146
|
+
project: projectSchema,
|
|
147
|
+
problem: z.enum(["paused", "project_unusable", "category_missing"]).nullable(),
|
|
148
|
+
updatedAt: ruleVersionSchema
|
|
149
|
+
})
|
|
150
|
+
),
|
|
151
|
+
categories: z.array(
|
|
152
|
+
z.object({
|
|
153
|
+
value: z.string(),
|
|
154
|
+
label: z.string(),
|
|
155
|
+
route: z.object({ via: z.enum(["category", "fallback"]), project: projectSchema }).nullable()
|
|
156
|
+
})
|
|
157
|
+
),
|
|
158
|
+
unnotifiedTicketCount: z.number().int().optional().describe("Live portal tickets in the organization whose support notification reached nobody (`unnotified_at` set)")
|
|
159
|
+
});
|
|
160
|
+
const openApi = {
|
|
161
|
+
tag: "Staff",
|
|
162
|
+
summary: "Preview support ticket routing",
|
|
163
|
+
methods: {
|
|
164
|
+
GET: {
|
|
165
|
+
summary: "Preview support ticket routing",
|
|
166
|
+
description: "Returns the fallback rule, every routing rule with the problem that makes it fall back (paused, unusable project, deleted category), the project each support category routes to right now, which routing targets would notify nobody before the organization project managers (`project.nobodyNotified`), how many live tickets were not notified to anyone (`project.unnotifiedTicketCount`, `unnotifiedTicketCount`), and each rule `updatedAt` version token read together with its values.",
|
|
167
|
+
responses: [{ status: 200, description: "Routing preview", schema: previewSchema }],
|
|
168
|
+
errors: [
|
|
169
|
+
{ status: 401, description: "Unauthorized" },
|
|
170
|
+
{ status: 403, description: "Missing staff.timesheets.settings.manage" }
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
export {
|
|
176
|
+
GET,
|
|
177
|
+
metadata,
|
|
178
|
+
openApi
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/staff/api/timesheets/support-routing-rules/preview/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeProject } from '../../../../data/entities'\nimport { resolveAssignmentGraceDaysFromContainer } from '../../../../lib/time-tracking/projectAssignment'\nimport { projectReachesRecipientBeforeManagers } from '../../../../lib/time-tracking/workRecipients'\nimport { loadSupportTicketCategories } from '../../../../lib/support/dictionaries'\nimport {\n loadSupportRoutingRules,\n loadUsableSupportProjectIds,\n resolveSupportRouteFromRules,\n type SupportRoutingRuleView,\n} from '../../../../lib/support/routing'\n\nconst logger = createLogger('staff').child({ component: 'api/timesheets/support-routing-rules/preview' })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['staff.timesheets.settings.manage'] },\n}\n\ntype RuleProblem = 'paused' | 'project_unusable' | 'category_missing' | null\n\ntype UnnotifiedCountDb = {\n staff_support_tickets: {\n task_id: string\n tenant_id: string\n organization_id: string\n unnotified_at: Date | null\n deleted_at: Date | null\n }\n staff_time_tasks: {\n id: string\n tenant_id: string\n organization_id: string\n time_project_id: string\n source: string\n deleted_at: Date | null\n }\n}\n\nasync function countUnnotifiedTicketsByProject(\n em: EntityManager,\n scope: { tenantId: string; organizationId: string },\n): Promise<Map<string, number>> {\n const rows = await em\n .getKysely<UnnotifiedCountDb>()\n .selectFrom('staff_support_tickets')\n .innerJoin('staff_time_tasks', 'staff_time_tasks.id', 'staff_support_tickets.task_id')\n .select(['staff_time_tasks.time_project_id as time_project_id'])\n .select((builder) => builder.fn.countAll<string | number>().as('ticket_count'))\n .where('staff_support_tickets.tenant_id', '=', scope.tenantId)\n .where('staff_support_tickets.organization_id', '=', scope.organizationId)\n .where('staff_support_tickets.deleted_at', 'is', null)\n .where('staff_support_tickets.unnotified_at', 'is not', null)\n .where('staff_time_tasks.tenant_id', '=', scope.tenantId)\n .where('staff_time_tasks.organization_id', '=', scope.organizationId)\n .where('staff_time_tasks.source', '=', 'portal')\n .where('staff_time_tasks.deleted_at', 'is', null)\n .groupBy('staff_time_tasks.time_project_id')\n .execute()\n const counts = new Map<string, number>()\n for (const row of rows) {\n const count = Number(row.ticket_count)\n if (Number.isFinite(count) && count > 0) counts.set(row.time_project_id, count)\n }\n return counts\n}\n\nexport async function GET(req: Request): Promise<Response> {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') })\n }\n const supportScope = { tenantId, organizationId }\n const em = (container.resolve('em') as EntityManager).fork()\n const [categories, rules] = await Promise.all([\n loadSupportTicketCategories(em, supportScope),\n loadSupportRoutingRules(em, supportScope),\n ])\n const projectIds = rules.map((rule) => rule.timeProjectId)\n const usable = await loadUsableSupportProjectIds(em, supportScope, projectIds)\n const projects = projectIds.length\n ? await em.find(\n StaffTimeProject,\n { id: { $in: Array.from(new Set(projectIds)) }, tenantId, organizationId },\n { fields: ['id', 'name', 'code', 'status', 'deletedAt', 'ownerUserId', 'defaultAssigneeStaffMemberId'] },\n )\n : []\n const projectById = new Map(projects.map((project) => [project.id, project]))\n const graceDays = await resolveAssignmentGraceDaysFromContainer(container, tenantId)\n const [unnotifiedByProject, reachability] = await Promise.all([\n countUnnotifiedTicketsByProject(em.fork(), supportScope),\n Promise.all(\n projects.map(async (project) => {\n if (project.deletedAt) return [project.id, true] as const\n const reaches = await projectReachesRecipientBeforeManagers(em.fork(), supportScope, project, { graceDays })\n return [project.id, reaches] as const\n }),\n ),\n ])\n const reachesById = new Map<string, boolean>(reachability)\n let unnotifiedTicketCount = 0\n for (const count of unnotifiedByProject.values()) unnotifiedTicketCount += count\n const categoryValues = new Set(categories.map((category) => category.value))\n const fallbackRule = rules.find((rule) => rule.categoryValue === null) ?? null\n\n const describeProject = (id: string) => {\n const project = projectById.get(id)\n return {\n id,\n name: project?.name ?? null,\n code: project?.code ?? null,\n status: project && !project.deletedAt ? project.status : 'deleted',\n usable: usable.has(id),\n nobodyNotified: usable.has(id) && reachesById.get(id) === false,\n unnotifiedTicketCount: unnotifiedByProject.get(id) ?? 0,\n }\n }\n\n const ruleVersion = (rule: SupportRoutingRuleView): string | null =>\n rule.updatedAt instanceof Date ? rule.updatedAt.toISOString() : null\n\n const ruleViews = rules.map((rule) => {\n let problem: RuleProblem = null\n if (rule.categoryValue !== null) {\n if (!categoryValues.has(rule.categoryValue)) problem = 'category_missing'\n else if (!rule.isActive) problem = 'paused'\n else if (!usable.has(rule.timeProjectId)) problem = 'project_unusable'\n } else if (!usable.has(rule.timeProjectId)) {\n problem = 'project_unusable'\n }\n return {\n id: rule.id,\n categoryValue: rule.categoryValue,\n isActive: rule.isActive,\n isFallback: rule.categoryValue === null,\n project: describeProject(rule.timeProjectId),\n problem,\n updatedAt: ruleVersion(rule),\n }\n })\n\n const categoryViews = categories.map((category) => {\n const route = resolveSupportRouteFromRules(rules, category.value, usable)\n return {\n value: category.value,\n label: category.label,\n route: route ? { via: route.via, project: describeProject(route.timeProjectId) } : null,\n }\n })\n\n return NextResponse.json({\n fallback: fallbackRule\n ? { ruleId: fallbackRule.id, project: describeProject(fallbackRule.timeProjectId), updatedAt: ruleVersion(fallbackRule) }\n : null,\n rules: ruleViews,\n categories: categoryViews,\n unnotifiedTicketCount,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const { translate } = await resolveTranslations()\n logger.error('staff.support.routing preview failed', { err })\n getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_routing_preview_failed' })\n return NextResponse.json({ error: translate('staff.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n\nconst projectSchema = z.object({\n id: z.string().uuid(),\n name: z.string().nullable(),\n code: z.string().nullable(),\n status: z.string(),\n usable: z.boolean(),\n nobodyNotified: z\n .boolean()\n .optional()\n .describe('True for a usable project with no active assigner, no usable default assignee and no owner: new-ticket notifications fall through to the organization project managers'),\n unnotifiedTicketCount: z\n .number()\n .int()\n .optional()\n .describe('Live portal tickets in this project whose support notification reached nobody (`unnotified_at` set)'),\n})\n\nconst ruleVersionSchema = z\n .string()\n .nullable()\n .describe('The rule `updated_at` read in the same snapshot as its values; send it as the optimistic-lock expected version when editing or deleting the rule')\n\nconst previewSchema = z.object({\n fallback: z.object({ ruleId: z.string().uuid(), project: projectSchema, updatedAt: ruleVersionSchema }).nullable(),\n rules: z.array(\n z.object({\n id: z.string().uuid(),\n categoryValue: z.string().nullable(),\n isActive: z.boolean(),\n isFallback: z.boolean(),\n project: projectSchema,\n problem: z.enum(['paused', 'project_unusable', 'category_missing']).nullable(),\n updatedAt: ruleVersionSchema,\n }),\n ),\n categories: z.array(\n z.object({\n value: z.string(),\n label: z.string(),\n route: z.object({ via: z.enum(['category', 'fallback']), project: projectSchema }).nullable(),\n }),\n ),\n unnotifiedTicketCount: z\n .number()\n .int()\n .optional()\n .describe('Live portal tickets in the organization whose support notification reached nobody (`unnotified_at` set)'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Preview support ticket routing',\n methods: {\n GET: {\n summary: 'Preview support ticket routing',\n description:\n 'Returns the fallback rule, every routing rule with the problem that makes it fall back (paused, unusable project, deleted category), the project each support category routes to right now, which routing targets would notify nobody before the organization project managers (`project.nobodyNotified`), how many live tickets were not notified to anyone (`project.unnotifiedTicketCount`, `unnotifiedTicketCount`), and each rule `updatedAt` version token read together with its values.',\n responses: [{ status: 200, description: 'Routing preview', schema: previewSchema }],\n errors: [\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Missing staff.timesheets.settings.manage' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
4
|
+
import { resolveCrudRecordId, parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
|
|
5
|
+
import { StaffSupportRoutingRule } from "../../../data/entities.js";
|
|
6
|
+
import {
|
|
7
|
+
staffSupportRoutingRuleCreateSchema,
|
|
8
|
+
staffSupportRoutingRuleUpdateSchema
|
|
9
|
+
} from "../../../data/validators.js";
|
|
10
|
+
import { staffSupportRoutingRuleCommandIds } from "../../../commands/support-routing-rules.js";
|
|
11
|
+
import { createStaffCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from "../../openapi.js";
|
|
12
|
+
const SETTINGS_FEATURE = "staff.timesheets.settings.manage";
|
|
13
|
+
const F = {
|
|
14
|
+
id: "id",
|
|
15
|
+
tenant_id: "tenant_id",
|
|
16
|
+
organization_id: "organization_id",
|
|
17
|
+
category_value: "category_value",
|
|
18
|
+
time_project_id: "time_project_id",
|
|
19
|
+
is_active: "is_active",
|
|
20
|
+
created_at: "created_at",
|
|
21
|
+
updated_at: "updated_at"
|
|
22
|
+
};
|
|
23
|
+
const routeMetadata = {
|
|
24
|
+
GET: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },
|
|
25
|
+
POST: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },
|
|
26
|
+
PUT: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },
|
|
27
|
+
DELETE: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] }
|
|
28
|
+
};
|
|
29
|
+
const metadata = routeMetadata;
|
|
30
|
+
const rawBodySchema = z.object({}).passthrough();
|
|
31
|
+
const listSchema = z.object({
|
|
32
|
+
page: z.coerce.number().min(1).default(1),
|
|
33
|
+
pageSize: z.coerce.number().min(1).max(100).default(100),
|
|
34
|
+
ids: z.string().optional(),
|
|
35
|
+
sortField: z.string().optional(),
|
|
36
|
+
sortDir: z.enum(["asc", "desc"]).optional()
|
|
37
|
+
}).passthrough();
|
|
38
|
+
const crud = makeCrudRoute({
|
|
39
|
+
metadata: routeMetadata,
|
|
40
|
+
orm: {
|
|
41
|
+
entity: StaffSupportRoutingRule,
|
|
42
|
+
idField: "id",
|
|
43
|
+
orgField: "organizationId",
|
|
44
|
+
tenantField: "tenantId",
|
|
45
|
+
softDeleteField: "deletedAt"
|
|
46
|
+
},
|
|
47
|
+
indexer: { entityType: "staff:staff_support_routing_rule" },
|
|
48
|
+
list: {
|
|
49
|
+
schema: listSchema,
|
|
50
|
+
entityId: "staff:staff_support_routing_rule",
|
|
51
|
+
fields: [
|
|
52
|
+
F.id,
|
|
53
|
+
F.organization_id,
|
|
54
|
+
F.tenant_id,
|
|
55
|
+
F.category_value,
|
|
56
|
+
F.time_project_id,
|
|
57
|
+
F.is_active,
|
|
58
|
+
F.created_at,
|
|
59
|
+
F.updated_at
|
|
60
|
+
],
|
|
61
|
+
defaultSort: { field: F.created_at, dir: "asc" },
|
|
62
|
+
sortFieldMap: { createdAt: F.created_at, updatedAt: F.updated_at },
|
|
63
|
+
buildFilters: async (query) => {
|
|
64
|
+
const filters = {};
|
|
65
|
+
if (typeof query.ids === "string" && query.ids.trim().length > 0) {
|
|
66
|
+
const ids = query.ids.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
67
|
+
if (ids.length > 0) filters[F.id] = { $in: ids };
|
|
68
|
+
}
|
|
69
|
+
return filters;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
actions: {
|
|
73
|
+
create: {
|
|
74
|
+
commandId: staffSupportRoutingRuleCommandIds.create,
|
|
75
|
+
schema: rawBodySchema,
|
|
76
|
+
mapInput: async ({ raw, ctx }) => {
|
|
77
|
+
const { translate } = await resolveTranslations();
|
|
78
|
+
return parseScopedCommandInput(staffSupportRoutingRuleCreateSchema, raw ?? {}, ctx, translate);
|
|
79
|
+
},
|
|
80
|
+
response: ({ result }) => ({ id: result?.ruleId ?? null }),
|
|
81
|
+
status: 201
|
|
82
|
+
},
|
|
83
|
+
update: {
|
|
84
|
+
commandId: staffSupportRoutingRuleCommandIds.update,
|
|
85
|
+
schema: rawBodySchema,
|
|
86
|
+
mapInput: async ({ raw, ctx }) => {
|
|
87
|
+
const { translate } = await resolveTranslations();
|
|
88
|
+
return parseScopedCommandInput(staffSupportRoutingRuleUpdateSchema, raw ?? {}, ctx, translate);
|
|
89
|
+
},
|
|
90
|
+
response: () => ({ ok: true })
|
|
91
|
+
},
|
|
92
|
+
delete: {
|
|
93
|
+
commandId: staffSupportRoutingRuleCommandIds.delete,
|
|
94
|
+
schema: rawBodySchema,
|
|
95
|
+
mapInput: async ({ parsed, ctx }) => {
|
|
96
|
+
const { translate } = await resolveTranslations();
|
|
97
|
+
return { id: resolveCrudRecordId(parsed, ctx, translate) };
|
|
98
|
+
},
|
|
99
|
+
response: () => ({ ok: true })
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
const GET = crud.GET;
|
|
104
|
+
const POST = crud.POST;
|
|
105
|
+
const PUT = crud.PUT;
|
|
106
|
+
const DELETE = crud.DELETE;
|
|
107
|
+
const supportRoutingRuleListItemSchema = z.object({
|
|
108
|
+
id: z.string().uuid().nullable().optional(),
|
|
109
|
+
organization_id: z.string().uuid().nullable().optional(),
|
|
110
|
+
tenant_id: z.string().uuid().nullable().optional(),
|
|
111
|
+
category_value: z.string().nullable().optional(),
|
|
112
|
+
time_project_id: z.string().uuid().nullable().optional(),
|
|
113
|
+
is_active: z.boolean().nullable().optional(),
|
|
114
|
+
created_at: z.string().nullable().optional(),
|
|
115
|
+
updated_at: z.string().nullable().optional()
|
|
116
|
+
});
|
|
117
|
+
const openApi = createStaffCrudOpenApi({
|
|
118
|
+
resourceName: "Support routing rule",
|
|
119
|
+
pluralName: "Support routing rules",
|
|
120
|
+
querySchema: listSchema,
|
|
121
|
+
listResponseSchema: createPagedListResponseSchema(supportRoutingRuleListItemSchema),
|
|
122
|
+
create: {
|
|
123
|
+
schema: staffSupportRoutingRuleCreateSchema.omit({ tenantId: true, organizationId: true }),
|
|
124
|
+
responseSchema: z.object({ id: z.string().uuid().nullable() }),
|
|
125
|
+
description: "Creates a routing rule. A null `categoryValue` is the organization fallback rule (exactly one; it can be repointed but not deleted or paused). A rule with a category sends that category to its own project."
|
|
126
|
+
},
|
|
127
|
+
update: {
|
|
128
|
+
schema: staffSupportRoutingRuleUpdateSchema,
|
|
129
|
+
responseSchema: defaultOkResponseSchema,
|
|
130
|
+
description: "Repoints a rule at another project or pauses/resumes a per-category rule. Pausing the fallback rule answers 422."
|
|
131
|
+
},
|
|
132
|
+
del: {
|
|
133
|
+
schema: z.object({ id: z.string().uuid() }),
|
|
134
|
+
responseSchema: defaultOkResponseSchema,
|
|
135
|
+
description: "Deletes a per-category rule so the category falls back to the fallback project. Deleting the fallback rule answers 422."
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
export {
|
|
139
|
+
DELETE,
|
|
140
|
+
GET,
|
|
141
|
+
POST,
|
|
142
|
+
PUT,
|
|
143
|
+
metadata,
|
|
144
|
+
openApi,
|
|
145
|
+
supportRoutingRuleListItemSchema
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/staff/api/timesheets/support-routing-rules/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { StaffSupportRoutingRule } from '../../../data/entities'\nimport {\n staffSupportRoutingRuleCreateSchema,\n staffSupportRoutingRuleUpdateSchema,\n} from '../../../data/validators'\nimport { staffSupportRoutingRuleCommandIds } from '../../../commands/support-routing-rules'\nimport { createStaffCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from '../../openapi'\n\nconst SETTINGS_FEATURE = 'staff.timesheets.settings.manage'\n\nconst F = {\n id: 'id',\n tenant_id: 'tenant_id',\n organization_id: 'organization_id',\n category_value: 'category_value',\n time_project_id: 'time_project_id',\n is_active: 'is_active',\n created_at: 'created_at',\n updated_at: 'updated_at',\n} as const\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },\n POST: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },\n PUT: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },\n DELETE: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },\n}\n\nexport const metadata = routeMetadata\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(100),\n ids: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: StaffSupportRoutingRule,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: { entityType: 'staff:staff_support_routing_rule' },\n list: {\n schema: listSchema,\n entityId: 'staff:staff_support_routing_rule',\n fields: [\n F.id,\n F.organization_id,\n F.tenant_id,\n F.category_value,\n F.time_project_id,\n F.is_active,\n F.created_at,\n F.updated_at,\n ],\n defaultSort: { field: F.created_at, dir: 'asc' },\n sortFieldMap: { createdAt: F.created_at, updatedAt: F.updated_at },\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {}\n if (typeof query.ids === 'string' && query.ids.trim().length > 0) {\n const ids = query.ids.split(',').map((value) => value.trim()).filter((value) => value.length > 0)\n if (ids.length > 0) filters[F.id] = { $in: ids }\n }\n return filters\n },\n },\n actions: {\n create: {\n commandId: staffSupportRoutingRuleCommandIds.create,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(staffSupportRoutingRuleCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.ruleId ?? null }),\n status: 201,\n },\n update: {\n commandId: staffSupportRoutingRuleCommandIds.update,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(staffSupportRoutingRuleUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: staffSupportRoutingRuleCommandIds.delete,\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n return { id: resolveCrudRecordId(parsed, ctx, translate) }\n },\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nexport const supportRoutingRuleListItemSchema = z.object({\n id: z.string().uuid().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n category_value: z.string().nullable().optional(),\n time_project_id: z.string().uuid().nullable().optional(),\n is_active: z.boolean().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n})\n\nexport const openApi = createStaffCrudOpenApi({\n resourceName: 'Support routing rule',\n pluralName: 'Support routing rules',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(supportRoutingRuleListItemSchema),\n create: {\n schema: staffSupportRoutingRuleCreateSchema.omit({ tenantId: true, organizationId: true }),\n responseSchema: z.object({ id: z.string().uuid().nullable() }),\n description:\n 'Creates a routing rule. A null `categoryValue` is the organization fallback rule (exactly one; it can be repointed but not deleted or paused). A rule with a category sends that category to its own project.',\n },\n update: {\n schema: staffSupportRoutingRuleUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Repoints a rule at another project or pauses/resumes a per-category rule. Pausing the fallback rule answers 422.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a per-category rule so the category falls back to the fallback project. Deleting the fallback rule answers 422.',\n },\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
6
|
+
import { resolveSingleOrganizationIdOrDeny } from "@open-mercato/core/modules/directory/utils/organizationScopeFilter";
|
|
7
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
8
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
9
|
+
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
10
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
11
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
12
|
+
import { StaffTeamMember, StaffTimeProjectMember } from "../../../../data/entities.js";
|
|
13
|
+
import { loadSupportTicketCategories } from "../../../../lib/support/dictionaries.js";
|
|
14
|
+
import { resolveFeatureAccess } from "../../../../lib/time-tracking/featureAccess.js";
|
|
15
|
+
import { MANAGE_PROJECTS_FEATURE, resolveProjectAccess } from "../../../../lib/time-tracking/access.js";
|
|
16
|
+
import {
|
|
17
|
+
isActiveAssignment,
|
|
18
|
+
normalizeAssignmentGraceDays,
|
|
19
|
+
resolveAssignmentGraceDaysFromContainer
|
|
20
|
+
} from "../../../../lib/time-tracking/projectAssignment.js";
|
|
21
|
+
import { readSearchParamsRecord, runTimesheetInterceptors } from "../../_shared/withTimesheetInterceptors.js";
|
|
22
|
+
const logger = createLogger("staff").child({ component: "api/timesheets/support-tickets/options" });
|
|
23
|
+
const VIEW_FEATURE = "staff.timesheets.tasks.view";
|
|
24
|
+
const MAX_ASSIGNEES = 500;
|
|
25
|
+
const metadata = {
|
|
26
|
+
GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] }
|
|
27
|
+
};
|
|
28
|
+
const optionsResponseSchema = z.object({
|
|
29
|
+
categories: z.array(z.object({ value: z.string(), label: z.string() })),
|
|
30
|
+
assignees: z.array(z.object({ id: z.string().uuid(), displayName: z.string() }))
|
|
31
|
+
});
|
|
32
|
+
async function loadAssignableStaffMemberIds(em, scope, projectIds, graceDays) {
|
|
33
|
+
if (projectIds.length === 0) return [];
|
|
34
|
+
const memberships = await em.find(StaffTimeProjectMember, {
|
|
35
|
+
timeProjectId: { $in: projectIds },
|
|
36
|
+
tenantId: scope.tenantId,
|
|
37
|
+
organizationId: scope.organizationId,
|
|
38
|
+
status: "active",
|
|
39
|
+
deletedAt: null
|
|
40
|
+
});
|
|
41
|
+
const now = /* @__PURE__ */ new Date();
|
|
42
|
+
return Array.from(
|
|
43
|
+
new Set(
|
|
44
|
+
memberships.filter((membership) => isActiveAssignment(membership, now, normalizeAssignmentGraceDays(graceDays))).map((membership) => membership.staffMemberId)
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
async function loadAssignees(em, scope, staffMemberIds) {
|
|
49
|
+
if (staffMemberIds !== null && staffMemberIds.length === 0) return [];
|
|
50
|
+
const where = {
|
|
51
|
+
tenantId: scope.tenantId,
|
|
52
|
+
organizationId: scope.organizationId,
|
|
53
|
+
isActive: true,
|
|
54
|
+
deletedAt: null
|
|
55
|
+
};
|
|
56
|
+
if (staffMemberIds !== null) where.id = { $in: staffMemberIds };
|
|
57
|
+
const members = await findWithDecryption(em, StaffTeamMember, where, { limit: MAX_ASSIGNEES }, scope);
|
|
58
|
+
return members.map((member) => ({ id: member.id, displayName: member.displayName || member.id })).sort((left, right) => left.displayName.localeCompare(right.displayName));
|
|
59
|
+
}
|
|
60
|
+
async function GET(req) {
|
|
61
|
+
try {
|
|
62
|
+
const container = await createRequestContainer();
|
|
63
|
+
const auth = await getAuthFromRequest(req);
|
|
64
|
+
const { translate } = await resolveTranslations();
|
|
65
|
+
if (!auth) throw new CrudHttpError(401, { error: translate("staff.errors.unauthorized", "Unauthorized") });
|
|
66
|
+
const orgScope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
67
|
+
const tenantId = orgScope?.tenantId ?? auth.tenantId ?? null;
|
|
68
|
+
const organizationId = resolveSingleOrganizationIdOrDeny(orgScope, auth) ?? null;
|
|
69
|
+
if (!tenantId || !organizationId) {
|
|
70
|
+
throw new CrudHttpError(400, {
|
|
71
|
+
error: translate("staff.errors.missingScope", "Missing tenant or organization scope.")
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const interceptors = await runTimesheetInterceptors({
|
|
75
|
+
request: req,
|
|
76
|
+
method: "GET",
|
|
77
|
+
scope: { container, userId: auth.sub, tenantId, organizationId },
|
|
78
|
+
query: readSearchParamsRecord(req.url)
|
|
79
|
+
});
|
|
80
|
+
if (!interceptors.ok) return interceptors.response;
|
|
81
|
+
const { session } = interceptors;
|
|
82
|
+
const scope = { tenantId, organizationId };
|
|
83
|
+
const em = container.resolve("em").fork();
|
|
84
|
+
const manageAccess = await resolveFeatureAccess(container, auth.sub ?? null, [MANAGE_PROJECTS_FEATURE], scope);
|
|
85
|
+
const graceDays = await resolveAssignmentGraceDaysFromContainer(container, tenantId);
|
|
86
|
+
const access = await resolveProjectAccess({
|
|
87
|
+
em,
|
|
88
|
+
userId: auth.sub ?? null,
|
|
89
|
+
tenantId,
|
|
90
|
+
organizationId,
|
|
91
|
+
canManageAll: manageAccess.allowed,
|
|
92
|
+
userFeatures: manageAccess.grantedFeatures,
|
|
93
|
+
assignmentGraceDays: graceDays
|
|
94
|
+
});
|
|
95
|
+
const assignableIds = access.canManageAll ? null : await loadAssignableStaffMemberIds(em, scope, access.projectIds, graceDays);
|
|
96
|
+
const [categories, assignees] = await Promise.all([
|
|
97
|
+
loadSupportTicketCategories(em, scope),
|
|
98
|
+
loadAssignees(em.fork(), scope, assignableIds)
|
|
99
|
+
]);
|
|
100
|
+
const payload = optionsResponseSchema.parse({ categories, assignees });
|
|
101
|
+
return session.respond(200, payload);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (err instanceof CrudHttpError) {
|
|
104
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
105
|
+
}
|
|
106
|
+
logger.error("staff.support.inbox options failed", { err });
|
|
107
|
+
getTelemetryRuntime()?.reportError(err, { module: "staff", code: "staff.support_inbox_options_failed" });
|
|
108
|
+
const { translate } = await resolveTranslations();
|
|
109
|
+
return NextResponse.json({ error: translate("staff.errors.internal", "Internal server error") }, { status: 500 });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const openApi = {
|
|
113
|
+
tag: "Staff",
|
|
114
|
+
summary: "Support Inbox filter options",
|
|
115
|
+
methods: {
|
|
116
|
+
GET: {
|
|
117
|
+
summary: "Support Inbox filter options",
|
|
118
|
+
description: "The organization's support ticket categories and the staff members a ticket can be filtered by assignee. Assignees are limited to the people actively assigned to the caller's projects; a holder of `staff.timesheets.projects.manage` gets every active staff member of the organization.",
|
|
119
|
+
responses: [{ status: 200, description: "Filter options", schema: optionsResponseSchema }],
|
|
120
|
+
errors: [
|
|
121
|
+
{ status: 400, description: "Missing tenant or organization scope" },
|
|
122
|
+
{ status: 401, description: "Unauthorized" },
|
|
123
|
+
{ status: 403, description: "Missing staff.timesheets.tasks.view" }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
export {
|
|
129
|
+
GET,
|
|
130
|
+
metadata,
|
|
131
|
+
openApi
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/staff/api/timesheets/support-tickets/options/route.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `GET /api/staff/timesheets/support-tickets/options` \u2014 the filter options behind\n * the Support Inbox.\n *\n * The inbox is open to every holder of `staff.timesheets.tasks.view`, so its option\n * lists cannot come from routes that need more: the routing preview needs\n * `staff.timesheets.settings.manage` and the team directory needs `staff.view`,\n * which an agent with the `employee` role holds neither of.\n *\n * * `categories` is the organization's support category dictionary \u2014 the exact\n * organization, never an inherited one, the same lookup the portal form and the\n * ticket command use.\n * * `assignees` is limited to the people the caller can already see through their\n * project access: everyone actively assigned to one of the caller's projects, or,\n * for a holder of `staff.timesheets.projects.manage`, every active staff member of\n * the organization.\n */\n\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveSingleOrganizationIdOrDeny } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTeamMember, StaffTimeProjectMember } from '../../../../data/entities'\nimport { loadSupportTicketCategories } from '../../../../lib/support/dictionaries'\nimport { resolveFeatureAccess } from '../../../../lib/time-tracking/featureAccess'\nimport { MANAGE_PROJECTS_FEATURE, resolveProjectAccess } from '../../../../lib/time-tracking/access'\nimport {\n isActiveAssignment,\n normalizeAssignmentGraceDays,\n resolveAssignmentGraceDaysFromContainer,\n} from '../../../../lib/time-tracking/projectAssignment'\nimport { readSearchParamsRecord, runTimesheetInterceptors } from '../../_shared/withTimesheetInterceptors'\n\nconst logger = createLogger('staff').child({ component: 'api/timesheets/support-tickets/options' })\n\nconst VIEW_FEATURE = 'staff.timesheets.tasks.view'\nconst MAX_ASSIGNEES = 500\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },\n}\n\nconst optionsResponseSchema = z.object({\n categories: z.array(z.object({ value: z.string(), label: z.string() })),\n assignees: z.array(z.object({ id: z.string().uuid(), displayName: z.string() })),\n})\n\ntype SupportInboxOptions = z.infer<typeof optionsResponseSchema>\n\ntype Scope = { tenantId: string; organizationId: string }\n\nasync function loadAssignableStaffMemberIds(\n em: EntityManager,\n scope: Scope,\n projectIds: string[],\n graceDays: number | null,\n): Promise<string[]> {\n if (projectIds.length === 0) return []\n const memberships = await em.find(StaffTimeProjectMember, {\n timeProjectId: { $in: projectIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n })\n const now = new Date()\n return Array.from(\n new Set(\n memberships\n .filter((membership) => isActiveAssignment(membership, now, normalizeAssignmentGraceDays(graceDays)))\n .map((membership) => membership.staffMemberId),\n ),\n )\n}\n\nasync function loadAssignees(\n em: EntityManager,\n scope: Scope,\n staffMemberIds: string[] | null,\n): Promise<SupportInboxOptions['assignees']> {\n if (staffMemberIds !== null && staffMemberIds.length === 0) return []\n const where: Record<string, unknown> = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isActive: true,\n deletedAt: null,\n }\n if (staffMemberIds !== null) where.id = { $in: staffMemberIds }\n const members = await findWithDecryption(em, StaffTeamMember, where, { limit: MAX_ASSIGNEES }, scope)\n return members\n .map((member) => ({ id: member.id, displayName: member.displayName || member.id }))\n .sort((left, right) => left.displayName.localeCompare(right.displayName))\n}\n\nexport async function GET(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n\n const orgScope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = orgScope?.tenantId ?? auth.tenantId ?? null\n const organizationId = resolveSingleOrganizationIdOrDeny(orgScope, auth) ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, {\n error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),\n })\n }\n\n const interceptors = await runTimesheetInterceptors({\n request: req,\n method: 'GET',\n scope: { container, userId: auth.sub, tenantId, organizationId },\n query: readSearchParamsRecord(req.url),\n })\n if (!interceptors.ok) return interceptors.response\n const { session } = interceptors\n\n const scope: Scope = { tenantId, organizationId }\n const em = (container.resolve('em') as EntityManager).fork()\n const manageAccess = await resolveFeatureAccess(container, auth.sub ?? null, [MANAGE_PROJECTS_FEATURE], scope)\n const graceDays = await resolveAssignmentGraceDaysFromContainer(container, tenantId)\n const access = await resolveProjectAccess({\n em,\n userId: auth.sub ?? null,\n tenantId,\n organizationId,\n canManageAll: manageAccess.allowed,\n userFeatures: manageAccess.grantedFeatures,\n assignmentGraceDays: graceDays,\n })\n\n const assignableIds = access.canManageAll\n ? null\n : await loadAssignableStaffMemberIds(em, scope, access.projectIds, graceDays)\n\n const [categories, assignees] = await Promise.all([\n loadSupportTicketCategories(em, scope),\n loadAssignees(em.fork(), scope, assignableIds),\n ])\n\n const payload: SupportInboxOptions = optionsResponseSchema.parse({ categories, assignees })\n return session.respond(200, payload)\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('staff.support.inbox options failed', { err })\n getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_inbox_options_failed' })\n const { translate } = await resolveTranslations()\n return NextResponse.json({ error: translate('staff.errors.internal', 'Internal server error') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Support Inbox filter options',\n methods: {\n GET: {\n summary: 'Support Inbox filter options',\n description:\n \"The organization's support ticket categories and the staff members a ticket can be filtered by assignee. Assignees are limited to the people actively assigned to the caller's projects; a holder of `staff.timesheets.projects.manage` gets every active staff member of the organization.\",\n responses: [{ status: 200, description: 'Filter options', schema: optionsResponseSchema }],\n errors: [\n { status: 400, description: 'Missing tenant or organization scope' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Missing staff.timesheets.tasks.view' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TASK_ASSIGNMENT_VIEW_FEATURE,
|
|
3
|
+
createTaskAssignmentPostHandler,
|
|
4
|
+
taskAssignmentResponseSchema
|
|
5
|
+
} from "../../../../../lib/time-tracking/taskAssignmentRoute.js";
|
|
6
|
+
const metadata = {
|
|
7
|
+
POST: { requireAuth: true, requireFeatures: [TASK_ASSIGNMENT_VIEW_FEATURE] }
|
|
8
|
+
};
|
|
9
|
+
const POST = createTaskAssignmentPostHandler("claim");
|
|
10
|
+
const openApi = {
|
|
11
|
+
tag: "Staff",
|
|
12
|
+
summary: "Claim an unassigned task",
|
|
13
|
+
methods: {
|
|
14
|
+
POST: {
|
|
15
|
+
summary: "Claim an unassigned task",
|
|
16
|
+
description: "Sets the caller as the assignee of an unassigned, not-done task. Only active assigners of the task project (a project member flagged as assigner, inside the assignment window) may claim; `staff.timesheets.tasks.manage` is not required. The update is conditional on the task still being unassigned, so of two concurrent claims exactly one wins and the other answers 409. Accepts `x-om-ext-optimistic-lock-expected-updated-at`.",
|
|
17
|
+
responses: [{ status: 200, description: "Claim an unassigned task", schema: taskAssignmentResponseSchema }],
|
|
18
|
+
errors: [
|
|
19
|
+
{ status: 401, description: "Unauthorized" },
|
|
20
|
+
{ status: 403, description: "The caller is not an active assigner of the project" },
|
|
21
|
+
{ status: 404, description: "Task not found, or not on a project the caller can see" },
|
|
22
|
+
{ status: 409, description: "The task was already claimed, or changed since the version the caller sent" },
|
|
23
|
+
{ status: 422, description: "The task is in a done status" }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export {
|
|
29
|
+
POST,
|
|
30
|
+
metadata,
|
|
31
|
+
openApi
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/staff/api/timesheets/tasks/%5Bid%5D/claim/route.ts"],
|
|
4
|
+
"sourcesContent": ["import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n TASK_ASSIGNMENT_VIEW_FEATURE,\n createTaskAssignmentPostHandler,\n taskAssignmentResponseSchema,\n} from '../../../../../lib/time-tracking/taskAssignmentRoute'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: [TASK_ASSIGNMENT_VIEW_FEATURE] },\n}\n\nexport const POST = createTaskAssignmentPostHandler('claim')\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Claim an unassigned task',\n methods: {\n POST: {\n summary: 'Claim an unassigned task',\n description: 'Sets the caller as the assignee of an unassigned, not-done task. Only active assigners of the task project (a project member flagged as assigner, inside the assignment window) may claim; `staff.timesheets.tasks.manage` is not required. The update is conditional on the task still being unassigned, so of two concurrent claims exactly one wins and the other answers 409. Accepts `x-om-ext-optimistic-lock-expected-updated-at`.',\n responses: [{ status: 200, description: 'Claim an unassigned task', schema: taskAssignmentResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'The caller is not an active assigner of the project' },\n { status: 404, description: 'Task not found, or not on a project the caller can see' },\n { status: 409, description: 'The task was already claimed, or changed since the version the caller sent' },\n { status: 422, description: 'The task is in a done status' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEO,MAAM,OAAO,gCAAgC,OAAO;AAEpD,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,6BAA6B,CAAC;AAAA,MAC1G,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,eAAe;AAAA,QAC3C,EAAE,QAAQ,KAAK,aAAa,sDAAsD;AAAA,QAClF,EAAE,QAAQ,KAAK,aAAa,yDAAyD;AAAA,QACrF,EAAE,QAAQ,KAAK,aAAa,6EAA6E;AAAA,QACzG,EAAE,QAAQ,KAAK,aAAa,+BAA+B;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|