feat(api): add staff order detail and compliance eligibility

This commit is contained in:
zouantchaw
2026-03-19 20:17:48 +01:00
parent 4d74fa52ab
commit d2bcb9f3ba
18 changed files with 1051 additions and 42 deletions

View File

@@ -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)

View 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',
]);
});