feat(api): add staff order detail and compliance eligibility
This commit is contained in:
@@ -27,6 +27,7 @@ function createMobileQueryService() {
|
||||
getSpendReport: async () => ({ totals: { amountCents: 2000 } }),
|
||||
getSpendBreakdown: async () => ([{ category: 'Barista', amountCents: 1000 }]),
|
||||
getStaffDashboard: async () => ({ staffName: 'Ana Barista' }),
|
||||
getStaffOrderDetail: async () => ({ orderId: 'order-available-1', eligibility: { isEligible: true, blockers: [] } }),
|
||||
getStaffReliabilityStats: async () => ({ totalShifts: 12, reliabilityScore: 96.4 }),
|
||||
getStaffProfileCompletion: async () => ({ completed: true }),
|
||||
getStaffSession: async () => ({ staff: { staffId: 's1' } }),
|
||||
@@ -135,6 +136,27 @@ test('GET /query/staff/orders/available returns injected order-level opportuniti
|
||||
assert.equal(res.body.items[0].roleId, 'role-catalog-1');
|
||||
});
|
||||
|
||||
test('GET /query/staff/orders/:orderId returns injected order detail', async () => {
|
||||
const app = createApp({ mobileQueryService: createMobileQueryService() });
|
||||
const res = await request(app)
|
||||
.get('/query/staff/orders/11111111-1111-4111-8111-111111111111')
|
||||
.set('Authorization', 'Bearer test-token');
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.body.orderId, 'order-available-1');
|
||||
assert.equal(res.body.eligibility.isEligible, true);
|
||||
});
|
||||
|
||||
test('GET /query/staff/orders/:orderId validates uuid', async () => {
|
||||
const app = createApp({ mobileQueryService: createMobileQueryService() });
|
||||
const res = await request(app)
|
||||
.get('/query/staff/orders/not-a-uuid')
|
||||
.set('Authorization', 'Bearer test-token');
|
||||
|
||||
assert.equal(res.status, 400);
|
||||
assert.equal(res.body.code, 'VALIDATION_ERROR');
|
||||
});
|
||||
|
||||
test('GET /query/client/shifts/scheduled returns injected shift timeline items', async () => {
|
||||
const app = createApp({ mobileQueryService: createMobileQueryService() });
|
||||
const res = await request(app)
|
||||
|
||||
117
backend/query-api/test/staff-order-detail.test.js
Normal file
117
backend/query-api/test/staff-order-detail.test.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { summarizeStaffOrderDetail } from '../src/services/mobile-query-service.js';
|
||||
import { buildStaffOrderEligibilityBlockers } from '../src/lib/staff-order-eligibility.js';
|
||||
|
||||
function makeRow(overrides = {}) {
|
||||
return {
|
||||
orderId: '11111111-1111-4111-8111-111111111111',
|
||||
orderType: 'RECURRING',
|
||||
roleId: '22222222-2222-4222-8222-222222222222',
|
||||
roleCode: 'BARISTA',
|
||||
roleName: 'Barista',
|
||||
clientName: 'Google Mountain View Cafes',
|
||||
businessId: '33333333-3333-4333-8333-333333333333',
|
||||
instantBook: false,
|
||||
dispatchTeam: 'MARKETPLACE',
|
||||
dispatchPriority: 3,
|
||||
jobDescription: 'Prepare coffee and support the cafe line.',
|
||||
instructions: 'Arrive 15 minutes early.',
|
||||
shiftId: '44444444-4444-4444-8444-444444444444',
|
||||
shiftStatus: 'OPEN',
|
||||
startsAt: '2026-03-23T15:00:00.000Z',
|
||||
endsAt: '2026-03-23T23:00:00.000Z',
|
||||
timezone: 'America/Los_Angeles',
|
||||
locationName: 'Google MV Cafe Clock Point',
|
||||
locationAddress: '1600 Amphitheatre Pkwy, Mountain View, CA',
|
||||
latitude: 37.4221,
|
||||
longitude: -122.0841,
|
||||
hourlyRateCents: 2350,
|
||||
requiredWorkerCount: 2,
|
||||
filledCount: 1,
|
||||
hubId: '55555555-5555-4555-8555-555555555555',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('summarizeStaffOrderDetail aggregates recurring order schedule and staffing', () => {
|
||||
const result = summarizeStaffOrderDetail({
|
||||
rows: [
|
||||
makeRow(),
|
||||
makeRow({
|
||||
shiftId: '66666666-6666-4666-8666-666666666666',
|
||||
startsAt: '2026-03-25T15:00:00.000Z',
|
||||
endsAt: '2026-03-25T23:00:00.000Z',
|
||||
}),
|
||||
],
|
||||
managers: [
|
||||
{ name: 'Maria Ops', phone: '+15555550101', role: 'Hub Manager' },
|
||||
{ name: 'Maria Ops', phone: '+15555550101', role: 'Hub Manager' },
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(result.orderId, '11111111-1111-4111-8111-111111111111');
|
||||
assert.equal(result.status, 'OPEN');
|
||||
assert.equal(result.schedule.totalShifts, 2);
|
||||
assert.deepEqual(result.schedule.daysOfWeek, ['MON', 'WED']);
|
||||
assert.equal(result.staffing.requiredWorkerCount, 4);
|
||||
assert.equal(result.staffing.filledCount, 2);
|
||||
assert.equal(result.pay.hourlyRate, '$23.50');
|
||||
assert.equal(result.managers.length, 1);
|
||||
assert.equal(result.eligibility.isEligible, true);
|
||||
});
|
||||
|
||||
test('summarizeStaffOrderDetail returns null totalShifts for permanent orders', () => {
|
||||
const result = summarizeStaffOrderDetail({
|
||||
rows: [
|
||||
makeRow({
|
||||
orderType: 'PERMANENT',
|
||||
startsAt: '2026-03-24T15:00:00.000Z',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(result.orderType, 'PERMANENT');
|
||||
assert.equal(result.schedule.totalShifts, null);
|
||||
});
|
||||
|
||||
test('summarizeStaffOrderDetail marks order ineligible when blockers exist', () => {
|
||||
const result = summarizeStaffOrderDetail({
|
||||
rows: [
|
||||
makeRow({
|
||||
shiftStatus: 'FILLED',
|
||||
requiredWorkerCount: 1,
|
||||
filledCount: 1,
|
||||
}),
|
||||
],
|
||||
blockers: [
|
||||
'You are blocked from working for this client',
|
||||
'Missing required document: Food Handler Card',
|
||||
'Missing required document: Food Handler Card',
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(result.status, 'FILLED');
|
||||
assert.equal(result.eligibility.isEligible, false);
|
||||
assert.deepEqual(result.eligibility.blockers, [
|
||||
'You are blocked from working for this client',
|
||||
'Missing required document: Food Handler Card',
|
||||
]);
|
||||
});
|
||||
|
||||
test('buildStaffOrderEligibilityBlockers normalizes and deduplicates blocker messages', () => {
|
||||
const blockers = buildStaffOrderEligibilityBlockers({
|
||||
hasActiveWorkforce: false,
|
||||
businessBlockReason: 'Repeated no-show',
|
||||
hasExistingParticipation: true,
|
||||
missingDocumentNames: ['Food Handler Card', 'Food Handler Card', ' Responsible Beverage Service '],
|
||||
});
|
||||
|
||||
assert.deepEqual(blockers, [
|
||||
'Workforce profile is not active',
|
||||
'You are blocked from working for this client: Repeated no-show',
|
||||
'You already applied to or booked this order',
|
||||
'Missing required document: Food Handler Card',
|
||||
'Missing required document: Responsible Beverage Service',
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user