feat(api): add staff order detail and compliance eligibility
This commit is contained in:
@@ -160,6 +160,22 @@ async function finalizeVerifiedUpload({
|
||||
};
|
||||
}
|
||||
|
||||
async function approveVerification({
|
||||
token,
|
||||
verificationId,
|
||||
note = 'Smoke approval',
|
||||
}) {
|
||||
return apiCall(`/verifications/${verificationId}/review`, {
|
||||
method: 'POST',
|
||||
token,
|
||||
body: {
|
||||
decision: 'APPROVED',
|
||||
note,
|
||||
reasonCode: 'SMOKE_APPROVAL',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function signInClient() {
|
||||
return apiCall('/auth/client/sign-in', {
|
||||
method: 'POST',
|
||||
@@ -794,6 +810,8 @@ async function main() {
|
||||
assert.equal(typeof assignedTodayShift.longitude, 'number');
|
||||
assert.equal(assignedTodayShift.clockInMode, fixture.shifts.assigned.clockInMode);
|
||||
assert.equal(assignedTodayShift.allowClockInOverride, fixture.shifts.assigned.allowClockInOverride);
|
||||
const clockableTodayShift = todaysShifts.items.find((shift) => shift.attendanceStatus === 'NOT_CLOCKED_IN')
|
||||
|| assignedTodayShift;
|
||||
logStep('staff.clock-in.shifts-today.ok', { count: todaysShifts.items.length });
|
||||
|
||||
const attendanceStatusBefore = await apiCall('/staff/clock-in/status', {
|
||||
@@ -827,30 +845,61 @@ async function main() {
|
||||
const availableOrders = await apiCall('/staff/orders/available?limit=20', {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
const availableOrder = availableOrders.items.find((item) => item.orderId === createdRecurringOrder.orderId)
|
||||
|| availableOrders.items[0];
|
||||
assert.ok(availableOrder);
|
||||
assert.ok(availableOrder.roleId);
|
||||
logStep('staff.orders.available.ok', { count: availableOrders.items.length, orderId: availableOrder.orderId });
|
||||
assert.ok(availableOrders.items.length > 0);
|
||||
|
||||
const bookedOrder = await apiCall(`/staff/orders/${availableOrder.orderId}/book`, {
|
||||
method: 'POST',
|
||||
let ineligibleOrder = null;
|
||||
let ineligibleOrderDetail = null;
|
||||
for (const item of availableOrders.items) {
|
||||
const detail = await apiCall(`/staff/orders/${item.orderId}`, {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
|
||||
if (!ineligibleOrderDetail && detail.eligibility?.isEligible === false) {
|
||||
ineligibleOrder = item;
|
||||
ineligibleOrderDetail = detail;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const orderCard = ineligibleOrder || availableOrders.items[0];
|
||||
const orderDetail = ineligibleOrderDetail || await apiCall(`/staff/orders/${orderCard.orderId}`, {
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-order-book'),
|
||||
body: {
|
||||
roleId: availableOrder.roleId,
|
||||
},
|
||||
});
|
||||
assert.equal(bookedOrder.orderId, availableOrder.orderId);
|
||||
assert.ok(bookedOrder.assignedShiftCount >= 1);
|
||||
assert.equal(bookedOrder.status, 'PENDING');
|
||||
assert.ok(Array.isArray(bookedOrder.assignedShifts));
|
||||
logStep('staff.orders.book.ok', {
|
||||
orderId: bookedOrder.orderId,
|
||||
assignedShiftCount: bookedOrder.assignedShiftCount,
|
||||
status: bookedOrder.status,
|
||||
assert.ok(orderCard.roleId);
|
||||
logStep('staff.orders.available.ok', { count: availableOrders.items.length, orderId: orderCard.orderId });
|
||||
|
||||
assert.equal(orderDetail.orderId, orderCard.orderId);
|
||||
assert.equal(orderDetail.roleId, orderCard.roleId);
|
||||
assert.ok(orderDetail.clientName);
|
||||
assert.ok(orderDetail.schedule);
|
||||
assert.ok(orderDetail.location);
|
||||
assert.ok(Array.isArray(orderDetail.managers));
|
||||
assert.ok(orderDetail.eligibility);
|
||||
logStep('staff.orders.detail.ok', {
|
||||
orderId: orderDetail.orderId,
|
||||
status: orderDetail.status,
|
||||
isEligible: orderDetail.eligibility.isEligible,
|
||||
});
|
||||
|
||||
if (orderDetail.eligibility?.isEligible === false) {
|
||||
const rejectedIneligibleBooking = await apiCall(`/staff/orders/${orderCard.orderId}/book`, {
|
||||
method: 'POST',
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-order-book-ineligible'),
|
||||
body: {
|
||||
roleId: orderDetail.roleId,
|
||||
},
|
||||
allowFailure: true,
|
||||
});
|
||||
assert.equal(rejectedIneligibleBooking.statusCode, 422);
|
||||
assert.equal(rejectedIneligibleBooking.body.code, 'UNPROCESSABLE_ENTITY');
|
||||
assert.ok(Array.isArray(rejectedIneligibleBooking.body.details?.blockers));
|
||||
logStep('staff.orders.book.ineligible.rejected.ok', {
|
||||
orderId: orderCard.orderId,
|
||||
blockers: rejectedIneligibleBooking.body.details.blockers.length,
|
||||
});
|
||||
}
|
||||
|
||||
const openShifts = await apiCall('/staff/shifts/open', {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
@@ -864,10 +913,7 @@ async function main() {
|
||||
const pendingShifts = await apiCall('/staff/shifts/pending', {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
assert.ok(
|
||||
bookedOrder.assignedShifts.some((shift) => pendingShifts.items.some((item) => item.shiftId === shift.shiftId))
|
||||
);
|
||||
const pendingShift = pendingShifts.items.find((item) => item.shiftId === fixture.shifts.available.id)
|
||||
const pendingShift = pendingShifts.items.find((item) => item.shiftId === openShift.shiftId)
|
||||
|| pendingShifts.items[0];
|
||||
assert.ok(pendingShift);
|
||||
logStep('staff.shifts.pending.ok', { count: pendingShifts.items.length });
|
||||
@@ -1146,12 +1192,12 @@ async function main() {
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-shift-apply'),
|
||||
body: {
|
||||
roleId: fixture.shiftRoles.availableBarista.id,
|
||||
roleId: openShift.roleId,
|
||||
},
|
||||
});
|
||||
logStep('staff.shifts.apply.ok', appliedShift);
|
||||
|
||||
const acceptedShift = await apiCall(`/staff/shifts/${fixture.shifts.assigned.id}/accept`, {
|
||||
const acceptedShift = await apiCall(`/staff/shifts/${pendingShift.shiftId}/accept`, {
|
||||
method: 'POST',
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-shift-accept'),
|
||||
@@ -1164,7 +1210,7 @@ async function main() {
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-clock-in'),
|
||||
body: {
|
||||
shiftId: fixture.shifts.assigned.id,
|
||||
shiftId: clockableTodayShift.shiftId,
|
||||
sourceType: 'GEO',
|
||||
deviceId: 'smoke-iphone-15-pro',
|
||||
latitude: fixture.clockPoint.latitude + 0.0075,
|
||||
@@ -1177,7 +1223,7 @@ async function main() {
|
||||
},
|
||||
});
|
||||
assert.equal(clockIn.validationStatus, 'FLAGGED');
|
||||
assert.equal(clockIn.effectiveClockInMode, fixture.shifts.assigned.clockInMode);
|
||||
assert.equal(clockIn.effectiveClockInMode, clockableTodayShift.clockInMode);
|
||||
assert.equal(clockIn.overrideUsed, true);
|
||||
assert.ok(clockIn.securityProofId);
|
||||
logStep('staff.clock-in.ok', clockIn);
|
||||
@@ -1187,7 +1233,7 @@ async function main() {
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-clock-in-duplicate'),
|
||||
body: {
|
||||
shiftId: fixture.shifts.assigned.id,
|
||||
shiftId: clockableTodayShift.shiftId,
|
||||
sourceType: 'GEO',
|
||||
deviceId: 'smoke-iphone-15-pro',
|
||||
latitude: fixture.clockPoint.latitude,
|
||||
@@ -1214,7 +1260,7 @@ async function main() {
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-location-stream'),
|
||||
body: {
|
||||
shiftId: fixture.shifts.assigned.id,
|
||||
shiftId: clockableTodayShift.shiftId,
|
||||
sourceType: 'GEO',
|
||||
deviceId: 'smoke-iphone-15-pro',
|
||||
points: [
|
||||
@@ -1268,7 +1314,7 @@ async function main() {
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-clock-out'),
|
||||
body: {
|
||||
shiftId: fixture.shifts.assigned.id,
|
||||
shiftId: clockableTodayShift.shiftId,
|
||||
sourceType: 'GEO',
|
||||
deviceId: 'smoke-iphone-15-pro',
|
||||
latitude: fixture.clockPoint.latitude,
|
||||
@@ -1283,7 +1329,7 @@ async function main() {
|
||||
assert.ok(clockOut.securityProofId);
|
||||
logStep('staff.clock-out.ok', clockOut);
|
||||
|
||||
const submittedCompletedShift = await apiCall(`/staff/shifts/${fixture.shifts.assigned.id}/submit-for-approval`, {
|
||||
const submittedCompletedShift = await apiCall(`/staff/shifts/${clockableTodayShift.shiftId}/submit-for-approval`, {
|
||||
method: 'POST',
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-shift-submit-approval'),
|
||||
@@ -1430,6 +1476,50 @@ async function main() {
|
||||
assert.equal(uploadedGovId.finalized.documentId, fixture.documents.governmentId.id);
|
||||
logStep('staff.profile.document.upload.ok', uploadedGovId.finalized);
|
||||
|
||||
if (!['APPROVED', 'AUTO_PASS'].includes(`${uploadedGovId.finalized.verification?.status || ''}`)) {
|
||||
const reviewedGovId = await approveVerification({
|
||||
token: ownerSession.sessionToken,
|
||||
verificationId: uploadedGovId.finalized.verification.verificationId,
|
||||
note: 'Smoke approval for government ID',
|
||||
});
|
||||
assert.equal(reviewedGovId.status, 'APPROVED');
|
||||
logStep('staff.profile.document.review.ok', {
|
||||
verificationId: reviewedGovId.verificationId,
|
||||
status: reviewedGovId.status,
|
||||
});
|
||||
}
|
||||
|
||||
const uploadedI9 = await finalizeVerifiedUpload({
|
||||
token: staffAuth.idToken,
|
||||
uploadCategory: 'staff-tax-form',
|
||||
filename: 'i9-completed.pdf',
|
||||
contentType: 'application/pdf',
|
||||
content: Buffer.from('fake-i9-tax-form'),
|
||||
finalizePath: `/staff/profile/documents/${fixture.documents.taxFormI9.id}/upload`,
|
||||
finalizeMethod: 'PUT',
|
||||
verificationType: 'tax_form',
|
||||
subjectId: fixture.documents.taxFormI9.id,
|
||||
rules: {
|
||||
documentId: fixture.documents.taxFormI9.id,
|
||||
formType: 'I-9',
|
||||
},
|
||||
});
|
||||
assert.equal(uploadedI9.finalized.documentId, fixture.documents.taxFormI9.id);
|
||||
logStep('staff.profile.tax-form.upload.ok', uploadedI9.finalized);
|
||||
|
||||
if (!['APPROVED', 'AUTO_PASS'].includes(`${uploadedI9.finalized.verification?.status || ''}`)) {
|
||||
const reviewedI9 = await approveVerification({
|
||||
token: ownerSession.sessionToken,
|
||||
verificationId: uploadedI9.finalized.verification.verificationId,
|
||||
note: 'Smoke approval for completed I-9',
|
||||
});
|
||||
assert.equal(reviewedI9.status, 'APPROVED');
|
||||
logStep('staff.profile.tax-form.review.ok', {
|
||||
verificationId: reviewedI9.verificationId,
|
||||
status: reviewedI9.status,
|
||||
});
|
||||
}
|
||||
|
||||
const uploadedAttire = await finalizeVerifiedUpload({
|
||||
token: staffAuth.idToken,
|
||||
uploadCategory: 'staff-attire',
|
||||
@@ -1474,9 +1564,57 @@ async function main() {
|
||||
const profileDocumentsAfter = await apiCall('/staff/profile/documents', {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
assert.ok(profileDocumentsAfter.items.some((item) => item.documentId === fixture.documents.governmentId.id));
|
||||
const governmentIdAfter = profileDocumentsAfter.items.find((item) => item.documentId === fixture.documents.governmentId.id);
|
||||
assert.ok(governmentIdAfter);
|
||||
assert.equal(governmentIdAfter.status, 'VERIFIED');
|
||||
logStep('staff.profile.documents-after.ok', { count: profileDocumentsAfter.items.length });
|
||||
|
||||
const availableOrdersAfterVerification = await apiCall('/staff/orders/available?limit=20', {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
let eligibleOrder = null;
|
||||
let eligibleOrderDetail = null;
|
||||
for (const item of availableOrdersAfterVerification.items) {
|
||||
const detail = await apiCall(`/staff/orders/${item.orderId}`, {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
if (detail.eligibility?.isEligible === true) {
|
||||
eligibleOrder = item;
|
||||
eligibleOrderDetail = detail;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert.ok(eligibleOrder, 'Expected at least one eligible available order after document verification');
|
||||
|
||||
const bookedOrder = await apiCall(`/staff/orders/${eligibleOrder.orderId}/book`, {
|
||||
method: 'POST',
|
||||
token: staffAuth.idToken,
|
||||
idempotencyKey: uniqueKey('staff-order-book'),
|
||||
body: {
|
||||
roleId: eligibleOrderDetail.roleId,
|
||||
},
|
||||
});
|
||||
assert.equal(bookedOrder.orderId, eligibleOrder.orderId);
|
||||
assert.ok(bookedOrder.assignedShiftCount >= 1);
|
||||
assert.equal(bookedOrder.status, 'PENDING');
|
||||
assert.ok(Array.isArray(bookedOrder.assignedShifts));
|
||||
logStep('staff.orders.book.ok', {
|
||||
orderId: bookedOrder.orderId,
|
||||
assignedShiftCount: bookedOrder.assignedShiftCount,
|
||||
status: bookedOrder.status,
|
||||
});
|
||||
|
||||
const pendingShiftsAfterBooking = await apiCall('/staff/shifts/pending', {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
assert.ok(
|
||||
bookedOrder.assignedShifts.some((shift) => pendingShiftsAfterBooking.items.some((item) => item.shiftId === shift.shiftId))
|
||||
);
|
||||
logStep('staff.shifts.pending-after-order-book.ok', {
|
||||
count: pendingShiftsAfterBooking.items.length,
|
||||
bookedShiftCount: bookedOrder.assignedShiftCount,
|
||||
});
|
||||
|
||||
const certificatesAfter = await apiCall('/staff/profile/certificates', {
|
||||
token: staffAuth.idToken,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user