fix(api): close v2 mobile contract gaps
This commit is contained in:
@@ -171,17 +171,32 @@ export async function listRecentReorders(actorUid, limit) {
|
||||
o.id,
|
||||
o.title,
|
||||
o.starts_at AS "date",
|
||||
COALESCE(cp.label, o.location_name) AS "hubName",
|
||||
COALESCE(COUNT(sr.id), 0)::INTEGER AS "positionCount",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType"
|
||||
MAX(COALESCE(cp.label, o.location_name)) AS "hubName",
|
||||
MAX(b.business_name) AS "clientName",
|
||||
COALESCE(SUM(sr.workers_needed), 0)::INTEGER AS "positionCount",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType",
|
||||
COALESCE(ROUND(AVG(sr.bill_rate_cents))::INTEGER, 0) AS "hourlyRateCents",
|
||||
COALESCE(
|
||||
SUM(
|
||||
sr.bill_rate_cents
|
||||
* sr.workers_needed
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
),
|
||||
0
|
||||
)::BIGINT AS "totalPriceCents",
|
||||
COALESCE(
|
||||
SUM(GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)),
|
||||
0
|
||||
)::NUMERIC(12,2) AS hours
|
||||
FROM orders o
|
||||
JOIN businesses b ON b.id = o.business_id
|
||||
LEFT JOIN shifts s ON s.order_id = o.id
|
||||
LEFT JOIN shift_roles sr ON sr.shift_id = s.id
|
||||
LEFT JOIN clock_points cp ON cp.id = s.clock_point_id
|
||||
WHERE o.tenant_id = $1
|
||||
AND o.business_id = $2
|
||||
AND o.status IN ('COMPLETED', 'ACTIVE', 'FILLED')
|
||||
GROUP BY o.id, cp.label
|
||||
GROUP BY o.id
|
||||
ORDER BY o.starts_at DESC NULLS LAST
|
||||
LIMIT $3
|
||||
`,
|
||||
@@ -520,15 +535,33 @@ export async function listOrderItemsByDateRange(actorUid, { startDate, endDate }
|
||||
sr.id AS "itemId",
|
||||
o.id AS "orderId",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType",
|
||||
o.title AS "eventName",
|
||||
b.business_name AS "clientName",
|
||||
sr.role_name AS title,
|
||||
sr.role_name AS "roleName",
|
||||
s.starts_at AS date,
|
||||
to_char(s.starts_at AT TIME ZONE 'UTC', 'YYYY-MM-DD') AS date,
|
||||
s.starts_at AS "startsAt",
|
||||
s.ends_at AS "endsAt",
|
||||
to_char(s.starts_at AT TIME ZONE 'UTC', 'HH24:MI') AS "startTime",
|
||||
to_char(s.ends_at AT TIME ZONE 'UTC', 'HH24:MI') AS "endTime",
|
||||
sr.workers_needed AS "requiredWorkerCount",
|
||||
sr.assigned_count AS "filledCount",
|
||||
sr.bill_rate_cents AS "hourlyRateCents",
|
||||
ROUND(COALESCE(sr.bill_rate_cents, 0)::numeric / 100, 2) AS "hourlyRate",
|
||||
GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)::NUMERIC(12,2) AS hours,
|
||||
(sr.bill_rate_cents * sr.workers_needed)::BIGINT AS "totalCostCents",
|
||||
ROUND(
|
||||
(
|
||||
sr.bill_rate_cents
|
||||
* sr.workers_needed
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)::numeric / 100,
|
||||
2
|
||||
) AS "totalValue",
|
||||
COALESCE(cp.label, s.location_name) AS "locationName",
|
||||
COALESCE(s.location_address, cp.address) AS "locationAddress",
|
||||
hm.business_membership_id AS "hubManagerId",
|
||||
COALESCE(u.display_name, u.email) AS "hubManagerName",
|
||||
s.status,
|
||||
COALESCE(
|
||||
json_agg(
|
||||
@@ -544,14 +577,34 @@ export async function listOrderItemsByDateRange(actorUid, { startDate, endDate }
|
||||
FROM shift_roles sr
|
||||
JOIN shifts s ON s.id = sr.shift_id
|
||||
JOIN orders o ON o.id = s.order_id
|
||||
JOIN businesses b ON b.id = o.business_id
|
||||
LEFT JOIN clock_points cp ON cp.id = s.clock_point_id
|
||||
LEFT JOIN assignments a ON a.shift_role_id = sr.id
|
||||
LEFT JOIN staffs st ON st.id = a.staff_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT business_membership_id
|
||||
FROM hub_managers
|
||||
WHERE tenant_id = o.tenant_id
|
||||
AND hub_id = s.clock_point_id
|
||||
ORDER BY created_at ASC
|
||||
LIMIT 1
|
||||
) hm ON TRUE
|
||||
LEFT JOIN business_memberships bm ON bm.id = hm.business_membership_id
|
||||
LEFT JOIN users u ON u.id = bm.user_id
|
||||
WHERE o.tenant_id = $1
|
||||
AND o.business_id = $2
|
||||
AND s.starts_at >= $3::timestamptz
|
||||
AND s.starts_at <= $4::timestamptz
|
||||
GROUP BY sr.id, o.id, s.id, cp.label
|
||||
GROUP BY
|
||||
sr.id,
|
||||
o.id,
|
||||
s.id,
|
||||
cp.label,
|
||||
cp.address,
|
||||
b.business_name,
|
||||
hm.business_membership_id,
|
||||
u.display_name,
|
||||
u.email
|
||||
ORDER BY s.starts_at ASC, sr.role_name ASC
|
||||
`,
|
||||
[context.tenant.tenantId, context.business.businessId, range.start, range.end]
|
||||
@@ -633,6 +686,23 @@ export async function listTodayShifts(actorUid) {
|
||||
COALESCE(s.title, sr.role_name || ' shift') AS title,
|
||||
b.business_name AS "clientName",
|
||||
ROUND(COALESCE(sr.pay_rate_cents, 0)::numeric / 100, 2) AS "hourlyRate",
|
||||
COALESCE(sr.pay_rate_cents, 0)::INTEGER AS "hourlyRateCents",
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)::numeric / 100,
|
||||
2
|
||||
) AS "totalRate",
|
||||
COALESCE(
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)
|
||||
)::INTEGER,
|
||||
0
|
||||
) AS "totalRateCents",
|
||||
sr.role_name AS "roleName",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
COALESCE(s.location_address, cp.address) AS "locationAddress",
|
||||
@@ -656,7 +726,7 @@ export async function listTodayShifts(actorUid) {
|
||||
AND a.staff_id = $2
|
||||
AND s.starts_at >= $3::timestamptz
|
||||
AND s.starts_at < $4::timestamptz
|
||||
AND a.status IN ('ASSIGNED', 'ACCEPTED', 'CHECKED_IN', 'CHECKED_OUT', 'COMPLETED')
|
||||
AND a.status IN ('ASSIGNED', 'ACCEPTED', 'SWAP_REQUESTED', 'CHECKED_IN', 'CHECKED_OUT', 'COMPLETED')
|
||||
ORDER BY ABS(EXTRACT(EPOCH FROM (s.starts_at - NOW()))) ASC
|
||||
`,
|
||||
[context.tenant.tenantId, context.staff.staffId, from, to]
|
||||
@@ -767,24 +837,43 @@ export async function listAssignedShifts(actorUid, { startDate, endDate }) {
|
||||
SELECT
|
||||
a.id AS "assignmentId",
|
||||
s.id AS "shiftId",
|
||||
b.business_name AS "clientName",
|
||||
sr.role_name AS "roleName",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
s.starts_at AS date,
|
||||
s.starts_at AS "startTime",
|
||||
s.ends_at AS "endTime",
|
||||
sr.pay_rate_cents AS "hourlyRateCents",
|
||||
ROUND(COALESCE(sr.pay_rate_cents, 0)::numeric / 100, 2) AS "hourlyRate",
|
||||
COALESCE(
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)
|
||||
)::INTEGER,
|
||||
0
|
||||
) AS "totalRateCents",
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)::numeric / 100,
|
||||
2
|
||||
) AS "totalRate",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType",
|
||||
a.status
|
||||
FROM assignments a
|
||||
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
|
||||
WHERE a.tenant_id = $1
|
||||
AND a.staff_id = $2
|
||||
AND s.starts_at >= $3::timestamptz
|
||||
AND s.starts_at <= $4::timestamptz
|
||||
AND a.status IN ('ASSIGNED', 'ACCEPTED', 'CHECKED_IN', 'CHECKED_OUT', 'COMPLETED')
|
||||
AND a.status IN ('ASSIGNED', 'ACCEPTED', 'SWAP_REQUESTED', 'CHECKED_IN', 'CHECKED_OUT', 'COMPLETED')
|
||||
ORDER BY s.starts_at ASC
|
||||
`,
|
||||
[context.tenant.tenantId, context.staff.staffId, range.start, range.end]
|
||||
@@ -800,18 +889,37 @@ export async function listOpenShifts(actorUid, { limit, search } = {}) {
|
||||
SELECT
|
||||
s.id AS "shiftId",
|
||||
sr.id AS "roleId",
|
||||
b.business_name AS "clientName",
|
||||
sr.role_name AS "roleName",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
s.starts_at AS date,
|
||||
s.starts_at AS "startTime",
|
||||
s.ends_at AS "endTime",
|
||||
sr.pay_rate_cents AS "hourlyRateCents",
|
||||
ROUND(COALESCE(sr.pay_rate_cents, 0)::numeric / 100, 2) AS "hourlyRate",
|
||||
COALESCE(
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)
|
||||
)::INTEGER,
|
||||
0
|
||||
) AS "totalRateCents",
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)::numeric / 100,
|
||||
2
|
||||
) AS "totalRate",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType",
|
||||
FALSE AS "instantBook",
|
||||
sr.workers_needed AS "requiredWorkerCount"
|
||||
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
|
||||
WHERE s.tenant_id = $1
|
||||
AND s.status = 'OPEN'
|
||||
@@ -829,12 +937,30 @@ export async function listOpenShifts(actorUid, { limit, search } = {}) {
|
||||
SELECT
|
||||
s.id AS "shiftId",
|
||||
sr.id AS "roleId",
|
||||
b.business_name AS "clientName",
|
||||
sr.role_name AS "roleName",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
s.starts_at AS date,
|
||||
s.starts_at AS "startTime",
|
||||
s.ends_at AS "endTime",
|
||||
sr.pay_rate_cents AS "hourlyRateCents",
|
||||
ROUND(COALESCE(sr.pay_rate_cents, 0)::numeric / 100, 2) AS "hourlyRate",
|
||||
COALESCE(
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)
|
||||
)::INTEGER,
|
||||
0
|
||||
) AS "totalRateCents",
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)::numeric / 100,
|
||||
2
|
||||
) AS "totalRate",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType",
|
||||
FALSE AS "instantBook",
|
||||
1::INTEGER AS "requiredWorkerCount"
|
||||
@@ -842,6 +968,7 @@ export async function listOpenShifts(actorUid, { limit, search } = {}) {
|
||||
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
|
||||
WHERE a.tenant_id = $1
|
||||
AND a.status = 'SWAP_REQUESTED'
|
||||
@@ -911,8 +1038,11 @@ export async function getStaffShiftDetail(actorUid, shiftId) {
|
||||
s.id AS "shiftId",
|
||||
s.title,
|
||||
o.description,
|
||||
b.business_name AS "clientName",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
s.location_address AS address,
|
||||
COALESCE(s.location_address, cp.address) AS address,
|
||||
COALESCE(s.latitude, cp.latitude) AS latitude,
|
||||
COALESCE(s.longitude, cp.longitude) AS longitude,
|
||||
s.starts_at AS date,
|
||||
s.starts_at AS "startTime",
|
||||
s.ends_at AS "endTime",
|
||||
@@ -923,6 +1053,23 @@ export async function getStaffShiftDetail(actorUid, shiftId) {
|
||||
sr.id AS "roleId",
|
||||
sr.role_name AS "roleName",
|
||||
sr.pay_rate_cents AS "hourlyRateCents",
|
||||
ROUND(COALESCE(sr.pay_rate_cents, 0)::numeric / 100, 2) AS "hourlyRate",
|
||||
COALESCE(
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)
|
||||
)::INTEGER,
|
||||
0
|
||||
) AS "totalRateCents",
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)::numeric / 100,
|
||||
2
|
||||
) AS "totalRate",
|
||||
COALESCE(o.metadata->>'orderType', 'ONE_TIME') AS "orderType",
|
||||
sr.workers_needed AS "requiredCount",
|
||||
sr.assigned_count AS "confirmedCount",
|
||||
@@ -930,6 +1077,7 @@ export async function getStaffShiftDetail(actorUid, shiftId) {
|
||||
app.status AS "applicationStatus"
|
||||
FROM shifts s
|
||||
JOIN orders o ON o.id = s.order_id
|
||||
JOIN businesses b ON b.id = s.business_id
|
||||
JOIN shift_roles sr ON sr.shift_id = s.id
|
||||
LEFT JOIN clock_points cp ON cp.id = s.clock_point_id
|
||||
LEFT JOIN assignments a ON a.shift_role_id = sr.id AND a.staff_id = $3
|
||||
@@ -981,12 +1129,41 @@ export async function listCompletedShifts(actorUid) {
|
||||
a.id AS "assignmentId",
|
||||
s.id AS "shiftId",
|
||||
s.title,
|
||||
b.business_name AS "clientName",
|
||||
COALESCE(cp.label, s.location_name) AS location,
|
||||
s.starts_at AS date,
|
||||
to_char(s.starts_at AT TIME ZONE 'UTC', 'YYYY-MM-DD') AS date,
|
||||
s.starts_at AS "startTime",
|
||||
s.ends_at AS "endTime",
|
||||
COALESCE(sr.pay_rate_cents, 0)::INTEGER AS "hourlyRateCents",
|
||||
ROUND(COALESCE(sr.pay_rate_cents, 0)::numeric / 100, 2) AS "hourlyRate",
|
||||
COALESCE(ts.status, 'PENDING') AS "timesheetStatus",
|
||||
COALESCE(ts.regular_minutes + ts.overtime_minutes, 0) AS "minutesWorked",
|
||||
COALESCE(
|
||||
ts.gross_pay_cents,
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)
|
||||
)::BIGINT
|
||||
) AS "totalRateCents",
|
||||
ROUND(
|
||||
COALESCE(
|
||||
ts.gross_pay_cents,
|
||||
ROUND(
|
||||
(
|
||||
COALESCE(sr.pay_rate_cents, 0)
|
||||
* GREATEST(EXTRACT(EPOCH FROM (s.ends_at - s.starts_at)) / 3600, 0)
|
||||
)
|
||||
)::BIGINT
|
||||
)::numeric / 100,
|
||||
2
|
||||
) AS "totalRate",
|
||||
COALESCE(rp.status, 'PENDING') AS "paymentStatus"
|
||||
FROM assignments a
|
||||
JOIN shifts s ON s.id = a.shift_id
|
||||
JOIN businesses b ON b.id = s.business_id
|
||||
LEFT JOIN shift_roles sr ON sr.id = a.shift_role_id
|
||||
LEFT JOIN clock_points cp ON cp.id = s.clock_point_id
|
||||
LEFT JOIN timesheets ts ON ts.assignment_id = a.id
|
||||
LEFT JOIN recent_payments rp ON rp.assignment_id = a.id
|
||||
@@ -1003,19 +1180,22 @@ export async function listCompletedShifts(actorUid) {
|
||||
export async function getProfileSectionsStatus(actorUid) {
|
||||
const context = await requireStaffContext(actorUid);
|
||||
const completion = getProfileCompletionFromMetadata(context.staff);
|
||||
const [documents, certificates, benefits] = await Promise.all([
|
||||
const [documents, certificates, benefits, attire, taxForms] = await Promise.all([
|
||||
listProfileDocuments(actorUid),
|
||||
listCertificates(actorUid),
|
||||
listStaffBenefits(actorUid),
|
||||
listAttireChecklist(actorUid),
|
||||
listTaxForms(actorUid),
|
||||
]);
|
||||
return {
|
||||
personalInfoCompleted: completion.fields.firstName && completion.fields.lastName && completion.fields.email && completion.fields.phone && completion.fields.preferredLocations,
|
||||
emergencyContactCompleted: completion.fields.emergencyContact,
|
||||
experienceCompleted: completion.fields.skills && completion.fields.industries,
|
||||
attireCompleted: documents.filter((item) => item.documentType === 'ATTIRE').every((item) => item.status === 'VERIFIED'),
|
||||
taxFormsCompleted: documents.filter((item) => item.documentType === 'TAX_FORM').every((item) => item.status === 'VERIFIED'),
|
||||
attireCompleted: attire.every((item) => item.status === 'VERIFIED'),
|
||||
taxFormsCompleted: taxForms.every((item) => item.status === 'VERIFIED' || item.status === 'SUBMITTED'),
|
||||
benefitsConfigured: benefits.length > 0,
|
||||
certificateCount: certificates.length,
|
||||
documentCount: documents.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1054,6 +1234,7 @@ export async function listProfileDocuments(actorUid) {
|
||||
d.id AS "documentId",
|
||||
d.document_type AS "documentType",
|
||||
d.name,
|
||||
COALESCE(d.metadata->>'description', '') AS description,
|
||||
sd.id AS "staffDocumentId",
|
||||
sd.file_uri AS "fileUri",
|
||||
COALESCE(sd.status, 'NOT_UPLOADED') AS status,
|
||||
@@ -1065,7 +1246,7 @@ export async function listProfileDocuments(actorUid) {
|
||||
AND sd.tenant_id = d.tenant_id
|
||||
AND sd.staff_id = $2
|
||||
WHERE d.tenant_id = $1
|
||||
AND d.document_type IN ('DOCUMENT', 'GOVERNMENT_ID', 'ATTIRE', 'TAX_FORM')
|
||||
AND d.document_type IN ('DOCUMENT', 'GOVERNMENT_ID')
|
||||
ORDER BY d.name ASC
|
||||
`,
|
||||
[context.tenant.tenantId, context.staff.staffId]
|
||||
@@ -1645,9 +1826,12 @@ export async function listTaxForms(actorUid) {
|
||||
SELECT
|
||||
d.id AS "documentId",
|
||||
d.name AS "formType",
|
||||
COALESCE(d.metadata->>'description', '') AS description,
|
||||
sd.id AS "staffDocumentId",
|
||||
sd.file_uri AS "fileUri",
|
||||
COALESCE(sd.metadata->>'formStatus', 'NOT_STARTED') AS status,
|
||||
COALESCE(sd.metadata->'fields', '{}'::jsonb) AS fields
|
||||
COALESCE(sd.metadata->'fields', '{}'::jsonb) AS fields,
|
||||
sd.expires_at AS "expiresAt"
|
||||
FROM documents d
|
||||
LEFT JOIN staff_documents sd
|
||||
ON sd.document_id = d.id
|
||||
|
||||
Reference in New Issue
Block a user