feat(api): complete M5 swap and dispatch backend slice
This commit is contained in:
@@ -34,6 +34,8 @@ import {
|
||||
listCostCenters,
|
||||
listCoreTeam,
|
||||
listCoverageByDate,
|
||||
listCoverageDispatchCandidates,
|
||||
listCoverageDispatchTeams,
|
||||
listCompletedShifts,
|
||||
listEmergencyContacts,
|
||||
listFaqCategories,
|
||||
@@ -44,6 +46,7 @@ import {
|
||||
listOpenShifts,
|
||||
listTaxForms,
|
||||
listTimeCardEntries,
|
||||
listSwapRequests,
|
||||
listOrderItemsByDateRange,
|
||||
listPaymentsHistory,
|
||||
listPendingAssignments,
|
||||
@@ -99,6 +102,8 @@ const defaultQueryService = {
|
||||
listCostCenters,
|
||||
listCoreTeam,
|
||||
listCoverageByDate,
|
||||
listCoverageDispatchCandidates,
|
||||
listCoverageDispatchTeams,
|
||||
listCompletedShifts,
|
||||
listEmergencyContacts,
|
||||
listFaqCategories,
|
||||
@@ -109,6 +114,7 @@ const defaultQueryService = {
|
||||
listOpenShifts,
|
||||
listTaxForms,
|
||||
listTimeCardEntries,
|
||||
listSwapRequests,
|
||||
listOrderItemsByDateRange,
|
||||
listPaymentsHistory,
|
||||
listPendingAssignments,
|
||||
@@ -266,6 +272,33 @@ export function createMobileQueryRouter(queryService = defaultQueryService) {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/client/coverage/swap-requests', requireAuth, requirePolicy('coverage.read', 'coverage'), async (req, res, next) => {
|
||||
try {
|
||||
const items = await queryService.listSwapRequests(req.actor.uid, req.query);
|
||||
return res.status(200).json({ items, requestId: req.requestId });
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/client/coverage/dispatch-teams', requireAuth, requirePolicy('coverage.read', 'coverage'), async (req, res, next) => {
|
||||
try {
|
||||
const items = await queryService.listCoverageDispatchTeams(req.actor.uid, req.query);
|
||||
return res.status(200).json({ items, requestId: req.requestId });
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/client/coverage/dispatch-candidates', requireAuth, requirePolicy('coverage.read', 'coverage'), async (req, res, next) => {
|
||||
try {
|
||||
const items = await queryService.listCoverageDispatchCandidates(req.actor.uid, req.query);
|
||||
return res.status(200).json({ items, requestId: req.requestId });
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/client/hubs', requireAuth, requirePolicy('hubs.read', 'hub'), async (req, res, next) => {
|
||||
try {
|
||||
const items = await queryService.listHubs(req.actor.uid);
|
||||
|
||||
@@ -906,6 +906,7 @@ export async function listOpenShifts(actorUid, { limit, search } = {}) {
|
||||
SELECT
|
||||
s.id AS "shiftId",
|
||||
sr.id AS "roleId",
|
||||
NULL::uuid AS "swapRequestId",
|
||||
b.business_name AS "clientName",
|
||||
sr.role_name AS "roleName",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
@@ -932,12 +933,40 @@ export async function listOpenShifts(actorUid, { limit, search } = {}) {
|
||||
) AS "totalRate",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType",
|
||||
FALSE AS "instantBook",
|
||||
sr.workers_needed AS "requiredWorkerCount"
|
||||
sr.workers_needed AS "requiredWorkerCount",
|
||||
COALESCE(dispatch.team_type, 'MARKETPLACE') AS "dispatchTeam",
|
||||
COALESCE(dispatch.priority, 3) AS "dispatchPriority"
|
||||
FROM shifts s
|
||||
JOIN shift_roles sr ON sr.shift_id = s.id
|
||||
JOIN orders o ON o.id = s.order_id
|
||||
JOIN businesses b ON b.id = s.business_id
|
||||
LEFT JOIN clock_points cp ON cp.id = s.clock_point_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
dtm.team_type,
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END AS priority
|
||||
FROM dispatch_team_memberships dtm
|
||||
WHERE dtm.tenant_id = $1
|
||||
AND dtm.business_id = s.business_id
|
||||
AND dtm.staff_id = $3
|
||||
AND dtm.status = 'ACTIVE'
|
||||
AND dtm.effective_at <= NOW()
|
||||
AND (dtm.expires_at IS NULL OR dtm.expires_at > NOW())
|
||||
AND (dtm.hub_id IS NULL OR dtm.hub_id = s.clock_point_id)
|
||||
ORDER BY
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END ASC,
|
||||
CASE WHEN dtm.hub_id = s.clock_point_id THEN 0 ELSE 1 END ASC,
|
||||
dtm.created_at ASC
|
||||
LIMIT 1
|
||||
) dispatch ON TRUE
|
||||
WHERE s.tenant_id = $1
|
||||
AND s.status = 'OPEN'
|
||||
AND sr.role_code = $4
|
||||
@@ -954,6 +983,7 @@ export async function listOpenShifts(actorUid, { limit, search } = {}) {
|
||||
SELECT
|
||||
s.id AS "shiftId",
|
||||
sr.id AS "roleId",
|
||||
ssr.id AS "swapRequestId",
|
||||
b.business_name AS "clientName",
|
||||
sr.role_name AS "roleName",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
@@ -980,14 +1010,45 @@ export async function listOpenShifts(actorUid, { limit, search } = {}) {
|
||||
) AS "totalRate",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType",
|
||||
FALSE AS "instantBook",
|
||||
1::INTEGER AS "requiredWorkerCount"
|
||||
FROM assignments a
|
||||
1::INTEGER AS "requiredWorkerCount",
|
||||
COALESCE(dispatch.team_type, 'MARKETPLACE') AS "dispatchTeam",
|
||||
COALESCE(dispatch.priority, 3) AS "dispatchPriority"
|
||||
FROM shift_swap_requests ssr
|
||||
JOIN assignments a ON a.id = ssr.original_assignment_id
|
||||
JOIN shifts s ON s.id = a.shift_id
|
||||
JOIN shift_roles sr ON sr.id = a.shift_role_id
|
||||
JOIN orders o ON o.id = s.order_id
|
||||
JOIN businesses b ON b.id = s.business_id
|
||||
LEFT JOIN clock_points cp ON cp.id = s.clock_point_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
dtm.team_type,
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END AS priority
|
||||
FROM dispatch_team_memberships dtm
|
||||
WHERE dtm.tenant_id = $1
|
||||
AND dtm.business_id = s.business_id
|
||||
AND dtm.staff_id = $3
|
||||
AND dtm.status = 'ACTIVE'
|
||||
AND dtm.effective_at <= NOW()
|
||||
AND (dtm.expires_at IS NULL OR dtm.expires_at > NOW())
|
||||
AND (dtm.hub_id IS NULL OR dtm.hub_id = s.clock_point_id)
|
||||
ORDER BY
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END ASC,
|
||||
CASE WHEN dtm.hub_id = s.clock_point_id THEN 0 ELSE 1 END ASC,
|
||||
dtm.created_at ASC
|
||||
LIMIT 1
|
||||
) dispatch ON TRUE
|
||||
WHERE a.tenant_id = $1
|
||||
AND ssr.status = 'OPEN'
|
||||
AND ssr.expires_at > NOW()
|
||||
AND a.status = 'SWAP_REQUESTED'
|
||||
AND a.staff_id <> $3
|
||||
AND sr.role_code = $4
|
||||
@@ -1006,7 +1067,7 @@ export async function listOpenShifts(actorUid, { limit, search } = {}) {
|
||||
UNION ALL
|
||||
SELECT * FROM swap_roles
|
||||
) items
|
||||
ORDER BY "startTime" ASC
|
||||
ORDER BY "dispatchPriority" ASC, "startTime" ASC
|
||||
LIMIT $5
|
||||
`,
|
||||
[
|
||||
@@ -1369,17 +1430,165 @@ export async function listStaffBenefitHistory(actorUid, { limit, offset } = {})
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
export async function listCoreTeam(actorUid) {
|
||||
export async function listSwapRequests(actorUid, { shiftId, status = 'OPEN', limit } = {}) {
|
||||
const context = await requireClientContext(actorUid);
|
||||
const result = await query(
|
||||
const safeLimit = parseLimit(limit, 20, 100);
|
||||
const allowedStatuses = new Set(['OPEN', 'RESOLVED', 'CANCELLED', 'EXPIRED', 'AUTO_CANCELLED']);
|
||||
const normalizedStatus = allowedStatuses.has(`${status || 'OPEN'}`.toUpperCase())
|
||||
? `${status || 'OPEN'}`.toUpperCase()
|
||||
: 'OPEN';
|
||||
|
||||
const swapResult = await query(
|
||||
`
|
||||
SELECT
|
||||
srq.id AS "swapRequestId",
|
||||
srq.shift_id AS "shiftId",
|
||||
srq.shift_role_id AS "roleId",
|
||||
srq.original_assignment_id AS "originalAssignmentId",
|
||||
srq.original_staff_id AS "originalStaffId",
|
||||
srq.status,
|
||||
srq.reason,
|
||||
srq.expires_at AS "expiresAt",
|
||||
srq.resolved_at AS "resolvedAt",
|
||||
s.title AS "shiftTitle",
|
||||
s.starts_at AS "startTime",
|
||||
s.ends_at AS "endTime",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
COALESCE(cp.address, s.location_address) AS address,
|
||||
b.business_name AS "clientName",
|
||||
st.full_name AS "originalStaffName",
|
||||
sr.role_name AS "roleName"
|
||||
FROM shift_swap_requests srq
|
||||
JOIN shifts s ON s.id = srq.shift_id
|
||||
JOIN shift_roles sr ON sr.id = srq.shift_role_id
|
||||
JOIN staffs st ON st.id = srq.original_staff_id
|
||||
JOIN businesses b ON b.id = srq.business_id
|
||||
LEFT JOIN clock_points cp ON cp.id = s.clock_point_id
|
||||
WHERE srq.tenant_id = $1
|
||||
AND srq.business_id = $2
|
||||
AND ($3::uuid IS NULL OR srq.shift_id = $3)
|
||||
AND srq.status = $4
|
||||
ORDER BY srq.created_at DESC
|
||||
LIMIT $5
|
||||
`,
|
||||
[context.tenant.tenantId, context.business.businessId, shiftId || null, normalizedStatus, safeLimit]
|
||||
);
|
||||
|
||||
if (swapResult.rowCount === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const swapIds = swapResult.rows.map((row) => row.swapRequestId);
|
||||
const candidateResult = await query(
|
||||
`
|
||||
SELECT
|
||||
srq.id AS "swapRequestId",
|
||||
app.id AS "applicationId",
|
||||
app.status AS "applicationStatus",
|
||||
app.created_at AS "appliedAt",
|
||||
st.id AS "staffId",
|
||||
st.full_name AS "fullName",
|
||||
st.primary_role AS "primaryRole",
|
||||
st.average_rating AS "averageRating",
|
||||
st.rating_count AS "ratingCount",
|
||||
TRUE AS favorite
|
||||
COALESCE(dispatch.team_type, 'MARKETPLACE') AS "dispatchTeam",
|
||||
COALESCE(dispatch.priority, 3) AS "dispatchPriority"
|
||||
FROM shift_swap_requests srq
|
||||
JOIN shifts s ON s.id = srq.shift_id
|
||||
JOIN applications app ON app.shift_role_id = srq.shift_role_id
|
||||
JOIN staffs st ON st.id = app.staff_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
dtm.team_type,
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END AS priority
|
||||
FROM dispatch_team_memberships dtm
|
||||
WHERE dtm.tenant_id = srq.tenant_id
|
||||
AND dtm.business_id = srq.business_id
|
||||
AND dtm.staff_id = st.id
|
||||
AND dtm.status = 'ACTIVE'
|
||||
AND dtm.effective_at <= NOW()
|
||||
AND (dtm.expires_at IS NULL OR dtm.expires_at > NOW())
|
||||
AND (dtm.hub_id IS NULL OR dtm.hub_id = s.clock_point_id)
|
||||
ORDER BY
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END ASC,
|
||||
CASE WHEN dtm.hub_id = s.clock_point_id THEN 0 ELSE 1 END ASC,
|
||||
dtm.created_at ASC
|
||||
LIMIT 1
|
||||
) dispatch ON TRUE
|
||||
WHERE srq.id = ANY($1::uuid[])
|
||||
AND app.status IN ('PENDING', 'CONFIRMED')
|
||||
ORDER BY srq.created_at DESC, "dispatchPriority" ASC, st.average_rating DESC, app.created_at ASC
|
||||
`,
|
||||
[swapIds]
|
||||
);
|
||||
|
||||
const candidatesBySwapId = new Map();
|
||||
for (const row of candidateResult.rows) {
|
||||
if (!candidatesBySwapId.has(row.swapRequestId)) {
|
||||
candidatesBySwapId.set(row.swapRequestId, []);
|
||||
}
|
||||
candidatesBySwapId.get(row.swapRequestId).push(row);
|
||||
}
|
||||
|
||||
return swapResult.rows.map((row) => ({
|
||||
...row,
|
||||
candidates: candidatesBySwapId.get(row.swapRequestId) || [],
|
||||
candidateCount: (candidatesBySwapId.get(row.swapRequestId) || []).length,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function listCoreTeam(actorUid) {
|
||||
const context = await requireClientContext(actorUid);
|
||||
const result = await query(
|
||||
`
|
||||
SELECT
|
||||
dtm.id AS "membershipId",
|
||||
st.id AS "staffId",
|
||||
st.full_name AS "fullName",
|
||||
st.primary_role AS "primaryRole",
|
||||
st.average_rating AS "averageRating",
|
||||
st.rating_count AS "ratingCount",
|
||||
COALESCE(sf.id IS NOT NULL, FALSE) AS favorite,
|
||||
dtm.team_type AS "teamType"
|
||||
FROM dispatch_team_memberships dtm
|
||||
JOIN staffs st ON st.id = dtm.staff_id
|
||||
LEFT JOIN staff_favorites sf
|
||||
ON sf.staff_id = dtm.staff_id
|
||||
AND sf.tenant_id = dtm.tenant_id
|
||||
AND sf.business_id = dtm.business_id
|
||||
WHERE dtm.tenant_id = $1
|
||||
AND dtm.business_id = $2
|
||||
AND dtm.status = 'ACTIVE'
|
||||
AND dtm.team_type = 'CORE'
|
||||
AND dtm.effective_at <= NOW()
|
||||
AND (dtm.expires_at IS NULL OR dtm.expires_at > NOW())
|
||||
ORDER BY st.average_rating DESC, st.full_name ASC
|
||||
`,
|
||||
[context.tenant.tenantId, context.business.businessId]
|
||||
);
|
||||
if (result.rowCount > 0) {
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
const favoritesFallback = await query(
|
||||
`
|
||||
SELECT
|
||||
NULL::uuid AS "membershipId",
|
||||
st.id AS "staffId",
|
||||
st.full_name AS "fullName",
|
||||
st.primary_role AS "primaryRole",
|
||||
st.average_rating AS "averageRating",
|
||||
st.rating_count AS "ratingCount",
|
||||
TRUE AS favorite,
|
||||
'CORE'::text AS "teamType"
|
||||
FROM staff_favorites sf
|
||||
JOIN staffs st ON st.id = sf.staff_id
|
||||
WHERE sf.tenant_id = $1
|
||||
@@ -1388,6 +1597,143 @@ export async function listCoreTeam(actorUid) {
|
||||
`,
|
||||
[context.tenant.tenantId, context.business.businessId]
|
||||
);
|
||||
return favoritesFallback.rows;
|
||||
}
|
||||
|
||||
export async function listCoverageDispatchTeams(actorUid, { hubId, teamType } = {}) {
|
||||
const context = await requireClientContext(actorUid);
|
||||
const normalizedTeamType = teamType ? `${teamType}`.toUpperCase() : null;
|
||||
const result = await query(
|
||||
`
|
||||
SELECT
|
||||
dtm.id AS "membershipId",
|
||||
dtm.staff_id AS "staffId",
|
||||
st.full_name AS "fullName",
|
||||
st.primary_role AS "primaryRole",
|
||||
dtm.team_type AS "teamType",
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END AS "dispatchPriority",
|
||||
dtm.source,
|
||||
dtm.status,
|
||||
dtm.reason,
|
||||
dtm.effective_at AS "effectiveAt",
|
||||
dtm.expires_at AS "expiresAt",
|
||||
dtm.hub_id AS "hubId",
|
||||
cp.label AS "hubLabel"
|
||||
FROM dispatch_team_memberships dtm
|
||||
JOIN staffs st ON st.id = dtm.staff_id
|
||||
LEFT JOIN clock_points cp ON cp.id = dtm.hub_id
|
||||
WHERE dtm.tenant_id = $1
|
||||
AND dtm.business_id = $2
|
||||
AND dtm.status = 'ACTIVE'
|
||||
AND dtm.effective_at <= NOW()
|
||||
AND (dtm.expires_at IS NULL OR dtm.expires_at > NOW())
|
||||
AND ($3::uuid IS NULL OR dtm.hub_id = $3)
|
||||
AND ($4::text IS NULL OR dtm.team_type = $4)
|
||||
ORDER BY "dispatchPriority" ASC, st.full_name ASC
|
||||
`,
|
||||
[context.tenant.tenantId, context.business.businessId, hubId || null, normalizedTeamType]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
export async function listCoverageDispatchCandidates(actorUid, { shiftId, roleId, limit } = {}) {
|
||||
const context = await requireClientContext(actorUid);
|
||||
if (!shiftId) {
|
||||
throw new AppError('VALIDATION_ERROR', 'shiftId is required', 400, { field: 'shiftId' });
|
||||
}
|
||||
|
||||
const safeLimit = parseLimit(limit, 25, 100);
|
||||
const result = await query(
|
||||
`
|
||||
WITH target_role AS (
|
||||
SELECT
|
||||
s.id AS shift_id,
|
||||
s.tenant_id,
|
||||
s.business_id,
|
||||
s.clock_point_id,
|
||||
sr.id AS shift_role_id,
|
||||
sr.role_id,
|
||||
sr.role_code,
|
||||
sr.role_name
|
||||
FROM shifts s
|
||||
JOIN shift_roles sr ON sr.shift_id = s.id
|
||||
WHERE s.tenant_id = $1
|
||||
AND s.business_id = $2
|
||||
AND s.id = $3
|
||||
AND ($4::uuid IS NULL OR sr.id = $4)
|
||||
ORDER BY sr.created_at ASC
|
||||
LIMIT 1
|
||||
)
|
||||
SELECT
|
||||
st.id AS "staffId",
|
||||
st.full_name AS "fullName",
|
||||
st.primary_role AS "primaryRole",
|
||||
st.average_rating AS "averageRating",
|
||||
st.rating_count AS "ratingCount",
|
||||
COALESCE(dispatch.team_type, 'MARKETPLACE') AS "dispatchTeam",
|
||||
COALESCE(dispatch.priority, 3) AS "dispatchPriority",
|
||||
dispatch.hub_id AS "dispatchHubId"
|
||||
FROM target_role tr
|
||||
JOIN staffs st
|
||||
ON st.tenant_id = tr.tenant_id
|
||||
AND st.status = 'ACTIVE'
|
||||
LEFT JOIN staff_blocks sb
|
||||
ON sb.tenant_id = tr.tenant_id
|
||||
AND sb.business_id = tr.business_id
|
||||
AND sb.staff_id = st.id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
dtm.team_type,
|
||||
dtm.hub_id,
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END AS priority
|
||||
FROM dispatch_team_memberships dtm
|
||||
WHERE dtm.tenant_id = tr.tenant_id
|
||||
AND dtm.business_id = tr.business_id
|
||||
AND dtm.staff_id = st.id
|
||||
AND dtm.status = 'ACTIVE'
|
||||
AND dtm.effective_at <= NOW()
|
||||
AND (dtm.expires_at IS NULL OR dtm.expires_at > NOW())
|
||||
AND (dtm.hub_id IS NULL OR dtm.hub_id = tr.clock_point_id)
|
||||
ORDER BY
|
||||
CASE dtm.team_type
|
||||
WHEN 'CORE' THEN 1
|
||||
WHEN 'CERTIFIED_LOCATION' THEN 2
|
||||
ELSE 3
|
||||
END ASC,
|
||||
CASE WHEN dtm.hub_id = tr.clock_point_id THEN 0 ELSE 1 END ASC,
|
||||
dtm.created_at ASC
|
||||
LIMIT 1
|
||||
) dispatch ON TRUE
|
||||
WHERE sb.id IS NULL
|
||||
AND (
|
||||
st.primary_role = tr.role_code
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM staff_roles str
|
||||
WHERE str.staff_id = st.id
|
||||
AND str.role_id = tr.role_id
|
||||
)
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM assignments a
|
||||
WHERE a.shift_id = tr.shift_id
|
||||
AND a.staff_id = st.id
|
||||
AND a.status IN ('ASSIGNED', 'ACCEPTED', 'SWAP_REQUESTED', 'CHECKED_IN', 'CHECKED_OUT', 'COMPLETED')
|
||||
)
|
||||
ORDER BY "dispatchPriority" ASC, st.average_rating DESC, st.full_name ASC
|
||||
LIMIT $5
|
||||
`,
|
||||
[context.tenant.tenantId, context.business.businessId, shiftId, roleId || null, safeLimit]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ function createMobileQueryService() {
|
||||
listCoverageByDate: async () => ([{ shiftId: 'coverage-1' }]),
|
||||
listCoreTeam: async () => ([{ staffId: 'core-1' }]),
|
||||
listCompletedShifts: async () => ([{ shiftId: 'completed-1' }]),
|
||||
listCoverageDispatchCandidates: async () => ([{ staffId: 'dispatch-1' }]),
|
||||
listCoverageDispatchTeams: async () => ([{ membershipId: 'dispatch-team-1' }]),
|
||||
listEmergencyContacts: async () => ([{ contactId: 'ec-1' }]),
|
||||
listFaqCategories: async () => ([{ id: 'faq-1', title: 'Clock in' }]),
|
||||
listGeofenceIncidents: async () => ([{ incidentId: 'incident-1' }]),
|
||||
@@ -61,6 +63,7 @@ function createMobileQueryService() {
|
||||
listTaxForms: async () => ([{ formType: 'W4' }]),
|
||||
listAttireChecklist: async () => ([{ documentId: 'attire-1' }]),
|
||||
listTimeCardEntries: async () => ([{ entryId: 'tc-1' }]),
|
||||
listSwapRequests: async () => ([{ swapRequestId: 'swap-1' }]),
|
||||
listTodayShifts: async () => ([{ shiftId: 'today-1' }]),
|
||||
listVendorRoles: async () => ([{ roleId: 'role-1' }]),
|
||||
listVendors: async () => ([{ vendorId: 'vendor-1' }]),
|
||||
@@ -138,6 +141,36 @@ test('GET /query/client/coverage/incidents returns injected incidents list', asy
|
||||
assert.equal(res.body.items[0].incidentId, 'incident-1');
|
||||
});
|
||||
|
||||
test('GET /query/client/coverage/swap-requests returns injected swap request list', async () => {
|
||||
const app = createApp({ mobileQueryService: createMobileQueryService() });
|
||||
const res = await request(app)
|
||||
.get('/query/client/coverage/swap-requests?status=OPEN')
|
||||
.set('Authorization', 'Bearer test-token');
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.items[0].swapRequestId, 'swap-1');
|
||||
});
|
||||
|
||||
test('GET /query/client/coverage/dispatch-teams returns injected dispatch team memberships', async () => {
|
||||
const app = createApp({ mobileQueryService: createMobileQueryService() });
|
||||
const res = await request(app)
|
||||
.get('/query/client/coverage/dispatch-teams')
|
||||
.set('Authorization', 'Bearer test-token');
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.items[0].membershipId, 'dispatch-team-1');
|
||||
});
|
||||
|
||||
test('GET /query/client/coverage/dispatch-candidates returns injected candidate list', async () => {
|
||||
const app = createApp({ mobileQueryService: createMobileQueryService() });
|
||||
const res = await request(app)
|
||||
.get('/query/client/coverage/dispatch-candidates?shiftId=shift-1')
|
||||
.set('Authorization', 'Bearer test-token');
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.items[0].staffId, 'dispatch-1');
|
||||
});
|
||||
|
||||
test('GET /query/staff/profile/tax-forms returns injected tax forms', async () => {
|
||||
const app = createApp({ mobileQueryService: createMobileQueryService() });
|
||||
const res = await request(app)
|
||||
|
||||
Reference in New Issue
Block a user