Add endpoint comparison diagnostic for cost figure discrepancies
GET /wages/api/sync/compare?from=YYYY-MM-DD&to=YYYY-MM-DD returns cost totals from four Workforce endpoint variants side-by-side: shifts (all statuses), shifts (APPROVED only), timesheets (all statuses), timesheets (APPROVED only). Useful for identifying which endpoint matches the expected payroll figure. Also adds data.timesheets to wfFetchPaged response unwrap chain. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3a068ece23
commit
c15147b298
2 changed files with 84 additions and 2 deletions
|
|
@ -40,7 +40,7 @@ async function wfFetchPaged(path) {
|
|||
const data = await wfFetch(`${path}${sep}page=${page}&page_size=100`)
|
||||
const items = Array.isArray(data)
|
||||
? data
|
||||
: (data.users ?? data.departments ?? data.schedules ?? data.shifts ?? [])
|
||||
: (data.users ?? data.departments ?? data.schedules ?? data.shifts ?? data.timesheets ?? [])
|
||||
results.push(...items)
|
||||
if (items.length < 100) break
|
||||
page++
|
||||
|
|
@ -258,6 +258,75 @@ export async function runRollingSync() {
|
|||
return { actualRows, scheduledRows }
|
||||
}
|
||||
|
||||
// Fetch a raw item list from a path and return a cost summary — used for endpoint comparison
|
||||
async function fetchAndSummarise(path) {
|
||||
try {
|
||||
const items = await wfFetchPaged(path)
|
||||
if (!Array.isArray(items) || items.length === 0) return { count: 0, baseCost: 0, totalCost: 0, byDept: {} }
|
||||
let baseCost = 0, totalCost = 0
|
||||
const byDept = {}
|
||||
for (const s of items) {
|
||||
// timesheets may use start (unix ts) as the date source; shifts have date directly
|
||||
const base = parseFloat(s.cost ?? 0)
|
||||
const total = parseFloat(s.cost_with_oncosts ?? s.cost ?? 0)
|
||||
baseCost += base
|
||||
totalCost += total
|
||||
const deptId = String(s.department_id ?? 'unknown')
|
||||
const deptName = s.department_name || deptId
|
||||
if (!byDept[deptId]) byDept[deptId] = { name: deptName, baseCost: 0, totalCost: 0, count: 0 }
|
||||
byDept[deptId].baseCost += base
|
||||
byDept[deptId].totalCost += total
|
||||
byDept[deptId].count++
|
||||
}
|
||||
const deptList = Object.entries(byDept)
|
||||
.map(([id, v]) => ({ id, name: v.name, baseCost: +v.baseCost.toFixed(2), totalCost: +v.totalCost.toFixed(2), count: v.count }))
|
||||
.sort((a, b) => b.totalCost - a.totalCost)
|
||||
return { count: items.length, baseCost: +baseCost.toFixed(2), totalCost: +totalCost.toFixed(2), topDepts: deptList.slice(0, 15) }
|
||||
} catch (err) {
|
||||
return { error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
// Compare cost totals from all available endpoints for a given date range.
|
||||
// Call GET /api/sync/compare?from=YYYY-MM-DD&to=YYYY-MM-DD to diagnose figure discrepancies.
|
||||
export async function compareEndpoints(from, to) {
|
||||
const creds = await getWorkforceCreds()
|
||||
const locationId = creds.location_id
|
||||
|
||||
// Shifts — all statuses (current sync method)
|
||||
let shiftsAll = `/api/v2/shifts?from=${from}&to=${to}&show_costs=true&include_oncosts=true`
|
||||
if (locationId) shiftsAll += `&report_location_id=${locationId}`
|
||||
|
||||
// Shifts — APPROVED only
|
||||
let shiftsApproved = `/api/v2/shifts?from=${from}&to=${to}&show_costs=true&include_oncosts=true&status=APPROVED`
|
||||
if (locationId) shiftsApproved += `&report_location_id=${locationId}`
|
||||
|
||||
// Timesheets — range endpoint (closest to payroll actuals)
|
||||
let timesheets = `/api/v2/timesheets?from=${from}&to=${to}&show_costs=true&include_oncosts=true`
|
||||
if (locationId) timesheets += `&location_id=${locationId}`
|
||||
|
||||
// Timesheets — APPROVED only
|
||||
let timesheetsApproved = `/api/v2/timesheets?from=${from}&to=${to}&show_costs=true&include_oncosts=true&status=APPROVED`
|
||||
if (locationId) timesheetsApproved += `&location_id=${locationId}`
|
||||
|
||||
const [s_all, s_approved, ts_all, ts_approved] = await Promise.all([
|
||||
fetchAndSummarise(shiftsAll),
|
||||
fetchAndSummarise(shiftsApproved),
|
||||
fetchAndSummarise(timesheets),
|
||||
fetchAndSummarise(timesheetsApproved),
|
||||
])
|
||||
|
||||
return {
|
||||
from, to,
|
||||
current_method: 'shifts_all_statuses',
|
||||
shifts_all_statuses: s_all,
|
||||
shifts_approved_only: s_approved,
|
||||
timesheets_all_statuses: ts_all,
|
||||
timesheets_approved_only: ts_approved,
|
||||
note: 'baseCost = wage only; totalCost = with estimated employer on-costs. Switch current_method to whichever matches your expected payroll figure.',
|
||||
}
|
||||
}
|
||||
|
||||
export async function runBackfill(onProgress, signal) {
|
||||
const today = new Date()
|
||||
const endDate = new Date(today)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue