|
|
|
@@ -20,6 +20,7 @@ import postgres from "postgres";
|
|
|
|
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
|
|
|
import { eq, and, sql } from "drizzle-orm";
|
|
|
|
|
import * as schema from "./schema.js";
|
|
|
|
|
import type { MedicalAlert } from "@groombook/types";
|
|
|
|
|
|
|
|
|
|
// ── Seed profile configuration ─────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
@@ -243,6 +244,59 @@ const groomingNotes = [
|
|
|
|
|
"Previous clipper burn — be gentle on belly",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ── Extended pet profile pools ─────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
const temperamentFlagPool: string[] = [
|
|
|
|
|
"friendly",
|
|
|
|
|
"anxious-with-strangers",
|
|
|
|
|
"good-with-kids",
|
|
|
|
|
"leash-reactive",
|
|
|
|
|
"vocal",
|
|
|
|
|
"high-energy",
|
|
|
|
|
"calm-on-table",
|
|
|
|
|
"treat-motivated",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const medicalAlertPool: MedicalAlert[] = [
|
|
|
|
|
{ id: "", type: "allergies", description: "Seasonal allergies — monitor skin", severity: "low" },
|
|
|
|
|
{ id: "", type: "allergies", description: "Chicken allergy — avoid poultry-based treats", severity: "high" },
|
|
|
|
|
{ id: "", type: "joint", description: "Hip dysplasia — handle with care", severity: "medium" },
|
|
|
|
|
{ id: "", type: "joint", description: "Arthritis — anti-inflammatory medication on file", severity: "medium" },
|
|
|
|
|
{ id: "", type: "dental", description: "Dental disease — extractions in history", severity: "medium" },
|
|
|
|
|
{ id: "", type: "dental", description: "Baby teeth retained — vet monitor", severity: "low" },
|
|
|
|
|
{ id: "", type: "heart", description: "Heart murmur grade II — avoid stress", severity: "high" },
|
|
|
|
|
{ id: "", type: "heart", description: "Murmur cleared by vet last year", severity: "low" },
|
|
|
|
|
{ id: "", type: "other", description: "Eye ulcer history — be careful around face", severity: "medium" },
|
|
|
|
|
{ id: "", type: "other", description: "Seizure history — avoid flashing lights", severity: "high" },
|
|
|
|
|
{ id: "", type: "other", description: "Luxating patella — short walks only", severity: "medium" },
|
|
|
|
|
{ id: "", type: "other", description: "Ear infections — dry thoroughly after bath", severity: "low" },
|
|
|
|
|
{ id: "", type: "behavioral", description: "Anxiety — calm environment preferred", severity: "low" },
|
|
|
|
|
{ id: "", type: "behavioral", description: "Fear-based aggression — approach with caution", severity: "high" },
|
|
|
|
|
{ id: "", type: "skin", description: "Contact dermatitis — avoid harsh chemicals", severity: "medium" },
|
|
|
|
|
{ id: "", type: "skin", description: "Hot spots — monitor and report any worsening", severity: "high" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const preferredCutPool: string[] = [
|
|
|
|
|
"Puppy Cut",
|
|
|
|
|
"Teddy Bear Cut",
|
|
|
|
|
"Lion Cut",
|
|
|
|
|
"Breed Standard",
|
|
|
|
|
"Summer Shave",
|
|
|
|
|
"Kennel Cut",
|
|
|
|
|
"Lamb Cut",
|
|
|
|
|
"Continental Clip",
|
|
|
|
|
"Sporting Clip",
|
|
|
|
|
"Sanitary Trim",
|
|
|
|
|
"Face & Feet Trim",
|
|
|
|
|
"Full Groom",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
type CoatType = (typeof schema.coatTypeEnum.enumValues)[number];
|
|
|
|
|
type PetSizeCategory = (typeof schema.petSizeCategoryEnum.enumValues)[number];
|
|
|
|
|
|
|
|
|
|
const coatTypePool: CoatType[] = ["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"];
|
|
|
|
|
const petSizeCategoryPool: PetSizeCategory[] = ["small", "medium", "large", "extra_large"];
|
|
|
|
|
|
|
|
|
|
const appointmentNotes = [
|
|
|
|
|
null, null, null, null,
|
|
|
|
|
"Client requested extra brushing",
|
|
|
|
@@ -335,78 +389,19 @@ const servicesDef = [
|
|
|
|
|
{ id: "b0000001-0000-0000-0000-00000000000a", name: "Sanitary Trim", desc: "Hygienic trim of paw pads, face, and sanitary areas", price: 2500, dur: 20 },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ── Known-users-only seed (prod/demo) ───────────────────────────────────────
|
|
|
|
|
// ── UAT staff account seeding (shared between seed paths) ─────────────────────
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Seeds only the minimal known users for prod/demo environments.
|
|
|
|
|
* Creates: Demo Manager staff + Demo Client + Demo Dog + basic services.
|
|
|
|
|
* Idempotent: skips creation if records already exist.
|
|
|
|
|
* Seeds or upserts the deterministic UAT staff accounts with numeric OIDC subs
|
|
|
|
|
* from SEED_UAT_*_OIDC_SUB / SEED_UAT_GROOMER_OIDC_SUBS env vars.
|
|
|
|
|
*
|
|
|
|
|
* In the full seed path this must run AFTER random staff are created so the
|
|
|
|
|
* deterministic upserts land on the correct rows (groomers referenced by the
|
|
|
|
|
* UAT test-client appointment logic use groomers[0] etc.).
|
|
|
|
|
*
|
|
|
|
|
* In seedKnownUsers() this replaces the inline UAT-staff block.
|
|
|
|
|
*/
|
|
|
|
|
async function seedKnownUsers() {
|
|
|
|
|
const url = process.env.DATABASE_URL;
|
|
|
|
|
if (!url) {
|
|
|
|
|
console.error("DATABASE_URL is not set");
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const client = postgres(url, { max: 5 });
|
|
|
|
|
const db = drizzle(client, { schema });
|
|
|
|
|
|
|
|
|
|
console.log("Seeding known users (prod/demo mode)...\n");
|
|
|
|
|
|
|
|
|
|
const KNOWN_STAFF_ID = "00000000-0000-0000-0000-000000000001";
|
|
|
|
|
const DEMO_CLIENT_ID = "00000000-0000-0000-0000-000000000002";
|
|
|
|
|
const DEMO_PET_ID = "00000000-0000-0000-0000-000000000003";
|
|
|
|
|
|
|
|
|
|
// ── Staff: Demo Manager ──
|
|
|
|
|
const [existingStaff] = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(schema.staff)
|
|
|
|
|
.where(eq(schema.staff.email, "demo-manager@groombook.dev"))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
if (existingStaff) {
|
|
|
|
|
console.log(`✓ Staff '${existingStaff.name}' already exists — skipping`);
|
|
|
|
|
} else {
|
|
|
|
|
await db.insert(schema.staff).values({
|
|
|
|
|
id: KNOWN_STAFF_ID,
|
|
|
|
|
name: "Demo Manager",
|
|
|
|
|
email: "demo-manager@groombook.dev",
|
|
|
|
|
oidcSub: "demo-manager-001",
|
|
|
|
|
role: "manager",
|
|
|
|
|
isSuperUser: true,
|
|
|
|
|
active: true,
|
|
|
|
|
});
|
|
|
|
|
console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Staff: SEED_ADMIN_EMAIL admin ──
|
|
|
|
|
const adminEmail = process.env.SEED_ADMIN_EMAIL;
|
|
|
|
|
if (adminEmail) {
|
|
|
|
|
const adminName = process.env.SEED_ADMIN_NAME ?? "Admin";
|
|
|
|
|
const ADMIN_STAFF_ID = "00000000-0000-0000-0000-000000000002";
|
|
|
|
|
const [existingAdmin] = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(schema.staff)
|
|
|
|
|
.where(eq(schema.staff.email, adminEmail))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
if (existingAdmin) {
|
|
|
|
|
console.log(`✓ Staff admin '${existingAdmin.name}' already exists — skipping`);
|
|
|
|
|
} else {
|
|
|
|
|
await db.insert(schema.staff).values({
|
|
|
|
|
id: ADMIN_STAFF_ID,
|
|
|
|
|
name: adminName,
|
|
|
|
|
email: adminEmail,
|
|
|
|
|
oidcSub: adminEmail,
|
|
|
|
|
role: "manager",
|
|
|
|
|
isSuperUser: true,
|
|
|
|
|
active: true,
|
|
|
|
|
});
|
|
|
|
|
console.log(`✓ Created staff admin '${adminName}' (${adminEmail})`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function seedUatStaffAccounts(db: ReturnType<typeof drizzle>) {
|
|
|
|
|
// ── Staff: UAT Super User (oidcSub from SEED_UAT_SUPER_OIDC_SUB env var) ──
|
|
|
|
|
const uatSuperOidcSub = process.env.SEED_UAT_SUPER_OIDC_SUB;
|
|
|
|
|
if (uatSuperOidcSub) {
|
|
|
|
@@ -574,6 +569,141 @@ async function seedKnownUsers() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Client: UAT Customer ─────────────────────────────────────────────────────
|
|
|
|
|
// Only uat-customer is a real end-user who needs a clients row.
|
|
|
|
|
// uat-groomer and uat-super are staff — they have staff records, not client records.
|
|
|
|
|
const UAT_CUSTOMER_ID = "c0000001-0000-0000-0000-000000000001";
|
|
|
|
|
const [uatCustomerRow] = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(schema.clients)
|
|
|
|
|
.where(eq(schema.clients.email, "uat-customer@groombook.dev"))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
let uatCustomerClientId: string;
|
|
|
|
|
if (uatCustomerRow) {
|
|
|
|
|
uatCustomerClientId = uatCustomerRow.id;
|
|
|
|
|
console.log(`✓ UAT Customer client record already exists — skipping`);
|
|
|
|
|
} else {
|
|
|
|
|
const [created] = await db
|
|
|
|
|
.insert(schema.clients)
|
|
|
|
|
.values({
|
|
|
|
|
id: UAT_CUSTOMER_ID,
|
|
|
|
|
email: "uat-customer@groombook.dev",
|
|
|
|
|
name: "UAT Customer",
|
|
|
|
|
phone: "555-0102",
|
|
|
|
|
address: "1 UAT Lane, Test City, CA 90210",
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
uatCustomerClientId = created!.id;
|
|
|
|
|
console.log(`✓ Created client 'UAT Customer' for SSO bridge`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Pets: UAT Customer's dogs ────────────────────────────────────────────────
|
|
|
|
|
const uatCustomerPets = [
|
|
|
|
|
{ id: "c0000001-0000-0000-0000-000000000002", name: "UAT Pup Alpha", species: "Dog", breed: "Beagle", weight: "12.00", dob: "2022-03-10", image: "/demo-pets/dog-beagle.png" },
|
|
|
|
|
{ id: "c0000001-0000-0000-0000-000000000003", name: "UAT Pup Beta", species: "Dog", breed: "Labrador", weight: "28.00", dob: "2021-07-22", image: "/demo-pets/dog-labrador.png" },
|
|
|
|
|
];
|
|
|
|
|
for (const pet of uatCustomerPets) {
|
|
|
|
|
const [existing] = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(schema.pets)
|
|
|
|
|
.where(eq(schema.pets.id, pet.id))
|
|
|
|
|
.limit(1);
|
|
|
|
|
if (existing) {
|
|
|
|
|
console.log(`✓ UAT Pet '${existing.name}' already exists — skipping`);
|
|
|
|
|
} else {
|
|
|
|
|
await db.insert(schema.pets).values({
|
|
|
|
|
id: pet.id,
|
|
|
|
|
clientId: uatCustomerClientId,
|
|
|
|
|
name: pet.name,
|
|
|
|
|
species: pet.species,
|
|
|
|
|
breed: pet.breed,
|
|
|
|
|
weightKg: pet.weight,
|
|
|
|
|
dateOfBirth: new Date(`${pet.dob}T00:00:00Z`),
|
|
|
|
|
image: pet.image,
|
|
|
|
|
});
|
|
|
|
|
console.log(`✓ Created UAT pet '${pet.name}'`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Known-users-only seed (prod/demo) ───────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Seeds only the minimal known users for prod/demo environments.
|
|
|
|
|
* Creates: Demo Manager staff + Demo Client + Demo Dog + basic services.
|
|
|
|
|
* Idempotent: skips creation if records already exist.
|
|
|
|
|
*/
|
|
|
|
|
async function seedKnownUsers() {
|
|
|
|
|
const url = process.env.DATABASE_URL;
|
|
|
|
|
if (!url) {
|
|
|
|
|
console.error("DATABASE_URL is not set");
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const client = postgres(url, { max: 5 });
|
|
|
|
|
const db = drizzle(client, { schema });
|
|
|
|
|
|
|
|
|
|
console.log("Seeding known users (prod/demo mode)...\n");
|
|
|
|
|
|
|
|
|
|
const KNOWN_STAFF_ID = "00000000-0000-0000-0000-000000000001";
|
|
|
|
|
const DEMO_CLIENT_ID = "00000000-0000-0000-0000-000000000002";
|
|
|
|
|
const DEMO_PET_ID = "00000000-0000-0000-0000-000000000003";
|
|
|
|
|
|
|
|
|
|
// ── Staff: Demo Manager ──
|
|
|
|
|
const [existingStaff] = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(schema.staff)
|
|
|
|
|
.where(eq(schema.staff.email, "demo-manager@groombook.dev"))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
if (existingStaff) {
|
|
|
|
|
console.log(`✓ Staff '${existingStaff.name}' already exists — skipping`);
|
|
|
|
|
} else {
|
|
|
|
|
await db.insert(schema.staff).values({
|
|
|
|
|
id: KNOWN_STAFF_ID,
|
|
|
|
|
name: "Demo Manager",
|
|
|
|
|
email: "demo-manager@groombook.dev",
|
|
|
|
|
oidcSub: "demo-manager-001",
|
|
|
|
|
role: "manager",
|
|
|
|
|
isSuperUser: true,
|
|
|
|
|
active: true,
|
|
|
|
|
});
|
|
|
|
|
console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Staff: SEED_ADMIN_EMAIL admin ──
|
|
|
|
|
const adminEmail = process.env.SEED_ADMIN_EMAIL;
|
|
|
|
|
if (adminEmail) {
|
|
|
|
|
const adminName = process.env.SEED_ADMIN_NAME ?? "Admin";
|
|
|
|
|
const ADMIN_STAFF_ID = "00000000-0000-0000-0000-000000000002";
|
|
|
|
|
const [existingAdmin] = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(schema.staff)
|
|
|
|
|
.where(eq(schema.staff.email, adminEmail))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
if (existingAdmin) {
|
|
|
|
|
console.log(`✓ Staff admin '${existingAdmin.name}' already exists — skipping`);
|
|
|
|
|
} else {
|
|
|
|
|
await db.insert(schema.staff).values({
|
|
|
|
|
id: ADMIN_STAFF_ID,
|
|
|
|
|
name: adminName,
|
|
|
|
|
email: adminEmail,
|
|
|
|
|
oidcSub: adminEmail,
|
|
|
|
|
role: "manager",
|
|
|
|
|
isSuperUser: true,
|
|
|
|
|
active: true,
|
|
|
|
|
});
|
|
|
|
|
console.log(`✓ Created staff admin '${adminName}' (${adminEmail})`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── UAT staff accounts + Better Auth credentials (shared impl) ──────────────
|
|
|
|
|
// Extracted into seedUatStaffAccounts() so it runs in both seedKnownUsers()
|
|
|
|
|
// and the full seed() UAT branch.
|
|
|
|
|
await seedUatStaffAccounts(db);
|
|
|
|
|
|
|
|
|
|
// ── Services: idempotent upsert using name as unique key ─────────────────────
|
|
|
|
|
// UNIQUE constraint on services.name (migration 0020) must exist first.
|
|
|
|
|
// Uses b0000001-... IDs to match main seed servicesDef for same-named services.
|
|
|
|
@@ -740,30 +870,10 @@ async function seed() {
|
|
|
|
|
console.log(`✓ Upserted admin staff '${adminName}' (${adminEmail})`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── UAT Groomer Personas (SEED_UAT_GROOMER_EMAILS + SEED_UAT_GROOMER_NAMES) ──
|
|
|
|
|
const groomerEmails = process.env.SEED_UAT_GROOMER_EMAILS?.split(",").map((e) => e.trim()).filter(Boolean) ?? [];
|
|
|
|
|
const groomerNames = process.env.SEED_UAT_GROOMER_NAMES?.split(",").map((n) => n.trim()).filter(Boolean) ?? [];
|
|
|
|
|
const groomerCount = Math.min(groomerEmails.length, groomerNames.length);
|
|
|
|
|
for (let i = 0; i < groomerCount; i++) {
|
|
|
|
|
const email = groomerEmails[i]!;
|
|
|
|
|
const name = groomerNames[i]!;
|
|
|
|
|
const staffId = `00000000-0000-0000-0000-${String(5 + i).padStart(12, "0")}`;
|
|
|
|
|
await db.insert(schema.staff)
|
|
|
|
|
.values({
|
|
|
|
|
id: staffId,
|
|
|
|
|
name,
|
|
|
|
|
email,
|
|
|
|
|
oidcSub: email,
|
|
|
|
|
role: "groomer",
|
|
|
|
|
isSuperUser: false,
|
|
|
|
|
active: true,
|
|
|
|
|
})
|
|
|
|
|
.onConflictDoUpdate({
|
|
|
|
|
target: schema.staff.email,
|
|
|
|
|
set: { id: staffId, name, role: "groomer", isSuperUser: false, active: true },
|
|
|
|
|
});
|
|
|
|
|
console.log(`✓ Upserted groomer '${name}' (${email})`);
|
|
|
|
|
}
|
|
|
|
|
// ── UAT staff accounts + Better Auth credentials (shared impl) ──────────────
|
|
|
|
|
// Seeds deterministic UAT staff with numeric OIDC subs and Better Auth credentials.
|
|
|
|
|
// Must run AFTER random staff are created so upserts land correctly.
|
|
|
|
|
await seedUatStaffAccounts(db);
|
|
|
|
|
|
|
|
|
|
// ── Services ──
|
|
|
|
|
// Upsert services using name as unique key. With deterministic IDs in
|
|
|
|
@@ -853,6 +963,26 @@ async function seed() {
|
|
|
|
|
specialCareNotes: rand() < 0.1 ? "Vet clearance required before grooming" : null,
|
|
|
|
|
customFields: {},
|
|
|
|
|
image: petIndex < 250 ? pick(puggleImages) : pick(demoPetImages),
|
|
|
|
|
temperamentScore: randInt(1, 5),
|
|
|
|
|
temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)),
|
|
|
|
|
medicalAlerts: (() => {
|
|
|
|
|
// Deterministic alerts for UAT AC pets
|
|
|
|
|
if (uc.petName === "TestCooper") {
|
|
|
|
|
return pickN(medicalAlertPool.filter((a) => a.type === "behavioral"), 1).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
if (uc.petName === "TestRocky") {
|
|
|
|
|
return pickN(medicalAlertPool.filter((a) => a.type === "skin"), 1).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
// Other UAT pets: random
|
|
|
|
|
if (rand() < 0.3) {
|
|
|
|
|
const count = rand() < 0.7 ? 1 : 2;
|
|
|
|
|
return pickN(medicalAlertPool, count).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
})(),
|
|
|
|
|
preferredCuts: pickN(preferredCutPool, randInt(1, 2)),
|
|
|
|
|
coatType: pick(coatTypePool),
|
|
|
|
|
petSizeCategory: pick(petSizeCategoryPool),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
petRecords.push({ id: petId, clientId });
|
|
|
|
@@ -888,6 +1018,12 @@ async function seed() {
|
|
|
|
|
specialCareNotes: pet.specialCareNotes,
|
|
|
|
|
customFields: pet.customFields,
|
|
|
|
|
image: pet.image,
|
|
|
|
|
temperamentScore: pet.temperamentScore,
|
|
|
|
|
temperamentFlags: pet.temperamentFlags,
|
|
|
|
|
medicalAlerts: pet.medicalAlerts,
|
|
|
|
|
preferredCuts: pet.preferredCuts,
|
|
|
|
|
coatType: pet.coatType,
|
|
|
|
|
petSizeCategory: pet.petSizeCategory,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
@@ -922,8 +1058,68 @@ async function seed() {
|
|
|
|
|
.values({ id: uc.id, name: uc.name, email: uc.email, phone: uc.phone, address: uc.address })
|
|
|
|
|
.onConflictDoUpdate({ target: schema.clients.id, set: { name: uc.name, email: uc.email, phone: uc.phone, address: uc.address } });
|
|
|
|
|
await db.insert(schema.pets)
|
|
|
|
|
.values({ id: uc.petId, clientId: uc.id, name: uc.petName, species: "Dog", breed: uc.petBreed, weightKg: "25.00", dateOfBirth: new Date("2021-03-15T00:00:00Z"), image: pick(demoPetImages) })
|
|
|
|
|
.onConflictDoUpdate({ target: schema.pets.id, set: { clientId: uc.id, name: uc.petName, species: "Dog", breed: uc.petBreed, weightKg: "25.00", dateOfBirth: new Date("2021-03-15T00:00:00Z"), image: pick(demoPetImages) } });
|
|
|
|
|
.values({
|
|
|
|
|
id: uc.petId,
|
|
|
|
|
clientId: uc.id,
|
|
|
|
|
name: uc.petName,
|
|
|
|
|
species: "Dog",
|
|
|
|
|
breed: uc.petBreed,
|
|
|
|
|
weightKg: "25.00",
|
|
|
|
|
dateOfBirth: new Date("2021-03-15T00:00:00Z"),
|
|
|
|
|
image: pick(demoPetImages),
|
|
|
|
|
temperamentScore: randInt(1, 5),
|
|
|
|
|
temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)),
|
|
|
|
|
medicalAlerts: (() => {
|
|
|
|
|
// Deterministic alerts for UAT AC pets
|
|
|
|
|
if (uc.petName === "TestCooper") {
|
|
|
|
|
return pickN(medicalAlertPool.filter((a) => a.type === "behavioral"), 1).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
if (uc.petName === "TestRocky") {
|
|
|
|
|
return pickN(medicalAlertPool.filter((a) => a.type === "skin"), 1).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
// Other UAT pets: random
|
|
|
|
|
if (rand() < 0.3) {
|
|
|
|
|
const count = rand() < 0.7 ? 1 : 2;
|
|
|
|
|
return pickN(medicalAlertPool, count).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
})(),
|
|
|
|
|
preferredCuts: pickN(preferredCutPool, randInt(1, 2)),
|
|
|
|
|
coatType: pick(coatTypePool),
|
|
|
|
|
petSizeCategory: pick(petSizeCategoryPool),
|
|
|
|
|
})
|
|
|
|
|
.onConflictDoUpdate({
|
|
|
|
|
target: schema.pets.id,
|
|
|
|
|
set: {
|
|
|
|
|
clientId: uc.id,
|
|
|
|
|
name: uc.petName,
|
|
|
|
|
species: "Dog",
|
|
|
|
|
breed: uc.petBreed,
|
|
|
|
|
weightKg: "25.00",
|
|
|
|
|
dateOfBirth: new Date("2021-03-15T00:00:00Z"),
|
|
|
|
|
image: pick(demoPetImages),
|
|
|
|
|
temperamentScore: randInt(1, 5),
|
|
|
|
|
temperamentFlags: pickN(temperamentFlagPool, randInt(1, 3)),
|
|
|
|
|
medicalAlerts: (() => {
|
|
|
|
|
// Deterministic alerts for UAT AC pets
|
|
|
|
|
if (uc.petName === "TestCooper") {
|
|
|
|
|
return pickN(medicalAlertPool.filter((a) => a.type === "behavioral"), 1).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
if (uc.petName === "TestRocky") {
|
|
|
|
|
return pickN(medicalAlertPool.filter((a) => a.type === "skin"), 1).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
// Other UAT pets: random
|
|
|
|
|
if (rand() < 0.3) {
|
|
|
|
|
const count = rand() < 0.7 ? 1 : 2;
|
|
|
|
|
return pickN(medicalAlertPool, count).map((a) => ({ ...a, id: uuid() }));
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
})(),
|
|
|
|
|
preferredCuts: pickN(preferredCutPool, randInt(1, 2)),
|
|
|
|
|
coatType: pick(coatTypePool),
|
|
|
|
|
petSizeCategory: pick(petSizeCategoryPool),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
// Create one completed appointment for this client
|
|
|
|
|
const apptId = uuid();
|
|
|
|
|
const svcIdx = 0;
|
|
|
|
|