@hed-hog/operations 0.0.330 → 0.0.332
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/README.md +5 -5
- package/dist/controllers/operations-collaborators.controller.d.ts +58 -213
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +100 -0
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/controllers/operations-contracts.controller.d.ts +6 -6
- package/dist/controllers/operations-projects.controller.d.ts +25 -0
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.js +48 -0
- package/dist/controllers/operations-projects.controller.js.map +1 -1
- package/dist/controllers/operations-reports.controller.d.ts +1 -1
- package/dist/controllers/operations-tasks.controller.d.ts +34 -9
- package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
- package/dist/controllers/operations-tasks.controller.js +43 -32
- package/dist/controllers/operations-tasks.controller.js.map +1 -1
- package/dist/controllers/operations-timesheets.controller.d.ts +9 -9
- package/dist/dashboard/components/DashboardLayout.d.ts +30 -0
- package/dist/dashboard/components/DashboardLayout.d.ts.map +1 -0
- package/dist/dashboard/components/DashboardLayout.js +87 -0
- package/dist/dashboard/components/DashboardLayout.js.map +1 -0
- package/dist/dashboard/components/widget-registry.d.ts +23 -0
- package/dist/dashboard/components/widget-registry.d.ts.map +1 -0
- package/dist/dashboard/components/widget-registry.js +245 -0
- package/dist/dashboard/components/widget-registry.js.map +1 -0
- package/dist/dashboard/hooks/useDashboardData.d.ts +20 -0
- package/dist/dashboard/hooks/useDashboardData.d.ts.map +1 -0
- package/dist/dashboard/hooks/useDashboardData.js +24 -0
- package/dist/dashboard/hooks/useDashboardData.js.map +1 -0
- package/dist/dashboard/types/widgets.types.d.ts +233 -0
- package/dist/dashboard/types/widgets.types.d.ts.map +1 -0
- package/dist/dashboard/types/widgets.types.js +6 -0
- package/dist/dashboard/types/widgets.types.js.map +1 -0
- package/dist/dashboard/widgets/CapacityDistribution.d.ts +23 -0
- package/dist/dashboard/widgets/CapacityDistribution.d.ts.map +1 -0
- package/dist/dashboard/widgets/CapacityDistribution.js +11 -0
- package/dist/dashboard/widgets/CapacityDistribution.js.map +1 -0
- package/dist/dashboard/widgets/EffortByProject.d.ts +22 -0
- package/dist/dashboard/widgets/EffortByProject.d.ts.map +1 -0
- package/dist/dashboard/widgets/EffortByProject.js +11 -0
- package/dist/dashboard/widgets/EffortByProject.js.map +1 -0
- package/dist/dashboard/widgets/HeadcountByArea.d.ts +24 -0
- package/dist/dashboard/widgets/HeadcountByArea.d.ts.map +1 -0
- package/dist/dashboard/widgets/HeadcountByArea.js +11 -0
- package/dist/dashboard/widgets/HeadcountByArea.js.map +1 -0
- package/dist/dashboard/widgets/ManagedProjectsStatus.d.ts +18 -0
- package/dist/dashboard/widgets/ManagedProjectsStatus.d.ts.map +1 -0
- package/dist/dashboard/widgets/ManagedProjectsStatus.js +12 -0
- package/dist/dashboard/widgets/ManagedProjectsStatus.js.map +1 -0
- package/dist/dashboard/widgets/MyHoursPeriodKpi.d.ts +22 -0
- package/dist/dashboard/widgets/MyHoursPeriodKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyHoursPeriodKpi.js +12 -0
- package/dist/dashboard/widgets/MyHoursPeriodKpi.js.map +1 -0
- package/dist/dashboard/widgets/MyOpenRequestsKpi.d.ts +19 -0
- package/dist/dashboard/widgets/MyOpenRequestsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyOpenRequestsKpi.js +17 -0
- package/dist/dashboard/widgets/MyOpenRequestsKpi.js.map +1 -0
- package/dist/dashboard/widgets/MyPendingRequestsList.d.ts +23 -0
- package/dist/dashboard/widgets/MyPendingRequestsList.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyPendingRequestsList.js +14 -0
- package/dist/dashboard/widgets/MyPendingRequestsList.js.map +1 -0
- package/dist/dashboard/widgets/MyProjectAllocationsKpi.d.ts +22 -0
- package/dist/dashboard/widgets/MyProjectAllocationsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyProjectAllocationsKpi.js +11 -0
- package/dist/dashboard/widgets/MyProjectAllocationsKpi.js.map +1 -0
- package/dist/dashboard/widgets/MyQuickActions.d.ts +23 -0
- package/dist/dashboard/widgets/MyQuickActions.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyQuickActions.js +18 -0
- package/dist/dashboard/widgets/MyQuickActions.js.map +1 -0
- package/dist/dashboard/widgets/MyRelevantDeadlines.d.ts +23 -0
- package/dist/dashboard/widgets/MyRelevantDeadlines.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyRelevantDeadlines.js +22 -0
- package/dist/dashboard/widgets/MyRelevantDeadlines.js.map +1 -0
- package/dist/dashboard/widgets/MyTimesheetStatusKpi.d.ts +17 -0
- package/dist/dashboard/widgets/MyTimesheetStatusKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyTimesheetStatusKpi.js +11 -0
- package/dist/dashboard/widgets/MyTimesheetStatusKpi.js.map +1 -0
- package/dist/dashboard/widgets/MyWeeklyJourney.d.ts +21 -0
- package/dist/dashboard/widgets/MyWeeklyJourney.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyWeeklyJourney.js +19 -0
- package/dist/dashboard/widgets/MyWeeklyJourney.js.map +1 -0
- package/dist/dashboard/widgets/PortfolioCostsKpi.d.ts +19 -0
- package/dist/dashboard/widgets/PortfolioCostsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/PortfolioCostsKpi.js +12 -0
- package/dist/dashboard/widgets/PortfolioCostsKpi.js.map +1 -0
- package/dist/dashboard/widgets/PortfolioEffortKpi.d.ts +18 -0
- package/dist/dashboard/widgets/PortfolioEffortKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/PortfolioEffortKpi.js +8 -0
- package/dist/dashboard/widgets/PortfolioEffortKpi.js.map +1 -0
- package/dist/dashboard/widgets/PortfolioProjectsKpi.d.ts +22 -0
- package/dist/dashboard/widgets/PortfolioProjectsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/PortfolioProjectsKpi.js +56 -0
- package/dist/dashboard/widgets/PortfolioProjectsKpi.js.map +1 -0
- package/dist/dashboard/widgets/PortfolioRiskKpi.d.ts +19 -0
- package/dist/dashboard/widgets/PortfolioRiskKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/PortfolioRiskKpi.js +11 -0
- package/dist/dashboard/widgets/PortfolioRiskKpi.js.map +1 -0
- package/dist/dashboard/widgets/ProjectStatusOverview.d.ts +19 -0
- package/dist/dashboard/widgets/ProjectStatusOverview.d.ts.map +1 -0
- package/dist/dashboard/widgets/ProjectStatusOverview.js +18 -0
- package/dist/dashboard/widgets/ProjectStatusOverview.js.map +1 -0
- package/dist/dashboard/widgets/StrategicDeadlines.d.ts +24 -0
- package/dist/dashboard/widgets/StrategicDeadlines.d.ts.map +1 -0
- package/dist/dashboard/widgets/StrategicDeadlines.js +22 -0
- package/dist/dashboard/widgets/StrategicDeadlines.js.map +1 -0
- package/dist/dashboard/widgets/TeamApprovalQueue.d.ts +24 -0
- package/dist/dashboard/widgets/TeamApprovalQueue.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamApprovalQueue.js +12 -0
- package/dist/dashboard/widgets/TeamApprovalQueue.js.map +1 -0
- package/dist/dashboard/widgets/TeamCapacityKpi.d.ts +18 -0
- package/dist/dashboard/widgets/TeamCapacityKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamCapacityKpi.js +19 -0
- package/dist/dashboard/widgets/TeamCapacityKpi.js.map +1 -0
- package/dist/dashboard/widgets/TeamHeadcountKpi.d.ts +22 -0
- package/dist/dashboard/widgets/TeamHeadcountKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamHeadcountKpi.js +56 -0
- package/dist/dashboard/widgets/TeamHeadcountKpi.js.map +1 -0
- package/dist/dashboard/widgets/TeamHoursKpi.d.ts +19 -0
- package/dist/dashboard/widgets/TeamHoursKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamHoursKpi.js +13 -0
- package/dist/dashboard/widgets/TeamHoursKpi.js.map +1 -0
- package/dist/dashboard/widgets/TeamPendingApprovalsKpi.d.ts +20 -0
- package/dist/dashboard/widgets/TeamPendingApprovalsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamPendingApprovalsKpi.js +11 -0
- package/dist/dashboard/widgets/TeamPendingApprovalsKpi.js.map +1 -0
- package/dist/dashboard/widgets/TeamUtilizationOverview.d.ts +18 -0
- package/dist/dashboard/widgets/TeamUtilizationOverview.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamUtilizationOverview.js +17 -0
- package/dist/dashboard/widgets/TeamUtilizationOverview.js.map +1 -0
- package/dist/dashboard/widgets/TeamWorkloadAlerts.d.ts +24 -0
- package/dist/dashboard/widgets/TeamWorkloadAlerts.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamWorkloadAlerts.js +19 -0
- package/dist/dashboard/widgets/TeamWorkloadAlerts.js.map +1 -0
- package/dist/dashboard/widgets/index.d.ts +24 -0
- package/dist/dashboard/widgets/index.d.ts.map +1 -0
- package/dist/dashboard/widgets/index.js +54 -0
- package/dist/dashboard/widgets/index.js.map +1 -0
- package/dist/dto/create-collaborator-invoice.dto.d.ts +11 -0
- package/dist/dto/create-collaborator-invoice.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator-invoice.dto.js +55 -0
- package/dist/dto/create-collaborator-invoice.dto.js.map +1 -0
- package/dist/dto/create-collaborator-payment.dto.d.ts +10 -0
- package/dist/dto/create-collaborator-payment.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator-payment.dto.js +50 -0
- package/dist/dto/create-collaborator-payment.dto.js.map +1 -0
- package/dist/dto/create-collaborator.dto.d.ts +0 -1
- package/dist/dto/create-collaborator.dto.d.ts.map +1 -1
- package/dist/dto/create-collaborator.dto.js +0 -6
- package/dist/dto/create-collaborator.dto.js.map +1 -1
- package/dist/dto/list-collaborator-invoice.dto.d.ts +4 -0
- package/dist/dto/list-collaborator-invoice.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborator-invoice.dto.js +8 -0
- package/dist/dto/list-collaborator-invoice.dto.js.map +1 -0
- package/dist/dto/list-collaborator-payment.dto.d.ts +4 -0
- package/dist/dto/list-collaborator-payment.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborator-payment.dto.js +8 -0
- package/dist/dto/list-collaborator-payment.dto.js.map +1 -0
- package/dist/dto/update-collaborator-invoice.dto.d.ts +6 -0
- package/dist/dto/update-collaborator-invoice.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator-invoice.dto.js +9 -0
- package/dist/dto/update-collaborator-invoice.dto.js.map +1 -0
- package/dist/dto/update-collaborator-payment.dto.d.ts +6 -0
- package/dist/dto/update-collaborator-payment.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator-payment.dto.js +9 -0
- package/dist/dto/update-collaborator-payment.dto.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/operations.controller.d.ts +42 -0
- package/dist/operations.controller.d.ts.map +1 -1
- package/dist/operations.service.d.ts +258 -268
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +2381 -1341
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +345 -174
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/dashboard_component.yaml +66 -0
- package/hedhog/data/dashboard_item.yaml +25 -25
- package/hedhog/data/menu.yaml +27 -8
- package/hedhog/data/route.yaml +133 -0
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +78 -102
- package/hedhog/frontend/app/_components/collaborator-invoices-tab.tsx.ejs +443 -0
- package/hedhog/frontend/app/_components/collaborator-payment-history-tab.tsx.ejs +429 -0
- package/hedhog/frontend/app/_components/collaborator-picker.tsx.ejs +158 -0
- package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +247 -50
- package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +643 -450
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +992 -431
- package/hedhog/frontend/app/_components/project-file-attachments.tsx.ejs +371 -0
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +558 -386
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +383 -157
- package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +4 -1
- package/hedhog/frontend/app/_components/task-form-fields.tsx.ejs +406 -0
- package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +629 -784
- package/hedhog/frontend/app/_components/task-info-display.tsx.ejs +137 -0
- package/hedhog/frontend/app/_components/timesheet-entry-create-sheet.tsx.ejs +306 -0
- package/hedhog/frontend/app/_lib/api.ts.ejs +155 -0
- package/hedhog/frontend/app/_lib/types.ts.ejs +62 -0
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +0 -2
- package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +61 -0
- package/hedhog/frontend/app/approvals/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +59 -8
- package/hedhog/frontend/app/contracts/page.tsx.ejs +29 -8
- package/hedhog/frontend/app/dashboard/widgets/CapacityDistribution.tsx.ejs +84 -0
- package/hedhog/frontend/app/dashboard/widgets/EffortByProject.tsx.ejs +85 -0
- package/hedhog/frontend/app/dashboard/widgets/HeadcountByArea.tsx.ejs +101 -0
- package/hedhog/frontend/app/dashboard/widgets/ManagedProjectsStatus.tsx.ejs +113 -0
- package/hedhog/frontend/app/dashboard/widgets/MyHoursPeriodKpi.tsx.ejs +87 -0
- package/hedhog/frontend/app/dashboard/widgets/MyOpenRequestsKpi.tsx.ejs +97 -0
- package/hedhog/frontend/app/dashboard/widgets/MyPendingRequestsList.tsx.ejs +99 -0
- package/hedhog/frontend/app/dashboard/widgets/MyProjectAllocationsKpi.tsx.ejs +78 -0
- package/hedhog/frontend/app/dashboard/widgets/MyQuickActions.tsx.ejs +130 -0
- package/hedhog/frontend/app/dashboard/widgets/MyRelevantDeadlines.tsx.ejs +144 -0
- package/hedhog/frontend/app/dashboard/widgets/MyTimesheetStatusKpi.tsx.ejs +78 -0
- package/hedhog/frontend/app/dashboard/widgets/MyWeeklyJourney.tsx.ejs +99 -0
- package/hedhog/frontend/app/dashboard/widgets/PortfolioCostsKpi.tsx.ejs +112 -0
- package/hedhog/frontend/app/dashboard/widgets/PortfolioEffortKpi.tsx.ejs +93 -0
- package/hedhog/frontend/app/dashboard/widgets/PortfolioProjectsKpi.tsx.ejs +96 -0
- package/hedhog/frontend/app/dashboard/widgets/PortfolioRiskKpi.tsx.ejs +115 -0
- package/hedhog/frontend/app/dashboard/widgets/ProjectStatusOverview.tsx.ejs +120 -0
- package/hedhog/frontend/app/dashboard/widgets/StrategicDeadlines.tsx.ejs +146 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamApprovalQueue.tsx.ejs +108 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamCapacityKpi.tsx.ejs +97 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamHeadcountKpi.tsx.ejs +100 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamHoursKpi.tsx.ejs +104 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamPendingApprovalsKpi.tsx.ejs +110 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamUtilizationOverview.tsx.ejs +115 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamWorkloadAlerts.tsx.ejs +117 -0
- package/hedhog/frontend/app/dashboard/widgets/index.ts.ejs +26 -0
- package/hedhog/frontend/app/departments/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/my-projects/page.tsx.ejs +30 -12
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +286 -125
- package/hedhog/frontend/app/project-cost-categories/page.tsx.ejs +58 -52
- package/hedhog/frontend/app/project-cost-types/page.tsx.ejs +58 -51
- package/hedhog/frontend/app/projects/page.tsx.ejs +415 -33
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/tasks-gantt/page.tsx.ejs +953 -0
- package/hedhog/frontend/app/time-off/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +10 -4
- package/hedhog/frontend/messages/en.json +332 -46
- package/hedhog/frontend/messages/operations/en.json +61 -52
- package/hedhog/frontend/messages/operations/pt.json +59 -43
- package/hedhog/frontend/messages/pt.json +332 -46
- package/hedhog/frontend/widgets/capacity-distribution.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/effort-by-project.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/headcount-by-area.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/index.ts.ejs +25 -0
- package/hedhog/frontend/widgets/managed-projects-status.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-hours-period-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-open-requests-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-pending-requests-list.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-project-allocations-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-quick-actions.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-relevant-deadlines.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-timesheet-status-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-weekly-journey.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/portfolio-costs-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/portfolio-effort-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/portfolio-projects-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/portfolio-risk-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/project-status-overview.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/shared-operations-widget.tsx.ejs +170 -0
- package/hedhog/frontend/widgets/strategic-deadlines.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-approval-queue.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-capacity-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-headcount-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-hours-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-pending-approvals-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-utilization-overview.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-workload-alerts.tsx.ejs +17 -0
- package/hedhog/table/operations_collaborator.yaml +8 -13
- package/hedhog/table/operations_collaborator_invoice.yaml +35 -0
- package/hedhog/table/operations_collaborator_payment.yaml +32 -0
- package/hedhog/table/operations_project.yaml +1 -1
- package/hedhog/table/operations_project_file.yaml +23 -0
- package/hedhog/table/operations_task.yaml +76 -69
- package/hedhog/table/operations_task_activity.yaml +51 -0
- package/package.json +6 -5
- package/src/controllers/operations-collaborators.controller.ts +117 -8
- package/src/controllers/operations-projects.controller.ts +41 -8
- package/src/controllers/operations-tasks.controller.ts +156 -166
- package/src/dashboard/README.md +214 -0
- package/src/dashboard/components/DashboardLayout.tsx +131 -0
- package/src/dashboard/components/widget-registry.ts +255 -0
- package/src/dashboard/hooks/useDashboardData.ts +29 -0
- package/src/dashboard/types/widgets.types.ts +237 -0
- package/src/dashboard/widgets/CapacityDistribution.tsx +56 -0
- package/src/dashboard/widgets/EffortByProject.tsx +51 -0
- package/src/dashboard/widgets/HeadcountByArea.tsx +57 -0
- package/src/dashboard/widgets/ManagedProjectsStatus.tsx +53 -0
- package/src/dashboard/widgets/MyHoursPeriodKpi.tsx +87 -0
- package/src/dashboard/widgets/MyOpenRequestsKpi.tsx +51 -0
- package/src/dashboard/widgets/MyPendingRequestsList.tsx +63 -0
- package/src/dashboard/widgets/MyProjectAllocationsKpi.tsx +57 -0
- package/src/dashboard/widgets/MyQuickActions.tsx +62 -0
- package/src/dashboard/widgets/MyRelevantDeadlines.tsx +84 -0
- package/src/dashboard/widgets/MyTimesheetStatusKpi.tsx +65 -0
- package/src/dashboard/widgets/MyWeeklyJourney.tsx +57 -0
- package/src/dashboard/widgets/PortfolioCostsKpi.tsx +48 -0
- package/src/dashboard/widgets/PortfolioEffortKpi.tsx +41 -0
- package/src/dashboard/widgets/PortfolioRiskKpi.tsx +50 -0
- package/src/dashboard/widgets/ProjectStatusOverview.tsx +52 -0
- package/src/dashboard/widgets/StrategicDeadlines.tsx +93 -0
- package/src/dashboard/widgets/TeamApprovalQueue.tsx +70 -0
- package/src/dashboard/widgets/TeamCapacityKpi.tsx +50 -0
- package/src/dashboard/widgets/TeamHoursKpi.tsx +51 -0
- package/src/dashboard/widgets/TeamPendingApprovalsKpi.tsx +53 -0
- package/src/dashboard/widgets/TeamUtilizationOverview.tsx +62 -0
- package/src/dashboard/widgets/TeamWorkloadAlerts.tsx +81 -0
- package/src/dashboard/widgets/index.ts +26 -0
- package/src/dto/create-collaborator-invoice.dto.ts +39 -0
- package/src/dto/create-collaborator-payment.dto.ts +35 -0
- package/src/dto/create-collaborator.dto.ts +4 -11
- package/src/dto/list-collaborator-invoice.dto.ts +3 -0
- package/src/dto/list-collaborator-payment.dto.ts +3 -0
- package/src/dto/update-collaborator-invoice.dto.ts +6 -0
- package/src/dto/update-collaborator-payment.dto.ts +6 -0
- package/src/index.ts +3 -0
- package/src/operations.service.spec.ts +988 -764
- package/src/operations.service.ts +4689 -2624
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
5
|
const operations_service_1 = require("./operations.service");
|
|
6
6
|
const operations_access_service_1 = require("./services/shared/operations-access.service");
|
|
7
|
-
describe(
|
|
7
|
+
describe("OperationsService proposal integration", () => {
|
|
8
8
|
let service;
|
|
9
9
|
let prisma;
|
|
10
10
|
let integrationApi;
|
|
@@ -16,37 +16,43 @@ describe('OperationsService proposal integration', () => {
|
|
|
16
16
|
publishEvent: jest.fn().mockResolvedValue(undefined),
|
|
17
17
|
};
|
|
18
18
|
service = new operations_service_1.OperationsService(prisma, {}, integrationApi, {}, {}, new operations_access_service_1.OperationsAccessService());
|
|
19
|
-
jest
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
jest
|
|
20
|
+
.spyOn(service, "generateContractCode")
|
|
21
|
+
.mockResolvedValue("CTR-001");
|
|
22
|
+
jest
|
|
23
|
+
.spyOn(service, "replaceContractParties")
|
|
24
|
+
.mockResolvedValue(undefined);
|
|
25
|
+
jest
|
|
26
|
+
.spyOn(service, "insertContractHistory")
|
|
27
|
+
.mockResolvedValue(undefined);
|
|
22
28
|
});
|
|
23
29
|
afterEach(() => {
|
|
24
30
|
jest.restoreAllMocks();
|
|
25
31
|
});
|
|
26
|
-
it(
|
|
32
|
+
it("throws when proposalId is missing", async () => {
|
|
27
33
|
await expect(service.createContractFromProposalIntegration({})).rejects.toThrow(common_1.BadRequestException);
|
|
28
34
|
});
|
|
29
|
-
it(
|
|
35
|
+
it("returns the existing contract when the CRM proposal was already converted", async () => {
|
|
30
36
|
const tx = {
|
|
31
37
|
$queryRawUnsafe: jest
|
|
32
38
|
.fn()
|
|
33
39
|
.mockResolvedValueOnce([])
|
|
34
|
-
.mockResolvedValueOnce([{ id: 42, code:
|
|
40
|
+
.mockResolvedValueOnce([{ id: 42, code: "CTR-EXISTING" }]),
|
|
35
41
|
};
|
|
36
42
|
prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
37
43
|
const result = await service.createContractFromProposalIntegration({
|
|
38
44
|
proposalId: 1001,
|
|
39
|
-
title:
|
|
45
|
+
title: "Already converted proposal",
|
|
40
46
|
});
|
|
41
47
|
expect(result).toEqual({
|
|
42
48
|
id: 42,
|
|
43
|
-
code:
|
|
49
|
+
code: "CTR-EXISTING",
|
|
44
50
|
});
|
|
45
51
|
expect(service.generateContractCode).not.toHaveBeenCalled();
|
|
46
52
|
expect(service.replaceContractParties).not.toHaveBeenCalled();
|
|
47
53
|
expect(integrationApi.publishEvent).not.toHaveBeenCalled();
|
|
48
54
|
});
|
|
49
|
-
it(
|
|
55
|
+
it("creates a draft contract with CRM origin metadata and emits an event", async () => {
|
|
50
56
|
const tx = {
|
|
51
57
|
$queryRawUnsafe: jest
|
|
52
58
|
.fn()
|
|
@@ -60,74 +66,74 @@ describe('OperationsService proposal integration', () => {
|
|
|
60
66
|
proposalRevisionId: 21,
|
|
61
67
|
personId: 5,
|
|
62
68
|
approvedByUserId: 9,
|
|
63
|
-
correlationId:
|
|
64
|
-
code:
|
|
65
|
-
title:
|
|
69
|
+
correlationId: "proposal:1001",
|
|
70
|
+
code: "P-1001",
|
|
71
|
+
title: "Implementation Proposal",
|
|
66
72
|
total: 1234,
|
|
67
|
-
currency:
|
|
68
|
-
locale:
|
|
73
|
+
currency: "USD",
|
|
74
|
+
locale: "en",
|
|
69
75
|
commercialTerms: {
|
|
70
|
-
contractCategory:
|
|
71
|
-
contractType:
|
|
72
|
-
billingModel:
|
|
73
|
-
validFrom:
|
|
74
|
-
validUntil:
|
|
75
|
-
notes:
|
|
76
|
+
contractCategory: "client",
|
|
77
|
+
contractType: "service_agreement",
|
|
78
|
+
billingModel: "monthly_retainer",
|
|
79
|
+
validFrom: "2025-01-10",
|
|
80
|
+
validUntil: "2025-12-31",
|
|
81
|
+
notes: "Annual support agreement",
|
|
76
82
|
},
|
|
77
83
|
person: {
|
|
78
84
|
id: 5,
|
|
79
|
-
name:
|
|
80
|
-
tradeName:
|
|
81
|
-
email:
|
|
82
|
-
phone:
|
|
83
|
-
document:
|
|
85
|
+
name: "Acme Contact",
|
|
86
|
+
tradeName: "Acme Ltd",
|
|
87
|
+
email: "ops@acme.test",
|
|
88
|
+
phone: "555-0100",
|
|
89
|
+
document: "12.345.678/0001-90",
|
|
84
90
|
},
|
|
85
91
|
revision: {
|
|
86
92
|
id: 21,
|
|
87
|
-
title:
|
|
88
|
-
summary:
|
|
89
|
-
contentHtml:
|
|
93
|
+
title: "Revision 1",
|
|
94
|
+
summary: "Approved commercial terms",
|
|
95
|
+
contentHtml: "<p>draft</p>",
|
|
90
96
|
},
|
|
91
97
|
items: [
|
|
92
98
|
{
|
|
93
|
-
name:
|
|
94
|
-
description:
|
|
99
|
+
name: "Monthly retainer",
|
|
100
|
+
description: "Support hours",
|
|
95
101
|
amount: 1000,
|
|
96
|
-
recurrence:
|
|
102
|
+
recurrence: "monthly",
|
|
97
103
|
},
|
|
98
104
|
{
|
|
99
|
-
name:
|
|
105
|
+
name: "Setup fee",
|
|
100
106
|
amount: 234,
|
|
101
|
-
recurrence:
|
|
107
|
+
recurrence: "one_time",
|
|
102
108
|
},
|
|
103
109
|
],
|
|
104
110
|
};
|
|
105
111
|
const result = await service.createContractFromProposalIntegration(payload);
|
|
106
112
|
expect(result).toEqual({
|
|
107
113
|
id: 77,
|
|
108
|
-
code:
|
|
114
|
+
code: "CTR-001",
|
|
109
115
|
});
|
|
110
116
|
const insertCall = tx.$queryRawUnsafe.mock.calls[2];
|
|
111
|
-
expect(insertCall[0]).toContain(
|
|
112
|
-
expect(insertCall).toContain(
|
|
113
|
-
expect(insertCall).toContain(
|
|
117
|
+
expect(insertCall[0]).toContain("INSERT INTO operations_contract");
|
|
118
|
+
expect(insertCall).toContain("crm_proposal");
|
|
119
|
+
expect(insertCall).toContain("1001");
|
|
114
120
|
expect(service.replaceContractParties).toHaveBeenCalledWith(tx, 77, [
|
|
115
121
|
expect.objectContaining({
|
|
116
|
-
partyRole:
|
|
117
|
-
displayName:
|
|
122
|
+
partyRole: "client",
|
|
123
|
+
displayName: "Acme Ltd",
|
|
118
124
|
isPrimary: true,
|
|
119
125
|
}),
|
|
120
126
|
]);
|
|
121
|
-
expect(service.insertContractHistory).toHaveBeenCalledWith(tx, 77, 9,
|
|
127
|
+
expect(service.insertContractHistory).toHaveBeenCalledWith(tx, 77, 9, "created", "Draft contract generated from CRM proposal P-1001.", expect.any(String));
|
|
122
128
|
expect(integrationApi.publishEvent).toHaveBeenCalledWith(expect.objectContaining({
|
|
123
|
-
eventName:
|
|
129
|
+
eventName: "operations.contract.created",
|
|
124
130
|
payload: expect.objectContaining({
|
|
125
131
|
proposalId: 1001,
|
|
126
132
|
contract: expect.objectContaining({
|
|
127
133
|
id: 77,
|
|
128
|
-
originType:
|
|
129
|
-
originId:
|
|
130
|
-
contractType:
|
|
134
|
+
originType: "crm_proposal",
|
|
135
|
+
originId: "1001",
|
|
136
|
+
contractType: "service_agreement",
|
|
131
137
|
}),
|
|
132
138
|
}),
|
|
133
139
|
}), expect.objectContaining({
|
|
@@ -135,7 +141,7 @@ describe('OperationsService proposal integration', () => {
|
|
|
135
141
|
}));
|
|
136
142
|
});
|
|
137
143
|
});
|
|
138
|
-
describe(
|
|
144
|
+
describe("OperationsService quick-entry timesheets", () => {
|
|
139
145
|
let service;
|
|
140
146
|
beforeEach(() => {
|
|
141
147
|
service = new operations_service_1.OperationsService({
|
|
@@ -143,11 +149,11 @@ describe('OperationsService quick-entry timesheets', () => {
|
|
|
143
149
|
}, {}, {
|
|
144
150
|
publishEvent: jest.fn().mockResolvedValue(undefined),
|
|
145
151
|
}, {}, {}, new operations_access_service_1.OperationsAccessService());
|
|
146
|
-
jest.spyOn(service,
|
|
152
|
+
jest.spyOn(service, "getActorContext").mockResolvedValue({
|
|
147
153
|
userId: 15,
|
|
148
|
-
roleSlugs: [
|
|
154
|
+
roleSlugs: ["operations-collaborator"],
|
|
149
155
|
collaboratorId: 7,
|
|
150
|
-
collaboratorName:
|
|
156
|
+
collaboratorName: "Taylor Tester",
|
|
151
157
|
isDirector: false,
|
|
152
158
|
isSupervisor: false,
|
|
153
159
|
isCollaborator: true,
|
|
@@ -159,45 +165,55 @@ describe('OperationsService quick-entry timesheets', () => {
|
|
|
159
165
|
afterEach(() => {
|
|
160
166
|
jest.restoreAllMocks();
|
|
161
167
|
});
|
|
162
|
-
it(
|
|
168
|
+
it("rejects quick entries with non-positive duration", async () => {
|
|
163
169
|
await expect(service.createTimesheetEntry(15, {
|
|
164
170
|
projectId: 11,
|
|
165
|
-
workDate:
|
|
171
|
+
workDate: "2026-04-09",
|
|
166
172
|
duration: 0,
|
|
167
|
-
unit:
|
|
173
|
+
unit: "minutes",
|
|
168
174
|
})).rejects.toThrow(common_1.BadRequestException);
|
|
169
175
|
});
|
|
170
|
-
it(
|
|
176
|
+
it("rejects quick entries without a work date", async () => {
|
|
171
177
|
await expect(service.createTimesheetEntry(15, {
|
|
172
178
|
projectId: 11,
|
|
173
179
|
duration: 30,
|
|
174
|
-
unit:
|
|
180
|
+
unit: "minutes",
|
|
175
181
|
})).rejects.toThrow(common_1.BadRequestException);
|
|
176
182
|
});
|
|
177
|
-
it(
|
|
183
|
+
it("stores quick-entry duration in minutes and keeps hours compatible", async () => {
|
|
178
184
|
const tx = {
|
|
179
185
|
$queryRawUnsafe: jest.fn().mockResolvedValue([{ id: 91 }]),
|
|
180
186
|
};
|
|
181
187
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
182
|
-
jest
|
|
188
|
+
jest
|
|
189
|
+
.spyOn(service, "resolveOwnedProjectAssignment")
|
|
190
|
+
.mockResolvedValue({
|
|
183
191
|
id: 33,
|
|
184
192
|
projectId: 11,
|
|
185
|
-
projectName:
|
|
186
|
-
projectCode:
|
|
187
|
-
roleLabel:
|
|
193
|
+
projectName: "Project Atlas",
|
|
194
|
+
projectCode: "OPS-11",
|
|
195
|
+
roleLabel: "Engineer",
|
|
188
196
|
});
|
|
189
|
-
jest.spyOn(service,
|
|
197
|
+
jest.spyOn(service, "getOwnedTaskRecord").mockResolvedValue({
|
|
190
198
|
id: 44,
|
|
191
|
-
name:
|
|
199
|
+
name: "Implement API",
|
|
192
200
|
projectAssignmentId: 33,
|
|
193
201
|
projectId: 11,
|
|
194
|
-
projectName:
|
|
195
|
-
projectCode:
|
|
202
|
+
projectName: "Project Atlas",
|
|
203
|
+
projectCode: "OPS-11",
|
|
196
204
|
});
|
|
197
|
-
jest
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
jest
|
|
205
|
+
jest
|
|
206
|
+
.spyOn(service, "getOrCreateTimesheetForWorkDate")
|
|
207
|
+
.mockResolvedValue(55);
|
|
208
|
+
jest
|
|
209
|
+
.spyOn(service, "refreshTimesheetTotal")
|
|
210
|
+
.mockResolvedValue(undefined);
|
|
211
|
+
jest
|
|
212
|
+
.spyOn(service, "submitTimesheetForApproval")
|
|
213
|
+
.mockResolvedValue(undefined);
|
|
214
|
+
jest
|
|
215
|
+
.spyOn(service, "getTimesheetEntryByIdForActor")
|
|
216
|
+
.mockResolvedValue({
|
|
201
217
|
id: 91,
|
|
202
218
|
durationMinutes: 90,
|
|
203
219
|
hours: 1.5,
|
|
@@ -205,58 +221,60 @@ describe('OperationsService quick-entry timesheets', () => {
|
|
|
205
221
|
const result = await service.createTimesheetEntry(15, {
|
|
206
222
|
projectId: 11,
|
|
207
223
|
taskId: 44,
|
|
208
|
-
workDate:
|
|
224
|
+
workDate: "2026-04-09",
|
|
209
225
|
duration: 1.5,
|
|
210
|
-
unit:
|
|
211
|
-
description:
|
|
226
|
+
unit: "hours",
|
|
227
|
+
description: "Worked on the API layer",
|
|
212
228
|
});
|
|
213
229
|
expect(result).toEqual(expect.objectContaining({
|
|
214
230
|
id: 91,
|
|
215
231
|
durationMinutes: 90,
|
|
216
232
|
hours: 1.5,
|
|
217
233
|
}));
|
|
218
|
-
expect(tx.$queryRawUnsafe).toHaveBeenCalledWith(expect.stringContaining(
|
|
234
|
+
expect(tx.$queryRawUnsafe).toHaveBeenCalledWith(expect.stringContaining("INSERT INTO operations_timesheet_entry"), 55, 33, 44, "Implement API", "2026-04-09", 90, 1.5, "Worked on the API layer");
|
|
219
235
|
expect(service.submitTimesheetForApproval).toHaveBeenCalledWith(tx, 55, 7);
|
|
220
236
|
});
|
|
221
|
-
it(
|
|
237
|
+
it("uses the task assignment when full timesheet entries omit projectAssignmentId", async () => {
|
|
222
238
|
const tx = {
|
|
223
239
|
$queryRawUnsafe: jest.fn().mockResolvedValue([{ id: 55 }]),
|
|
224
240
|
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
225
241
|
};
|
|
226
242
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
227
|
-
jest.spyOn(service,
|
|
243
|
+
jest.spyOn(service, "getCollaboratorById").mockResolvedValue({
|
|
228
244
|
id: 7,
|
|
229
245
|
supervisorId: 18,
|
|
230
246
|
});
|
|
231
|
-
jest.spyOn(service,
|
|
247
|
+
jest.spyOn(service, "getOwnedTaskRecord").mockResolvedValue({
|
|
232
248
|
id: 44,
|
|
233
|
-
name:
|
|
249
|
+
name: "Implement API",
|
|
234
250
|
projectAssignmentId: 33,
|
|
235
251
|
projectId: 11,
|
|
236
|
-
projectName:
|
|
237
|
-
projectCode:
|
|
252
|
+
projectName: "Project Atlas",
|
|
253
|
+
projectCode: "OPS-11",
|
|
238
254
|
});
|
|
239
|
-
jest
|
|
240
|
-
|
|
255
|
+
jest
|
|
256
|
+
.spyOn(service, "refreshTimesheetTotal")
|
|
257
|
+
.mockResolvedValue(undefined);
|
|
258
|
+
jest.spyOn(service, "listSingleTimesheet").mockResolvedValue({
|
|
241
259
|
id: 55,
|
|
242
|
-
status:
|
|
260
|
+
status: "draft",
|
|
243
261
|
});
|
|
244
262
|
await service.createTimesheet(15, {
|
|
245
|
-
weekStartDate:
|
|
246
|
-
weekEndDate:
|
|
263
|
+
weekStartDate: "2026-04-06",
|
|
264
|
+
weekEndDate: "2026-04-12",
|
|
247
265
|
entries: [
|
|
248
266
|
{
|
|
249
267
|
taskId: 44,
|
|
250
|
-
workDate:
|
|
268
|
+
workDate: "2026-04-09",
|
|
251
269
|
duration: 2,
|
|
252
|
-
unit:
|
|
253
|
-
description:
|
|
270
|
+
unit: "hours",
|
|
271
|
+
description: "Worked on the API layer",
|
|
254
272
|
},
|
|
255
273
|
],
|
|
256
274
|
});
|
|
257
|
-
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining(
|
|
275
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining("INSERT INTO operations_timesheet_entry"), 55, 33, 44, "Implement API", "2026-04-09", 120, 2, "Worked on the API layer");
|
|
258
276
|
});
|
|
259
|
-
it(
|
|
277
|
+
it("rejects submitting a timesheet without a resolved approver", async () => {
|
|
260
278
|
const tx = {
|
|
261
279
|
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
262
280
|
$queryRawUnsafe: jest
|
|
@@ -265,24 +283,24 @@ describe('OperationsService quick-entry timesheets', () => {
|
|
|
265
283
|
.mockResolvedValueOnce([{ exists: true }]),
|
|
266
284
|
};
|
|
267
285
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
268
|
-
jest.spyOn(service,
|
|
286
|
+
jest.spyOn(service, "getTimesheetById").mockResolvedValue({
|
|
269
287
|
id: 55,
|
|
270
288
|
collaboratorId: 7,
|
|
271
289
|
approverCollaboratorId: null,
|
|
272
|
-
status:
|
|
290
|
+
status: "draft",
|
|
273
291
|
});
|
|
274
|
-
jest.spyOn(service,
|
|
292
|
+
jest.spyOn(service, "getCollaboratorById").mockResolvedValue({
|
|
275
293
|
id: 7,
|
|
276
294
|
supervisorId: null,
|
|
277
295
|
});
|
|
278
|
-
jest.spyOn(service,
|
|
296
|
+
jest.spyOn(service, "listSingleTimesheet").mockResolvedValue({
|
|
279
297
|
id: 55,
|
|
280
|
-
status:
|
|
298
|
+
status: "submitted",
|
|
281
299
|
});
|
|
282
300
|
await expect(service.submitTimesheet(15, 55)).rejects.toThrow(common_1.BadRequestException);
|
|
283
301
|
expect(tx.$executeRawUnsafe).not.toHaveBeenCalled();
|
|
284
302
|
});
|
|
285
|
-
it(
|
|
303
|
+
it("rejects submitting a timesheet without active entries", async () => {
|
|
286
304
|
const tx = {
|
|
287
305
|
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
288
306
|
$queryRawUnsafe: jest
|
|
@@ -291,49 +309,54 @@ describe('OperationsService quick-entry timesheets', () => {
|
|
|
291
309
|
.mockResolvedValueOnce([{ exists: false }]),
|
|
292
310
|
};
|
|
293
311
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
294
|
-
jest.spyOn(service,
|
|
312
|
+
jest.spyOn(service, "getTimesheetById").mockResolvedValue({
|
|
295
313
|
id: 56,
|
|
296
314
|
collaboratorId: 7,
|
|
297
315
|
approverCollaboratorId: 18,
|
|
298
|
-
status:
|
|
316
|
+
status: "draft",
|
|
299
317
|
});
|
|
300
|
-
jest.spyOn(service,
|
|
318
|
+
jest.spyOn(service, "getCollaboratorById").mockResolvedValue({
|
|
301
319
|
id: 7,
|
|
302
320
|
supervisorId: 18,
|
|
303
321
|
});
|
|
304
|
-
jest.spyOn(service,
|
|
322
|
+
jest.spyOn(service, "listSingleTimesheet").mockResolvedValue({
|
|
305
323
|
id: 56,
|
|
306
|
-
status:
|
|
324
|
+
status: "submitted",
|
|
307
325
|
});
|
|
308
326
|
await expect(service.submitTimesheet(15, 56)).rejects.toThrow(common_1.BadRequestException);
|
|
309
327
|
expect(tx.$executeRawUnsafe).not.toHaveBeenCalled();
|
|
310
328
|
});
|
|
311
|
-
it(
|
|
329
|
+
it("deletes only draft quick entries and refreshes the weekly total", async () => {
|
|
312
330
|
const tx = {
|
|
313
331
|
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
332
|
+
$queryRawUnsafe: jest.fn().mockResolvedValue([]),
|
|
314
333
|
};
|
|
315
334
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
316
|
-
jest
|
|
335
|
+
jest
|
|
336
|
+
.spyOn(service, "getTimesheetEntryByIdForActor")
|
|
337
|
+
.mockResolvedValue({
|
|
317
338
|
id: 91,
|
|
318
339
|
timesheetId: 55,
|
|
319
340
|
collaboratorId: 7,
|
|
320
|
-
status:
|
|
341
|
+
status: "draft",
|
|
321
342
|
});
|
|
322
|
-
jest
|
|
343
|
+
jest
|
|
344
|
+
.spyOn(service, "refreshTimesheetTotal")
|
|
345
|
+
.mockResolvedValue(undefined);
|
|
323
346
|
await expect(service.removeTimesheetEntry(15, 91)).resolves.toEqual({
|
|
324
347
|
success: true,
|
|
325
348
|
});
|
|
326
|
-
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining(
|
|
349
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining("UPDATE operations_timesheet_entry"), 91);
|
|
327
350
|
expect(service.refreshTimesheetTotal).toHaveBeenCalledWith(tx, 55);
|
|
328
351
|
});
|
|
329
|
-
it(
|
|
352
|
+
it("updates quick entries and moves them to the correct weekly timesheet", async () => {
|
|
330
353
|
const tx = {
|
|
331
354
|
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
332
355
|
$queryRawUnsafe: jest.fn().mockResolvedValue([]),
|
|
333
356
|
};
|
|
334
357
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
335
358
|
jest
|
|
336
|
-
.spyOn(service,
|
|
359
|
+
.spyOn(service, "getTimesheetEntryByIdForActor")
|
|
337
360
|
.mockResolvedValueOnce({
|
|
338
361
|
id: 93,
|
|
339
362
|
timesheetId: 55,
|
|
@@ -341,10 +364,10 @@ describe('OperationsService quick-entry timesheets', () => {
|
|
|
341
364
|
projectId: 11,
|
|
342
365
|
projectAssignmentId: 33,
|
|
343
366
|
taskId: 44,
|
|
344
|
-
status:
|
|
345
|
-
weekStartDate:
|
|
346
|
-
weekEndDate:
|
|
347
|
-
workDate:
|
|
367
|
+
status: "draft",
|
|
368
|
+
weekStartDate: "2026-04-06",
|
|
369
|
+
weekEndDate: "2026-04-12",
|
|
370
|
+
workDate: "2026-04-09",
|
|
348
371
|
hours: 1,
|
|
349
372
|
durationMinutes: 60,
|
|
350
373
|
})
|
|
@@ -355,80 +378,222 @@ describe('OperationsService quick-entry timesheets', () => {
|
|
|
355
378
|
projectId: 12,
|
|
356
379
|
projectAssignmentId: 34,
|
|
357
380
|
taskId: 45,
|
|
358
|
-
status:
|
|
359
|
-
weekStartDate:
|
|
360
|
-
weekEndDate:
|
|
361
|
-
workDate:
|
|
381
|
+
status: "draft",
|
|
382
|
+
weekStartDate: "2026-04-13",
|
|
383
|
+
weekEndDate: "2026-04-19",
|
|
384
|
+
workDate: "2026-04-14",
|
|
362
385
|
hours: 2,
|
|
363
386
|
durationMinutes: 120,
|
|
364
|
-
taskName:
|
|
387
|
+
taskName: "Review backlog",
|
|
365
388
|
});
|
|
366
|
-
jest
|
|
389
|
+
jest
|
|
390
|
+
.spyOn(service, "resolveOwnedProjectAssignment")
|
|
391
|
+
.mockResolvedValue({
|
|
367
392
|
id: 34,
|
|
368
393
|
projectId: 12,
|
|
369
|
-
projectName:
|
|
370
|
-
projectCode:
|
|
371
|
-
roleLabel:
|
|
394
|
+
projectName: "Project Boreal",
|
|
395
|
+
projectCode: "OPS-12",
|
|
396
|
+
roleLabel: "Engineer",
|
|
372
397
|
});
|
|
373
|
-
jest.spyOn(service,
|
|
398
|
+
jest.spyOn(service, "getOwnedTaskRecord").mockResolvedValue({
|
|
374
399
|
id: 45,
|
|
375
|
-
name:
|
|
400
|
+
name: "Review backlog",
|
|
376
401
|
projectAssignmentId: 34,
|
|
377
402
|
projectId: 12,
|
|
378
|
-
projectName:
|
|
379
|
-
projectCode:
|
|
403
|
+
projectName: "Project Boreal",
|
|
404
|
+
projectCode: "OPS-12",
|
|
380
405
|
});
|
|
381
|
-
jest
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
jest
|
|
406
|
+
jest
|
|
407
|
+
.spyOn(service, "getOrCreateTimesheetForWorkDate")
|
|
408
|
+
.mockResolvedValue(77);
|
|
409
|
+
jest
|
|
410
|
+
.spyOn(service, "refreshTimesheetTotal")
|
|
411
|
+
.mockResolvedValue(undefined);
|
|
412
|
+
jest
|
|
413
|
+
.spyOn(service, "cleanupEmptyEditableTimesheet")
|
|
414
|
+
.mockResolvedValue(undefined);
|
|
415
|
+
jest
|
|
416
|
+
.spyOn(service, "submitTimesheetForApproval")
|
|
417
|
+
.mockResolvedValue(undefined);
|
|
385
418
|
const result = await service.updateTimesheetEntry(15, 93, {
|
|
386
419
|
projectId: 12,
|
|
387
420
|
projectAssignmentId: 34,
|
|
388
421
|
taskId: 45,
|
|
389
|
-
workDate:
|
|
422
|
+
workDate: "2026-04-14",
|
|
390
423
|
duration: 2,
|
|
391
|
-
unit:
|
|
392
|
-
description:
|
|
424
|
+
unit: "hours",
|
|
425
|
+
description: "Backlog refinement",
|
|
393
426
|
});
|
|
394
427
|
expect(result).toEqual(expect.objectContaining({
|
|
395
428
|
id: 93,
|
|
396
429
|
timesheetId: 77,
|
|
397
430
|
}));
|
|
398
|
-
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining(
|
|
431
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining("UPDATE operations_timesheet_entry"), 77, 34, 45, "Review backlog", "2026-04-14", 120, 2, "Backlog refinement", 93);
|
|
399
432
|
expect(service.refreshTimesheetTotal).toHaveBeenCalledWith(tx, 55);
|
|
400
433
|
expect(service.refreshTimesheetTotal).toHaveBeenCalledWith(tx, 77);
|
|
401
434
|
expect(service.cleanupEmptyEditableTimesheet).toHaveBeenCalledWith(tx, 55);
|
|
402
435
|
expect(service.submitTimesheetForApproval).toHaveBeenCalledWith(tx, 77, 7);
|
|
403
436
|
});
|
|
404
|
-
it(
|
|
437
|
+
it("allows deleting entries from submitted timesheets until approval", async () => {
|
|
405
438
|
const tx = {
|
|
406
439
|
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
407
440
|
};
|
|
408
441
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
409
|
-
jest
|
|
442
|
+
jest
|
|
443
|
+
.spyOn(service, "getTimesheetEntryByIdForActor")
|
|
444
|
+
.mockResolvedValue({
|
|
410
445
|
id: 92,
|
|
411
446
|
timesheetId: 56,
|
|
412
447
|
collaboratorId: 7,
|
|
413
|
-
status:
|
|
448
|
+
status: "submitted",
|
|
414
449
|
});
|
|
415
|
-
jest
|
|
416
|
-
|
|
450
|
+
jest
|
|
451
|
+
.spyOn(service, "refreshTimesheetTotal")
|
|
452
|
+
.mockResolvedValue(undefined);
|
|
453
|
+
jest
|
|
454
|
+
.spyOn(service, "cleanupEmptyEditableTimesheet")
|
|
455
|
+
.mockResolvedValue(undefined);
|
|
417
456
|
await expect(service.removeTimesheetEntry(15, 92)).resolves.toEqual({
|
|
418
457
|
success: true,
|
|
419
458
|
});
|
|
420
459
|
});
|
|
421
|
-
it(
|
|
422
|
-
jest
|
|
460
|
+
it("rejects deleting entries from approved timesheets", async () => {
|
|
461
|
+
jest
|
|
462
|
+
.spyOn(service, "getTimesheetEntryByIdForActor")
|
|
463
|
+
.mockResolvedValue({
|
|
423
464
|
id: 94,
|
|
424
465
|
timesheetId: 57,
|
|
425
466
|
collaboratorId: 7,
|
|
426
|
-
status:
|
|
467
|
+
status: "approved",
|
|
427
468
|
});
|
|
428
469
|
await expect(service.removeTimesheetEntry(15, 94)).rejects.toThrow(common_1.BadRequestException);
|
|
429
470
|
});
|
|
430
471
|
});
|
|
431
|
-
describe(
|
|
472
|
+
describe("OperationsService task column activity", () => {
|
|
473
|
+
let service;
|
|
474
|
+
let prisma;
|
|
475
|
+
let integrationApi;
|
|
476
|
+
const baseTask = {
|
|
477
|
+
id: 44,
|
|
478
|
+
name: "Implement board",
|
|
479
|
+
description: null,
|
|
480
|
+
priority: "medium",
|
|
481
|
+
status: "todo",
|
|
482
|
+
dueDate: null,
|
|
483
|
+
estimateHours: null,
|
|
484
|
+
position: 0,
|
|
485
|
+
tags: null,
|
|
486
|
+
assigneeCollaboratorId: null,
|
|
487
|
+
projectAssignmentId: null,
|
|
488
|
+
projectId: 11,
|
|
489
|
+
projectName: "Project Atlas",
|
|
490
|
+
projectCode: "OPS-11",
|
|
491
|
+
doingStartedAt: null,
|
|
492
|
+
totalDoingMinutes: 0,
|
|
493
|
+
deletedAt: null,
|
|
494
|
+
};
|
|
495
|
+
beforeEach(() => {
|
|
496
|
+
prisma = {
|
|
497
|
+
$transaction: jest.fn(),
|
|
498
|
+
};
|
|
499
|
+
integrationApi = {
|
|
500
|
+
publishEvent: jest.fn().mockResolvedValue(undefined),
|
|
501
|
+
};
|
|
502
|
+
service = new operations_service_1.OperationsService(prisma, {}, integrationApi, {}, {}, new operations_access_service_1.OperationsAccessService());
|
|
503
|
+
jest.spyOn(service, "getActorContext").mockResolvedValue({
|
|
504
|
+
userId: 15,
|
|
505
|
+
roleSlugs: ["operations-collaborator"],
|
|
506
|
+
collaboratorId: 7,
|
|
507
|
+
collaboratorName: "Taylor Tester",
|
|
508
|
+
isDirector: false,
|
|
509
|
+
isSupervisor: false,
|
|
510
|
+
isCollaborator: true,
|
|
511
|
+
teamCollaboratorIds: [],
|
|
512
|
+
visibleCollaboratorIds: [7],
|
|
513
|
+
visibleProjectIds: [11],
|
|
514
|
+
});
|
|
515
|
+
jest
|
|
516
|
+
.spyOn(service, "assertProjectAccess")
|
|
517
|
+
.mockResolvedValue(undefined);
|
|
518
|
+
jest.spyOn(service, "getProjectBoardTask").mockResolvedValue(Object.assign(Object.assign({}, baseTask), { assigneeCollaboratorId: 7, status: "doing" }));
|
|
519
|
+
});
|
|
520
|
+
afterEach(() => {
|
|
521
|
+
jest.restoreAllMocks();
|
|
522
|
+
});
|
|
523
|
+
function mockTransaction() {
|
|
524
|
+
const tx = {
|
|
525
|
+
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
526
|
+
};
|
|
527
|
+
prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
528
|
+
return tx;
|
|
529
|
+
}
|
|
530
|
+
it("records todo to doing and auto-assigns the actor", async () => {
|
|
531
|
+
const tx = mockTransaction();
|
|
532
|
+
jest
|
|
533
|
+
.spyOn(service, "getTaskRecordForActor")
|
|
534
|
+
.mockResolvedValue(baseTask);
|
|
535
|
+
await service.updateTask(15, 44, { status: "doing" });
|
|
536
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledTimes(2);
|
|
537
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][0]).toContain("doing_started_at = CASE");
|
|
538
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][3]).toBe(7);
|
|
539
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][15]).toBe(true);
|
|
540
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][16]).toBe(false);
|
|
541
|
+
expect(tx.$executeRawUnsafe.mock.calls[1][0]).toContain("INSERT INTO operations_task_activity");
|
|
542
|
+
expect(tx.$executeRawUnsafe.mock.calls[1].slice(1)).toEqual([
|
|
543
|
+
44,
|
|
544
|
+
7,
|
|
545
|
+
"status_changed",
|
|
546
|
+
"todo",
|
|
547
|
+
"doing",
|
|
548
|
+
]);
|
|
549
|
+
});
|
|
550
|
+
it("records doing to review and closes accumulated doing minutes", async () => {
|
|
551
|
+
const tx = mockTransaction();
|
|
552
|
+
jest.spyOn(service, "getTaskRecordForActor").mockResolvedValue(Object.assign(Object.assign({}, baseTask), { status: "doing", assigneeCollaboratorId: 9, doingStartedAt: "2026-05-16T10:00:00.000Z", totalDoingMinutes: 15 }));
|
|
553
|
+
await service.updateTask(15, 44, { status: "review" });
|
|
554
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledTimes(2);
|
|
555
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][3]).toBe(9);
|
|
556
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][15]).toBe(false);
|
|
557
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][16]).toBe(true);
|
|
558
|
+
expect(tx.$executeRawUnsafe.mock.calls[1].slice(1)).toEqual([
|
|
559
|
+
44,
|
|
560
|
+
7,
|
|
561
|
+
"status_changed",
|
|
562
|
+
"doing",
|
|
563
|
+
"review",
|
|
564
|
+
]);
|
|
565
|
+
});
|
|
566
|
+
it("blocks moving advanced without a linked collaborator when no assignee exists", async () => {
|
|
567
|
+
prisma.$transaction.mockImplementation(async (callback) => callback({ $executeRawUnsafe: jest.fn() }));
|
|
568
|
+
jest.spyOn(service, "getActorContext").mockResolvedValue({
|
|
569
|
+
userId: 15,
|
|
570
|
+
roleSlugs: ["admin"],
|
|
571
|
+
collaboratorId: null,
|
|
572
|
+
collaboratorName: null,
|
|
573
|
+
isDirector: true,
|
|
574
|
+
isSupervisor: true,
|
|
575
|
+
isCollaborator: true,
|
|
576
|
+
teamCollaboratorIds: [],
|
|
577
|
+
visibleCollaboratorIds: [],
|
|
578
|
+
visibleProjectIds: [],
|
|
579
|
+
});
|
|
580
|
+
jest
|
|
581
|
+
.spyOn(service, "getTaskRecordForActor")
|
|
582
|
+
.mockResolvedValue(baseTask);
|
|
583
|
+
await expect(service.updateTask(15, 44, { status: "review" })).rejects.toThrow(common_1.BadRequestException);
|
|
584
|
+
});
|
|
585
|
+
it("does not create activity when the status does not change", async () => {
|
|
586
|
+
const tx = mockTransaction();
|
|
587
|
+
jest
|
|
588
|
+
.spyOn(service, "getTaskRecordForActor")
|
|
589
|
+
.mockResolvedValue(baseTask);
|
|
590
|
+
await service.updateTask(15, 44, { status: "todo" });
|
|
591
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledTimes(1);
|
|
592
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][15]).toBe(false);
|
|
593
|
+
expect(tx.$executeRawUnsafe.mock.calls[0][16]).toBe(false);
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
describe("OperationsService approval side effects", () => {
|
|
432
597
|
let service;
|
|
433
598
|
beforeEach(() => {
|
|
434
599
|
service = new operations_service_1.OperationsService({
|
|
@@ -436,11 +601,11 @@ describe('OperationsService approval side effects', () => {
|
|
|
436
601
|
}, {}, {
|
|
437
602
|
publishEvent: jest.fn().mockResolvedValue(undefined),
|
|
438
603
|
}, {}, {}, new operations_access_service_1.OperationsAccessService());
|
|
439
|
-
jest.spyOn(service,
|
|
604
|
+
jest.spyOn(service, "getActorContext").mockResolvedValue({
|
|
440
605
|
userId: 22,
|
|
441
|
-
roleSlugs: [
|
|
606
|
+
roleSlugs: ["admin-operations-supervisor"],
|
|
442
607
|
collaboratorId: 18,
|
|
443
|
-
collaboratorName:
|
|
608
|
+
collaboratorName: "Sam Supervisor",
|
|
444
609
|
isDirector: false,
|
|
445
610
|
isSupervisor: true,
|
|
446
611
|
isCollaborator: false,
|
|
@@ -452,7 +617,7 @@ describe('OperationsService approval side effects', () => {
|
|
|
452
617
|
afterEach(() => {
|
|
453
618
|
jest.restoreAllMocks();
|
|
454
619
|
});
|
|
455
|
-
it(
|
|
620
|
+
it("applies approved permanent schedule adjustments to the active collaborator weekly schedule", async () => {
|
|
456
621
|
const tx = {
|
|
457
622
|
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
458
623
|
$queryRawUnsafe: jest
|
|
@@ -460,19 +625,19 @@ describe('OperationsService approval side effects', () => {
|
|
|
460
625
|
.mockResolvedValueOnce([
|
|
461
626
|
{
|
|
462
627
|
collaboratorId: 7,
|
|
463
|
-
requestScope:
|
|
628
|
+
requestScope: "permanent",
|
|
464
629
|
},
|
|
465
630
|
])
|
|
466
631
|
.mockResolvedValueOnce([
|
|
467
632
|
{
|
|
468
|
-
weekday:
|
|
633
|
+
weekday: "monday",
|
|
469
634
|
isWorkingDay: true,
|
|
470
|
-
startTime:
|
|
471
|
-
endTime:
|
|
635
|
+
startTime: "08:00",
|
|
636
|
+
endTime: "17:00",
|
|
472
637
|
breakMinutes: 45,
|
|
473
638
|
},
|
|
474
639
|
{
|
|
475
|
-
weekday:
|
|
640
|
+
weekday: "friday",
|
|
476
641
|
isWorkingDay: false,
|
|
477
642
|
startTime: null,
|
|
478
643
|
endTime: null,
|
|
@@ -481,68 +646,74 @@ describe('OperationsService approval side effects', () => {
|
|
|
481
646
|
]),
|
|
482
647
|
};
|
|
483
648
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
484
|
-
jest
|
|
649
|
+
jest
|
|
650
|
+
.spyOn(service, "querySingle")
|
|
485
651
|
.mockResolvedValueOnce({
|
|
486
652
|
id: 81,
|
|
487
|
-
targetType:
|
|
653
|
+
targetType: "schedule_adjustment_request",
|
|
488
654
|
targetId: 400,
|
|
489
655
|
requesterCollaboratorId: 7,
|
|
490
656
|
approverCollaboratorId: 18,
|
|
491
|
-
status:
|
|
657
|
+
status: "pending",
|
|
492
658
|
})
|
|
493
659
|
.mockResolvedValueOnce({
|
|
494
660
|
id: 81,
|
|
495
|
-
targetType:
|
|
661
|
+
targetType: "schedule_adjustment_request",
|
|
496
662
|
targetId: 400,
|
|
497
|
-
status:
|
|
498
|
-
decidedAt:
|
|
499
|
-
decisionNote:
|
|
663
|
+
status: "approved",
|
|
664
|
+
decidedAt: "2026-04-11T12:00:00.000Z",
|
|
665
|
+
decisionNote: "Approved for the new weekly schedule.",
|
|
500
666
|
});
|
|
501
|
-
jest
|
|
667
|
+
jest
|
|
668
|
+
.spyOn(service, "insertApprovalHistory")
|
|
669
|
+
.mockResolvedValue(undefined);
|
|
502
670
|
await expect(service.approve(22, 81, {
|
|
503
|
-
note:
|
|
671
|
+
note: "Approved for the new weekly schedule.",
|
|
504
672
|
})).resolves.toEqual(expect.objectContaining({
|
|
505
673
|
id: 81,
|
|
506
|
-
status:
|
|
674
|
+
status: "approved",
|
|
507
675
|
}));
|
|
508
|
-
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining(
|
|
509
|
-
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining(
|
|
510
|
-
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining(
|
|
511
|
-
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining(
|
|
676
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining("UPDATE operations_schedule_adjustment_request"), "approved", 18, 400);
|
|
677
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining("UPDATE operations_collaborator_schedule_day"), 7);
|
|
678
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining("INSERT INTO operations_collaborator_schedule_day"), 7, "monday", true, "08:00", "17:00", 45);
|
|
679
|
+
expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(expect.stringContaining("INSERT INTO operations_collaborator_schedule_day"), 7, "friday", false, null, null, 0);
|
|
512
680
|
});
|
|
513
|
-
it(
|
|
681
|
+
it("keeps the collaborator weekly schedule unchanged for approved temporary adjustments", async () => {
|
|
514
682
|
const tx = {
|
|
515
683
|
$executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
|
|
516
684
|
$queryRawUnsafe: jest.fn().mockResolvedValueOnce([
|
|
517
685
|
{
|
|
518
686
|
collaboratorId: 7,
|
|
519
|
-
requestScope:
|
|
687
|
+
requestScope: "temporary",
|
|
520
688
|
},
|
|
521
689
|
]),
|
|
522
690
|
};
|
|
523
691
|
service.prisma.$transaction.mockImplementation(async (callback) => callback(tx));
|
|
524
|
-
jest
|
|
692
|
+
jest
|
|
693
|
+
.spyOn(service, "querySingle")
|
|
525
694
|
.mockResolvedValueOnce({
|
|
526
695
|
id: 82,
|
|
527
|
-
targetType:
|
|
696
|
+
targetType: "schedule_adjustment_request",
|
|
528
697
|
targetId: 401,
|
|
529
698
|
requesterCollaboratorId: 7,
|
|
530
699
|
approverCollaboratorId: 18,
|
|
531
|
-
status:
|
|
700
|
+
status: "pending",
|
|
532
701
|
})
|
|
533
702
|
.mockResolvedValueOnce({
|
|
534
703
|
id: 82,
|
|
535
|
-
targetType:
|
|
704
|
+
targetType: "schedule_adjustment_request",
|
|
536
705
|
targetId: 401,
|
|
537
|
-
status:
|
|
538
|
-
decidedAt:
|
|
539
|
-
decisionNote:
|
|
706
|
+
status: "approved",
|
|
707
|
+
decidedAt: "2026-04-11T12:05:00.000Z",
|
|
708
|
+
decisionNote: "Approved as a temporary exception.",
|
|
540
709
|
});
|
|
541
|
-
jest
|
|
710
|
+
jest
|
|
711
|
+
.spyOn(service, "insertApprovalHistory")
|
|
712
|
+
.mockResolvedValue(undefined);
|
|
542
713
|
await service.approve(22, 82, {
|
|
543
|
-
note:
|
|
714
|
+
note: "Approved as a temporary exception.",
|
|
544
715
|
});
|
|
545
|
-
expect(tx.$executeRawUnsafe).not.toHaveBeenCalledWith(expect.stringContaining(
|
|
716
|
+
expect(tx.$executeRawUnsafe).not.toHaveBeenCalledWith(expect.stringContaining("UPDATE operations_collaborator_schedule_day"), 7);
|
|
546
717
|
});
|
|
547
718
|
});
|
|
548
719
|
//# sourceMappingURL=operations.service.spec.js.map
|