456 lines
9.0 KiB
GraphQL
456 lines
9.0 KiB
GraphQL
# ==========================================================
|
|
# Reports Connector - For MVP
|
|
# ==========================================================
|
|
# This file exposes SOURCE-DATA queries for all report screens.
|
|
# All aggregation (group by day/week, percentages, averages)
|
|
# is intentionally done on the client (Flutter).
|
|
#
|
|
# Entities used:
|
|
# - Shift
|
|
# - Application
|
|
# - Staff
|
|
# - Invoice
|
|
# - TimeSheet
|
|
#
|
|
# NOTE:
|
|
# Data Connect does NOT support custom root resolvers.
|
|
# These queries return raw rows from @table entities.
|
|
# ==========================================================
|
|
|
|
|
|
# ==========================================================
|
|
# 1) COVERAGE REPORT (Next 7 days)
|
|
# ==========================================================
|
|
# Coverage definition:
|
|
# - Needed: sum(Shift.workersNeeded) grouped by date
|
|
# - Confirmed: count(Application) where status IN (...)
|
|
#
|
|
# Client groups by Shift.date and calculates:
|
|
# coveragePercentage = confirmed / needed * 100
|
|
# ==========================================================
|
|
|
|
query listShiftsForCoverage(
|
|
$businessId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { businessId: { eq: $businessId } }
|
|
date: { ge: $startDate, le: $endDate }
|
|
}
|
|
orderBy: { date: ASC }
|
|
) {
|
|
id
|
|
date
|
|
workersNeeded
|
|
filled
|
|
status
|
|
}
|
|
}
|
|
|
|
#I comment here because I have an error en sdk
|
|
query listApplicationsForCoverage(
|
|
$shiftIds: [UUID!]!
|
|
#$statuses: [ApplicationStatus!]!
|
|
) @auth(level: USER) {
|
|
applications(
|
|
where: {
|
|
shiftId: { in: $shiftIds }
|
|
#status: { in: $statuses }
|
|
}
|
|
) {
|
|
id
|
|
shiftId
|
|
staffId
|
|
status
|
|
}
|
|
}
|
|
|
|
|
|
# ==========================================================
|
|
# 2) DAILY OPS REPORT (Single day)
|
|
# ==========================================================
|
|
# Metrics derived on client:
|
|
# - scheduledShifts = shifts.length
|
|
# - workersConfirmed = confirmedApps / totalNeeded
|
|
# - inProgressShifts = status == IN_PROGRESS
|
|
# - completedShifts = status == COMPLETED
|
|
# ==========================================================
|
|
|
|
query listShiftsForDailyOpsByBusiness(
|
|
$businessId: UUID!
|
|
$date: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { businessId: { eq: $businessId } }
|
|
date: { eq: $date }
|
|
}
|
|
orderBy: { startTime: ASC }
|
|
) {
|
|
id
|
|
title
|
|
location
|
|
startTime
|
|
endTime
|
|
workersNeeded
|
|
filled
|
|
status
|
|
}
|
|
}
|
|
|
|
query listShiftsForDailyOpsByVendor(
|
|
$vendorId: UUID!
|
|
$date: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { vendorId: { eq: $vendorId } }
|
|
date: { eq: $date }
|
|
}
|
|
orderBy: { startTime: ASC }
|
|
) {
|
|
id
|
|
title
|
|
location
|
|
startTime
|
|
endTime
|
|
workersNeeded
|
|
filled
|
|
status
|
|
}
|
|
}
|
|
|
|
query listApplicationsForDailyOps(
|
|
$shiftIds: [UUID!]!
|
|
) @auth(level: USER) {
|
|
applications(where: { shiftId: { in: $shiftIds } }) {
|
|
id
|
|
shiftId
|
|
staffId
|
|
status
|
|
checkInTime
|
|
checkOutTime
|
|
}
|
|
}
|
|
|
|
|
|
# ==========================================================
|
|
# 3) FORECAST REPORT (Next N weeks)
|
|
# ==========================================================
|
|
# Projected spend formula (client):
|
|
# projectedCost = workersNeeded * hours * hourlyRate
|
|
# ==========================================================
|
|
|
|
query listShiftsForForecastByBusiness(
|
|
$businessId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { businessId: { eq: $businessId } }
|
|
date: { ge: $startDate, le: $endDate }
|
|
}
|
|
orderBy: { date: ASC }
|
|
) {
|
|
id
|
|
date
|
|
workersNeeded
|
|
hours
|
|
cost
|
|
status
|
|
}
|
|
}
|
|
|
|
query listShiftsForForecastByVendor(
|
|
$vendorId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { vendorId: { eq: $vendorId } }
|
|
date: { ge: $startDate, le: $endDate }
|
|
}
|
|
orderBy: { date: ASC }
|
|
) {
|
|
id
|
|
date
|
|
workersNeeded
|
|
hours
|
|
cost
|
|
status
|
|
}
|
|
}
|
|
|
|
# ==========================================================
|
|
# 4) NO-SHOW REPORT (Historical range)
|
|
# ==========================================================
|
|
# Now fully supported because ApplicationStatus includes NO_SHOW.
|
|
#
|
|
# Metrics:
|
|
# - totalNoShows = count(status == NO_SHOW)
|
|
# - noShowRate = noShows / totalApplications
|
|
# - breakdown by staff
|
|
# ==========================================================
|
|
|
|
query listShiftsForNoShowRangeByBusiness(
|
|
$businessId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { businessId: { eq: $businessId } }
|
|
date: { ge: $startDate, le: $endDate }
|
|
}
|
|
) {
|
|
id
|
|
date
|
|
}
|
|
}
|
|
|
|
query listShiftsForNoShowRangeByVendor(
|
|
$vendorId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { vendorId: { eq: $vendorId } }
|
|
date: { ge: $startDate, le: $endDate }
|
|
}
|
|
) {
|
|
id
|
|
date
|
|
}
|
|
}
|
|
|
|
|
|
query listApplicationsForNoShowRange(
|
|
$shiftIds: [UUID!]!
|
|
) @auth(level: USER) {
|
|
applications(where: { shiftId: { in: $shiftIds } }) {
|
|
id
|
|
shiftId
|
|
staffId
|
|
status
|
|
}
|
|
}
|
|
|
|
query listStaffForNoShowReport(
|
|
$staffIds: [UUID!]!
|
|
) @auth(level: USER) {
|
|
staffs(where: { id: { in: $staffIds } }) {
|
|
id
|
|
fullName
|
|
noShowCount
|
|
reliabilityScore
|
|
}
|
|
}
|
|
|
|
|
|
# ==========================================================
|
|
# 5) SPEND REPORT (Financial)
|
|
# ==========================================================
|
|
# Uses Invoice as source of truth for money.
|
|
# Filter is now based on:
|
|
# - businessId (business dashboard)
|
|
# - vendorId (vendor dashboard)
|
|
# since Invoice no longer has ownerId.
|
|
# ==========================================================
|
|
|
|
query listInvoicesForSpendByBusiness(
|
|
$businessId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
invoices(
|
|
where: {
|
|
businessId: { eq: $businessId }
|
|
issueDate: { ge: $startDate, le: $endDate }
|
|
}
|
|
orderBy: { issueDate: ASC }
|
|
) {
|
|
id
|
|
issueDate
|
|
dueDate
|
|
amount
|
|
status
|
|
invoiceNumber
|
|
|
|
vendor { id companyName }
|
|
business { id businessName }
|
|
order { id eventName }
|
|
}
|
|
}
|
|
|
|
query listInvoicesForSpendByVendor(
|
|
$vendorId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
invoices(
|
|
where: {
|
|
vendorId: { eq: $vendorId }
|
|
issueDate: { ge: $startDate, le: $endDate }
|
|
}
|
|
orderBy: { issueDate: ASC }
|
|
) {
|
|
id
|
|
issueDate
|
|
dueDate
|
|
amount
|
|
status
|
|
invoiceNumber
|
|
|
|
vendor { id companyName }
|
|
business { id businessName }
|
|
order { id eventName }
|
|
}
|
|
}
|
|
|
|
# Optional: Spend by Order (if you need it)
|
|
query listInvoicesForSpendByOrder(
|
|
$orderId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
invoices(
|
|
where: {
|
|
orderId: { eq: $orderId }
|
|
issueDate: { ge: $startDate, le: $endDate }
|
|
}
|
|
orderBy: { issueDate: ASC }
|
|
) {
|
|
id
|
|
issueDate
|
|
dueDate
|
|
amount
|
|
status
|
|
invoiceNumber
|
|
|
|
vendor { id companyName }
|
|
business { id businessName }
|
|
order { id eventName }
|
|
}
|
|
}
|
|
|
|
query listTimesheetsForSpend(
|
|
$startTime: Timestamp!
|
|
$endTime: Timestamp!
|
|
) @auth(level: USER) {
|
|
shiftRoles(
|
|
where: {
|
|
shift: {
|
|
date: { ge: $startTime, le: $endTime }
|
|
}
|
|
}
|
|
) {
|
|
id
|
|
hours
|
|
totalValue
|
|
|
|
shift{
|
|
title
|
|
location
|
|
status
|
|
date
|
|
|
|
order{
|
|
business{
|
|
businessName
|
|
}
|
|
}
|
|
}
|
|
|
|
role{
|
|
costPerHour
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
# ==========================================================
|
|
# 6) PERFORMANCE REPORT (Historical KPIs)
|
|
# ==========================================================
|
|
# Metrics derivable:
|
|
# - fillRate
|
|
# - completionRate
|
|
# - onTimeRate
|
|
# - avgFillTimeHours (NOW POSSIBLE 🎉)
|
|
#
|
|
# avgFillTimeHours formula:
|
|
# (filledAt - createdAt) averaged across FILLED shifts
|
|
# ==========================================================
|
|
|
|
query listShiftsForPerformanceByBusiness(
|
|
$businessId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { businessId: { eq: $businessId } }
|
|
date: { ge: $startDate, le: $endDate }
|
|
filledAt: { isNull: false }
|
|
}
|
|
) {
|
|
id
|
|
workersNeeded
|
|
filled
|
|
status
|
|
createdAt
|
|
filledAt
|
|
}
|
|
}
|
|
|
|
query listShiftsForPerformanceByVendor(
|
|
$vendorId: UUID!
|
|
$startDate: Timestamp!
|
|
$endDate: Timestamp!
|
|
) @auth(level: USER) {
|
|
shifts(
|
|
where: {
|
|
order: { vendorId: { eq: $vendorId } }
|
|
date: { ge: $startDate, le: $endDate }
|
|
filledAt: { isNull: false }
|
|
}
|
|
) {
|
|
id
|
|
workersNeeded
|
|
filled
|
|
status
|
|
createdAt
|
|
filledAt
|
|
}
|
|
}
|
|
|
|
query listApplicationsForPerformance(
|
|
$shiftIds: [UUID!]!
|
|
) @auth(level: USER) {
|
|
applications(where: { shiftId: { in: $shiftIds } }) {
|
|
id
|
|
shiftId
|
|
staffId
|
|
status
|
|
checkInTime
|
|
checkOutTime
|
|
}
|
|
}
|
|
|
|
query listStaffForPerformance(
|
|
$staffIds: [UUID!]!
|
|
) @auth(level: USER) {
|
|
staffs(where: { id: { in: $staffIds } }) {
|
|
id
|
|
averageRating
|
|
onTimeRate
|
|
noShowCount
|
|
reliabilityScore
|
|
}
|
|
}
|