fix(db): remove dedup DELETE and use ON CONFLICT (name) for idempotent services seed
The dedup DELETE was causing two problems: 1. FK violation (GRO-365) — deleting services referenced by appointments 2. Duplicate services (GRO-301) — MIN(id) per name could delete the wrong row, causing ON CONFLICT (id) to create duplicates on re-run Fix: - Remove the dedup DELETE entirely - Keep TRUNCATE of downstream tables (appointments, invoices, etc.) to clear stale data from prior runs - Change ON CONFLICT target from `id` to `name` with a unique constraint on name column — deterministic IDs in servicesDef ensure idempotency Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -141,7 +141,7 @@ export const pets = pgTable("pets", {
|
|||||||
|
|
||||||
export const services = pgTable("services", {
|
export const services = pgTable("services", {
|
||||||
id: uuid("id").primaryKey().defaultRandom(),
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull().unique(),
|
||||||
description: text("description"),
|
description: text("description"),
|
||||||
basePriceCents: integer("base_price_cents").notNull(),
|
basePriceCents: integer("base_price_cents").notNull(),
|
||||||
durationMinutes: integer("duration_minutes").notNull(),
|
durationMinutes: integer("duration_minutes").notNull(),
|
||||||
|
|||||||
+7
-10
@@ -423,17 +423,14 @@ async function seed() {
|
|||||||
}
|
}
|
||||||
console.log(`✓ Created ${allStaff.length} staff (1 manager, 1 receptionist, 3 groomers, 3 bathers)`);
|
console.log(`✓ Created ${allStaff.length} staff (1 manager, 1 receptionist, 3 groomers, 3 bathers)`);
|
||||||
|
|
||||||
// Truncate downstream tables before services dedup to avoid FK violation
|
// Truncate downstream tables before services upsert — clears stale appointments
|
||||||
|
// from prior seed runs so the FK constraint on service_id is never violated
|
||||||
await db.execute(sql`TRUNCATE appointments, invoices, invoice_line_items, invoice_tip_splits, grooming_visit_logs CASCADE`);
|
await db.execute(sql`TRUNCATE appointments, invoices, invoice_line_items, invoice_tip_splits, grooming_visit_logs CASCADE`);
|
||||||
|
|
||||||
// ── Services ──
|
// ── Services ──
|
||||||
// Deduplicate existing services (keep lowest id per name) before inserting.
|
// Upsert services using name as unique key. With deterministic IDs in
|
||||||
await db.execute(sql`
|
// servicesDef and TRUNCATE clearing downstream tables first, this is
|
||||||
DELETE FROM services WHERE id NOT IN (
|
// idempotent: first run inserts, subsequent runs update existing rows.
|
||||||
SELECT (MIN(id::text))::uuid FROM services GROUP BY name
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
const serviceIds: string[] = [];
|
const serviceIds: string[] = [];
|
||||||
for (const s of servicesDef) {
|
for (const s of servicesDef) {
|
||||||
serviceIds.push(s.id);
|
serviceIds.push(s.id);
|
||||||
@@ -447,8 +444,8 @@ async function seed() {
|
|||||||
active: true,
|
active: true,
|
||||||
})
|
})
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: schema.services.id,
|
target: schema.services.name,
|
||||||
set: { name: s.name, description: s.desc, basePriceCents: s.price, durationMinutes: s.dur, active: true },
|
set: { description: s.desc, basePriceCents: s.price, durationMinutes: s.dur, active: true },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log(`✓ Created ${servicesDef.length} services`);
|
console.log(`✓ Created ${servicesDef.length} services`);
|
||||||
|
|||||||
Reference in New Issue
Block a user