fix(api): use UTC in reports date helpers — reports show no data #186
@@ -37,10 +37,10 @@ const DEMO_PET = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DEMO_SERVICES = [
|
const DEMO_SERVICES = [
|
||||||
{ name: "Bath & Brush", description: "Full bath, blow-dry, brush out, and ear cleaning", basePriceCents: 4500, durationMinutes: 45 },
|
{ id: "a0000001-0000-0000-0000-000000000001", name: "Bath & Brush", description: "Full bath, blow-dry, brush out, and ear cleaning", basePriceCents: 4500, durationMinutes: 45 },
|
||||||
{ name: "Full Groom — Small", description: "Complete grooming for dogs under 25 lbs", basePriceCents: 6500, durationMinutes: 60 },
|
{ id: "a0000001-0000-0000-0000-000000000002", name: "Full Groom — Small", description: "Complete grooming for dogs under 25 lbs", basePriceCents: 6500, durationMinutes: 60 },
|
||||||
{ name: "Full Groom — Medium", description: "Complete grooming for dogs 25-50 lbs", basePriceCents: 8000, durationMinutes: 75 },
|
{ id: "a0000001-0000-0000-0000-000000000003", name: "Full Groom — Medium", description: "Complete grooming for dogs 25-50 lbs", basePriceCents: 8000, durationMinutes: 75 },
|
||||||
{ name: "Nail Trim", description: "Nail clipping and filing", basePriceCents: 1500, durationMinutes: 15 },
|
{ id: "a0000001-0000-0000-0000-000000000004", name: "Nail Trim", description: "Nail clipping and filing", basePriceCents: 1500, durationMinutes: 15 },
|
||||||
];
|
];
|
||||||
|
|
||||||
adminSeedRouter.post("/seed", async (c) => {
|
adminSeedRouter.post("/seed", async (c) => {
|
||||||
@@ -71,18 +71,16 @@ adminSeedRouter.post("/seed", async (c) => {
|
|||||||
results.push(`Created staff '${KNOWN_STAFF.name}' (id: ${created!.id}, oidcSub: ${KNOWN_STAFF.oidcSub})`);
|
results.push(`Created staff '${KNOWN_STAFF.name}' (id: ${created!.id}, oidcSub: ${KNOWN_STAFF.oidcSub})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Services: only seed if none exist ─────────────────────────────────────
|
// ── Services: idempotent upsert ─────────────────────────────────────────────
|
||||||
const existingServices = await db.select().from(services).limit(1);
|
for (const svc of DEMO_SERVICES) {
|
||||||
if (existingServices.length > 0) {
|
await db.insert(services)
|
||||||
results.push("Services already exist — skipping");
|
.values({ ...svc, active: true })
|
||||||
} else {
|
.onConflictDoUpdate({
|
||||||
const created: { id: string; name: string }[] = [];
|
target: services.id,
|
||||||
for (const svc of DEMO_SERVICES) {
|
set: { name: svc.name, description: svc.description, basePriceCents: svc.basePriceCents, durationMinutes: svc.durationMinutes, active: true },
|
||||||
const [row] = await db.insert(services).values({ ...svc, active: true }).returning();
|
});
|
||||||
created.push(row!);
|
|
||||||
}
|
|
||||||
results.push(`Created ${created.length} services: ${created.map((s) => s.name).join(", ")}`);
|
|
||||||
}
|
}
|
||||||
|
results.push(`Upserted ${DEMO_SERVICES.length} services`);
|
||||||
|
|
||||||
// ── Client: Demo Client ───────────────────────────────────────────────────
|
// ── Client: Demo Client ───────────────────────────────────────────────────
|
||||||
const [existingClient] = await db
|
const [existingClient] = await db
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ function parseDate(value: string | undefined, fallback: Date): Date {
|
|||||||
|
|
||||||
function defaultFrom(): Date {
|
function defaultFrom(): Date {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
d.setDate(d.getDate() - 30);
|
d.setUTCDate(d.getUTCDate() - 30);
|
||||||
d.setHours(0, 0, 0, 0);
|
d.setUTCHours(0, 0, 0, 0);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultTo(): Date {
|
function defaultTo(): Date {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
d.setHours(23, 59, 59, 999);
|
d.setUTCHours(23, 59, 59, 999);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +283,7 @@ reportsRouter.get("/clients", async (c) => {
|
|||||||
|
|
||||||
// Clients with no appointment in last 90 days (churn risk)
|
// Clients with no appointment in last 90 days (churn risk)
|
||||||
const ninetyDaysAgo = new Date();
|
const ninetyDaysAgo = new Date();
|
||||||
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
|
ninetyDaysAgo.setUTCDate(ninetyDaysAgo.getUTCDate() - 90);
|
||||||
const ninetyDaysAgoISO = ninetyDaysAgo.toISOString();
|
const ninetyDaysAgoISO = ninetyDaysAgo.toISOString();
|
||||||
|
|
||||||
const churnRisk = await db
|
const churnRisk = await db
|
||||||
|
|||||||
+15
-15
@@ -293,22 +293,22 @@ async function seedKnownUsers() {
|
|||||||
console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)");
|
console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Services: only seed if none exist ──
|
// ── Services: idempotent upsert using deterministic IDs ──
|
||||||
const existingServices = await db.select().from(schema.services).limit(1);
|
const demoSvcs = [
|
||||||
if (existingServices.length > 0) {
|
{ id: "a0000001-0000-0000-0000-000000000001", name: "Bath & Brush", description: "Full bath, blow-dry, brush out, and ear cleaning", basePriceCents: 4500, durationMinutes: 45 },
|
||||||
console.log("✓ Services already exist — skipping");
|
{ id: "a0000001-0000-0000-0000-000000000002", name: "Full Groom — Small", description: "Complete grooming for dogs under 25 lbs", basePriceCents: 6500, durationMinutes: 60 },
|
||||||
} else {
|
{ id: "a0000001-0000-0000-0000-000000000003", name: "Full Groom — Medium", description: "Complete grooming for dogs 25-50 lbs", basePriceCents: 8000, durationMinutes: 75 },
|
||||||
const demoSvcs = [
|
{ id: "a0000001-0000-0000-0000-000000000004", name: "Nail Trim", description: "Nail clipping and filing", basePriceCents: 1500, durationMinutes: 15 },
|
||||||
{ name: "Bath & Brush", description: "Full bath, blow-dry, brush out, and ear cleaning", basePriceCents: 4500, durationMinutes: 45 },
|
];
|
||||||
{ name: "Full Groom — Small", description: "Complete grooming for dogs under 25 lbs", basePriceCents: 6500, durationMinutes: 60 },
|
for (const svc of demoSvcs) {
|
||||||
{ name: "Full Groom — Medium", description: "Complete grooming for dogs 25-50 lbs", basePriceCents: 8000, durationMinutes: 75 },
|
await db.insert(schema.services)
|
||||||
{ name: "Nail Trim", description: "Nail clipping and filing", basePriceCents: 1500, durationMinutes: 15 },
|
.values({ ...svc, active: true })
|
||||||
];
|
.onConflictDoUpdate({
|
||||||
for (const svc of demoSvcs) {
|
target: schema.services.id,
|
||||||
await db.insert(schema.services).values({ ...svc, active: true });
|
set: { name: svc.name, description: svc.description, basePriceCents: svc.basePriceCents, durationMinutes: svc.durationMinutes, active: true },
|
||||||
}
|
});
|
||||||
console.log(`✓ Created ${demoSvcs.length} services`);
|
|
||||||
}
|
}
|
||||||
|
console.log(`✓ Seeded ${demoSvcs.length} services`);
|
||||||
|
|
||||||
// ── Client: Demo Client ──
|
// ── Client: Demo Client ──
|
||||||
const [existingClient] = await db
|
const [existingClient] = await db
|
||||||
|
|||||||
Reference in New Issue
Block a user