import { Pool } from 'pg'; import { resolveDatabasePoolConfig } from '../src/services/db.js'; import { V2DemoFixture as fixture } from './v2-demo-fixture.mjs'; const poolConfig = resolveDatabasePoolConfig(); if (!poolConfig) { // eslint-disable-next-line no-console console.error('Database connection settings are required'); process.exit(1); } const pool = new Pool(poolConfig); function hoursFromNow(hours) { return new Date(Date.now() + (hours * 60 * 60 * 1000)).toISOString(); } async function upsertUser(client, user) { await client.query( ` INSERT INTO users (id, email, display_name, status, metadata) VALUES ($1, $2, $3, 'ACTIVE', '{}'::jsonb) ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email, display_name = EXCLUDED.display_name, status = 'ACTIVE', updated_at = NOW() `, [user.id, user.email || null, user.displayName || null] ); } async function main() { const client = await pool.connect(); try { await client.query('BEGIN'); await client.query('DELETE FROM tenants WHERE id = $1', [fixture.tenant.id]); const openStartsAt = hoursFromNow(4); const openEndsAt = hoursFromNow(12); const completedStartsAt = hoursFromNow(-28); const completedEndsAt = hoursFromNow(-20); const checkedInAt = hoursFromNow(-27.5); const checkedOutAt = hoursFromNow(-20.25); const invoiceDueAt = hoursFromNow(72); await upsertUser(client, fixture.users.businessOwner); await upsertUser(client, fixture.users.operationsManager); await upsertUser(client, fixture.users.vendorManager); await upsertUser(client, fixture.users.staffAna); await client.query( ` INSERT INTO tenants (id, slug, name, status, metadata) VALUES ($1, $2, $3, 'ACTIVE', $4::jsonb) `, [fixture.tenant.id, fixture.tenant.slug, fixture.tenant.name, JSON.stringify({ seededBy: 'seed-v2-demo-data' })] ); await client.query( ` INSERT INTO tenant_memberships (tenant_id, user_id, membership_status, base_role, metadata) VALUES ($1, $2, 'ACTIVE', 'admin', '{"persona":"business_owner"}'::jsonb), ($1, $3, 'ACTIVE', 'manager', '{"persona":"ops_manager"}'::jsonb), ($1, $4, 'ACTIVE', 'manager', '{"persona":"vendor_manager"}'::jsonb), ($1, $5, 'ACTIVE', 'member', '{"persona":"staff"}'::jsonb) `, [ fixture.tenant.id, fixture.users.businessOwner.id, fixture.users.operationsManager.id, fixture.users.vendorManager.id, fixture.users.staffAna.id, ] ); await client.query( ` INSERT INTO businesses ( id, tenant_id, slug, business_name, status, contact_name, contact_email, contact_phone, metadata ) VALUES ($1, $2, $3, $4, 'ACTIVE', $5, $6, $7, $8::jsonb) `, [ fixture.business.id, fixture.tenant.id, fixture.business.slug, fixture.business.name, 'Legendary Client Manager', fixture.users.businessOwner.email, '+15550001001', JSON.stringify({ segment: 'buyer', seeded: true }), ] ); await client.query( ` INSERT INTO business_memberships ( tenant_id, business_id, user_id, membership_status, business_role, metadata ) VALUES ($1, $2, $3, 'ACTIVE', 'owner', '{"persona":"client_owner"}'::jsonb), ($1, $2, $4, 'ACTIVE', 'manager', '{"persona":"client_ops"}'::jsonb) `, [fixture.tenant.id, fixture.business.id, fixture.users.businessOwner.id, fixture.users.operationsManager.id] ); await client.query( ` INSERT INTO vendors ( id, tenant_id, slug, company_name, status, contact_name, contact_email, contact_phone, metadata ) VALUES ($1, $2, $3, $4, 'ACTIVE', $5, $6, $7, $8::jsonb) `, [ fixture.vendor.id, fixture.tenant.id, fixture.vendor.slug, fixture.vendor.name, 'Vendor Manager', fixture.users.vendorManager.email, '+15550001002', JSON.stringify({ kind: 'internal_pool', seeded: true }), ] ); await client.query( ` INSERT INTO vendor_memberships ( tenant_id, vendor_id, user_id, membership_status, vendor_role, metadata ) VALUES ($1, $2, $3, 'ACTIVE', 'owner', '{"persona":"vendor_owner"}'::jsonb) `, [fixture.tenant.id, fixture.vendor.id, fixture.users.vendorManager.id] ); await client.query( ` INSERT INTO cost_centers (id, tenant_id, business_id, code, name, status, metadata) VALUES ($1, $2, $3, 'CAFE_OPS', $4, 'ACTIVE', '{"seeded":true}'::jsonb) `, [fixture.costCenters.cafeOps.id, fixture.tenant.id, fixture.business.id, fixture.costCenters.cafeOps.name] ); await client.query( ` INSERT INTO roles_catalog (id, tenant_id, code, name, status, metadata) VALUES ($1, $3, $4, $5, 'ACTIVE', '{}'::jsonb), ($2, $3, $6, $7, 'ACTIVE', '{}'::jsonb) `, [ fixture.roles.barista.id, fixture.roles.captain.id, fixture.tenant.id, fixture.roles.barista.code, fixture.roles.barista.name, fixture.roles.captain.code, fixture.roles.captain.name, ] ); await client.query( ` INSERT INTO staffs ( id, tenant_id, user_id, full_name, email, phone, status, primary_role, onboarding_status, average_rating, rating_count, metadata ) VALUES ($1, $2, $3, $4, $5, $6, 'ACTIVE', $7, 'COMPLETED', 4.50, 1, $8::jsonb) `, [ fixture.staff.ana.id, fixture.tenant.id, fixture.users.staffAna.id, fixture.staff.ana.fullName, fixture.staff.ana.email, fixture.staff.ana.phone, fixture.staff.ana.primaryRole, JSON.stringify({ favoriteCandidate: true, seeded: true, firstName: 'Ana', lastName: 'Barista', bio: 'Experienced barista and event staffing professional.', preferredLocations: [ { city: 'Mountain View', latitude: fixture.clockPoint.latitude, longitude: fixture.clockPoint.longitude, }, ], maxDistanceMiles: 20, industries: ['CATERING', 'CAFE'], skills: ['BARISTA', 'CUSTOMER_SERVICE'], emergencyContact: { name: 'Maria Barista', phone: '+15550007777', }, }), ] ); await client.query( ` INSERT INTO staff_roles (staff_id, role_id, is_primary) VALUES ($1, $2, TRUE) `, [fixture.staff.ana.id, fixture.roles.barista.id] ); await client.query( ` INSERT INTO workforce (id, tenant_id, vendor_id, staff_id, workforce_number, employment_type, status, metadata) VALUES ($1, $2, $3, $4, $5, 'TEMP', 'ACTIVE', $6::jsonb) `, [ fixture.workforce.ana.id, fixture.tenant.id, fixture.vendor.id, fixture.staff.ana.id, fixture.workforce.ana.workforceNumber, JSON.stringify({ source: 'seed-v2-demo' }), ] ); await client.query( ` INSERT INTO staff_availability ( id, tenant_id, staff_id, day_of_week, availability_status, time_slots, metadata ) VALUES ($1, $3, $4, 1, 'PARTIAL', '[{"start":"08:00","end":"18:00"}]'::jsonb, '{"seeded":true}'::jsonb), ($2, $3, $4, 5, 'PARTIAL', '[{"start":"09:00","end":"17:00"}]'::jsonb, '{"seeded":true}'::jsonb) `, [fixture.availability.monday.id, fixture.availability.friday.id, fixture.tenant.id, fixture.staff.ana.id] ); await client.query( ` INSERT INTO staff_benefits ( id, tenant_id, staff_id, benefit_type, title, status, tracked_hours, target_hours, metadata ) VALUES ($1, $2, $3, 'COMMUTER', $4, 'ACTIVE', 32, 40, '{"description":"Commuter stipend unlocked after 40 hours"}'::jsonb) `, [fixture.benefits.commuter.id, fixture.tenant.id, fixture.staff.ana.id, fixture.benefits.commuter.title] ); await client.query( ` INSERT INTO clock_points ( id, tenant_id, business_id, cost_center_id, label, address, latitude, longitude, geofence_radius_meters, nfc_tag_uid, status, metadata ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'ACTIVE', $11::jsonb) `, [ fixture.clockPoint.id, fixture.tenant.id, fixture.business.id, fixture.costCenters.cafeOps.id, fixture.clockPoint.label, fixture.clockPoint.address, fixture.clockPoint.latitude, fixture.clockPoint.longitude, fixture.clockPoint.geofenceRadiusMeters, fixture.clockPoint.nfcTagUid, JSON.stringify({ city: 'Mountain View', state: 'CA', zipCode: '94043', seeded: true }), ] ); await client.query( ` INSERT INTO hub_managers (id, tenant_id, hub_id, business_membership_id) SELECT $1, $2, $3, bm.id FROM business_memberships bm WHERE bm.business_id = $4 AND bm.user_id = $5 `, [ fixture.hubManagers.opsLead.id, fixture.tenant.id, fixture.clockPoint.id, fixture.business.id, fixture.users.operationsManager.id, ] ); await client.query( ` INSERT INTO orders ( id, tenant_id, business_id, vendor_id, order_number, title, description, status, service_type, starts_at, ends_at, location_name, location_address, latitude, longitude, notes, created_by_user_id, metadata ) VALUES ($1, $3, $4, $5, $6, $7, 'Open order for live v2 commands', 'OPEN', 'EVENT', $8, $9, 'Google Cafe', $10, $11, $12, 'Use this order for live smoke and frontend reads', $13, '{"slice":"open","orderType":"ONE_TIME"}'::jsonb), ($2, $3, $4, $5, $14, $15, 'Completed order for favorites, reviews, invoices, and attendance history', 'COMPLETED', 'CATERING', $16, $17, 'Google Catering', $10, $11, $12, 'Completed historical example', $13, '{"slice":"completed","orderType":"ONE_TIME"}'::jsonb) `, [ fixture.orders.open.id, fixture.orders.completed.id, fixture.tenant.id, fixture.business.id, fixture.vendor.id, fixture.orders.open.number, fixture.orders.open.title, openStartsAt, openEndsAt, fixture.clockPoint.address, fixture.clockPoint.latitude, fixture.clockPoint.longitude, fixture.users.businessOwner.id, fixture.orders.completed.number, fixture.orders.completed.title, completedStartsAt, completedEndsAt, ] ); await client.query( ` INSERT INTO shifts ( id, tenant_id, order_id, business_id, vendor_id, clock_point_id, shift_code, title, status, starts_at, ends_at, timezone, location_name, location_address, latitude, longitude, geofence_radius_meters, required_workers, assigned_workers, notes, metadata ) VALUES ($1, $3, $5, $7, $9, $11, $13, $15, 'OPEN', $17, $18, 'America/Los_Angeles', 'Google Cafe', $19, $21, $22, $23, 1, 0, 'Open staffing need', '{"slice":"open"}'::jsonb), ($2, $4, $6, $8, $10, $12, $14, $16, 'COMPLETED', $20, $24, 'America/Los_Angeles', 'Google Catering', $19, $21, $22, $23, 1, 1, 'Completed staffed shift', '{"slice":"completed"}'::jsonb) `, [ fixture.shifts.open.id, fixture.shifts.completed.id, fixture.tenant.id, fixture.tenant.id, fixture.orders.open.id, fixture.orders.completed.id, fixture.business.id, fixture.business.id, fixture.vendor.id, fixture.vendor.id, fixture.clockPoint.id, fixture.clockPoint.id, fixture.shifts.open.code, fixture.shifts.completed.code, fixture.shifts.open.title, fixture.shifts.completed.title, openStartsAt, openEndsAt, fixture.clockPoint.address, completedStartsAt, fixture.clockPoint.latitude, fixture.clockPoint.longitude, fixture.clockPoint.geofenceRadiusMeters, completedEndsAt, ] ); await client.query( ` INSERT INTO shift_roles ( id, shift_id, role_id, role_code, role_name, workers_needed, assigned_count, pay_rate_cents, bill_rate_cents, metadata ) VALUES ($1, $2, $3, $4, $5, 1, 0, 2200, 3500, '{"slice":"open"}'::jsonb), ($6, $7, $3, $4, $5, 1, 1, 2200, 3500, '{"slice":"completed"}'::jsonb) `, [ fixture.shiftRoles.openBarista.id, fixture.shifts.open.id, fixture.roles.barista.id, fixture.roles.barista.code, fixture.roles.barista.name, fixture.shiftRoles.completedBarista.id, fixture.shifts.completed.id, ] ); await client.query( ` INSERT INTO applications ( id, tenant_id, shift_id, shift_role_id, staff_id, status, origin, applied_at, metadata ) VALUES ($1, $2, $3, $4, $5, 'PENDING', 'STAFF', NOW(), '{"slice":"open"}'::jsonb) `, [ fixture.applications.openAna.id, fixture.tenant.id, fixture.shifts.open.id, fixture.shiftRoles.openBarista.id, fixture.staff.ana.id, ] ); await client.query( ` INSERT INTO assignments ( id, tenant_id, business_id, vendor_id, shift_id, shift_role_id, workforce_id, staff_id, status, assigned_at, accepted_at, checked_in_at, checked_out_at, metadata ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'COMPLETED', $9, $10, $11, $12, '{"slice":"completed"}'::jsonb) `, [ fixture.assignments.completedAna.id, fixture.tenant.id, fixture.business.id, fixture.vendor.id, fixture.shifts.completed.id, fixture.shiftRoles.completedBarista.id, fixture.workforce.ana.id, fixture.staff.ana.id, completedStartsAt, completedStartsAt, checkedInAt, checkedOutAt, ] ); await client.query( ` INSERT INTO attendance_events ( tenant_id, assignment_id, shift_id, staff_id, clock_point_id, event_type, source_type, source_reference, nfc_tag_uid, device_id, latitude, longitude, accuracy_meters, distance_to_clock_point_meters, within_geofence, validation_status, validation_reason, captured_at, raw_payload ) VALUES ($1, $2, $3, $4, $5, 'CLOCK_IN', 'NFC', 'seed', $6, 'seed-device', $7, $8, 5, 0, TRUE, 'ACCEPTED', NULL, $9, '{"seeded":true}'::jsonb), ($1, $2, $3, $4, $5, 'CLOCK_OUT', 'NFC', 'seed', $6, 'seed-device', $7, $8, 5, 0, TRUE, 'ACCEPTED', NULL, $10, '{"seeded":true}'::jsonb) `, [ fixture.tenant.id, fixture.assignments.completedAna.id, fixture.shifts.completed.id, fixture.staff.ana.id, fixture.clockPoint.id, fixture.clockPoint.nfcTagUid, fixture.clockPoint.latitude, fixture.clockPoint.longitude, checkedInAt, checkedOutAt, ] ); const attendanceEvents = await client.query( ` SELECT id, event_type FROM attendance_events WHERE assignment_id = $1 ORDER BY captured_at ASC `, [fixture.assignments.completedAna.id] ); await client.query( ` INSERT INTO attendance_sessions ( id, tenant_id, assignment_id, staff_id, clock_in_event_id, clock_out_event_id, status, check_in_at, check_out_at, worked_minutes, metadata ) VALUES ($1, $2, $3, $4, $5, $6, 'CLOSED', $7, $8, 435, '{"seeded":true}'::jsonb) `, [ '95f6017c-256c-4eb5-8033-eb942f018001', fixture.tenant.id, fixture.assignments.completedAna.id, fixture.staff.ana.id, attendanceEvents.rows.find((row) => row.event_type === 'CLOCK_IN')?.id, attendanceEvents.rows.find((row) => row.event_type === 'CLOCK_OUT')?.id, checkedInAt, checkedOutAt, ] ); await client.query( ` INSERT INTO timesheets ( id, tenant_id, assignment_id, staff_id, status, regular_minutes, overtime_minutes, break_minutes, gross_pay_cents, metadata ) VALUES ($1, $2, $3, $4, 'APPROVED', 420, 15, 30, 15950, '{"seeded":true}'::jsonb) `, [fixture.timesheets.completedAna.id, fixture.tenant.id, fixture.assignments.completedAna.id, fixture.staff.ana.id] ); await client.query( ` INSERT INTO documents (id, tenant_id, document_type, name, required_for_role_code, metadata) VALUES ($1, $2, 'CERTIFICATION', $3, $6, '{"seeded":true}'::jsonb), ($4, $2, 'ATTIRE', $5, $6, '{"seeded":true}'::jsonb), ($7, $2, 'TAX_FORM', $8, $6, '{"seeded":true}'::jsonb) `, [ fixture.documents.foodSafety.id, fixture.tenant.id, fixture.documents.foodSafety.name, fixture.documents.attireBlackShirt.id, fixture.documents.attireBlackShirt.name, fixture.roles.barista.code, fixture.documents.taxFormW9.id, fixture.documents.taxFormW9.name, ] ); await client.query( ` INSERT INTO staff_documents (id, tenant_id, staff_id, document_id, file_uri, status, expires_at, metadata) VALUES ($1, $2, $3, $4, $5, 'VERIFIED', $6, '{"seeded":true}'::jsonb), ($7, $2, $3, $8, $9, 'VERIFIED', NULL, '{"seeded":true}'::jsonb), ($10, $2, $3, $11, $12, 'VERIFIED', NULL, '{"seeded":true}'::jsonb) `, [ fixture.staffDocuments.foodSafety.id, fixture.tenant.id, fixture.staff.ana.id, fixture.documents.foodSafety.id, `gs://krow-workforce-dev-v2-private/uploads/${fixture.staff.ana.id}/food-handler-card.pdf`, hoursFromNow(24 * 180), fixture.staffDocuments.attireBlackShirt.id, fixture.documents.attireBlackShirt.id, `gs://krow-workforce-dev-v2-private/uploads/${fixture.staff.ana.id}/black-shirt.jpg`, fixture.staffDocuments.taxFormW9.id, fixture.documents.taxFormW9.id, `gs://krow-workforce-dev-v2-private/uploads/${fixture.staff.ana.id}/w9-form.pdf`, ] ); await client.query( ` INSERT INTO certificates (id, tenant_id, staff_id, certificate_type, certificate_number, issued_at, expires_at, status, metadata) VALUES ($1, $2, $3, 'FOOD_SAFETY', 'FH-ANA-2026', $4, $5, 'VERIFIED', '{"seeded":true}'::jsonb) `, [ fixture.certificates.foodSafety.id, fixture.tenant.id, fixture.staff.ana.id, hoursFromNow(-24 * 30), hoursFromNow(24 * 180), ] ); await client.query( ` INSERT INTO verification_jobs ( tenant_id, staff_id, document_id, type, file_uri, status, idempotency_key, provider_name, provider_reference, confidence, reasons, extracted, review, metadata ) VALUES ( $1, $2, $3, 'certification', $4, 'APPROVED', 'seed-certification-job', 'seed', 'seed-certification-provider', 0.980, '["Verified by seed"]'::jsonb, '{"certificateType":"FOOD_SAFETY"}'::jsonb, '{"decision":"APPROVED"}'::jsonb, '{"seeded":true}'::jsonb ) `, [ fixture.tenant.id, fixture.staff.ana.id, fixture.documents.foodSafety.id, `gs://krow-workforce-dev-v2-private/uploads/${fixture.staff.ana.id}/food-handler-card.pdf`, ] ); await client.query( ` INSERT INTO accounts ( id, tenant_id, owner_type, owner_business_id, owner_vendor_id, owner_staff_id, provider_name, provider_reference, last4, is_primary, metadata ) VALUES ($1, $3, 'BUSINESS', $4, NULL, NULL, 'stripe', 'ba_business_demo', '6789', TRUE, '{"seeded":true,"accountType":"CHECKING","routingNumberMasked":"*****0001"}'::jsonb), ($2, $3, 'STAFF', NULL, NULL, $5, 'stripe', 'ba_staff_demo', '4321', TRUE, '{"seeded":true,"accountType":"CHECKING","routingNumberMasked":"*****0002"}'::jsonb) `, [ fixture.accounts.businessPrimary.id, fixture.accounts.staffPrimary.id, fixture.tenant.id, fixture.business.id, fixture.staff.ana.id, ] ); await client.query( ` INSERT INTO invoices ( id, tenant_id, order_id, business_id, vendor_id, invoice_number, status, currency_code, subtotal_cents, tax_cents, total_cents, due_at, metadata ) VALUES ($1, $2, $3, $4, $5, $6, 'PENDING_REVIEW', 'USD', 15250, 700, 15950, $7, '{"seeded":true,"savingsCents":1250}'::jsonb) `, [ fixture.invoices.completed.id, fixture.tenant.id, fixture.orders.completed.id, fixture.business.id, fixture.vendor.id, fixture.invoices.completed.number, invoiceDueAt, ] ); await client.query( ` INSERT INTO recent_payments ( id, tenant_id, invoice_id, assignment_id, staff_id, status, amount_cents, process_date, metadata ) VALUES ($1, $2, $3, $4, $5, 'PENDING', 15950, NULL, '{"seeded":true}'::jsonb) `, [ fixture.recentPayments.completed.id, fixture.tenant.id, fixture.invoices.completed.id, fixture.assignments.completedAna.id, fixture.staff.ana.id, ] ); await client.query( ` INSERT INTO staff_favorites (id, tenant_id, business_id, staff_id, created_by_user_id, created_at) VALUES ($1, $2, $3, $4, $5, NOW()) `, [ fixture.favorites.ana.id, fixture.tenant.id, fixture.business.id, fixture.staff.ana.id, fixture.users.businessOwner.id, ] ); await client.query( ` INSERT INTO staff_reviews ( id, tenant_id, business_id, staff_id, assignment_id, reviewer_user_id, rating, review_text, tags, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, 5, 'Reliable, on time, and client friendly.', '["reliable","favorite"]'::jsonb, NOW(), NOW()) `, [ fixture.reviews.anaCompleted.id, fixture.tenant.id, fixture.business.id, fixture.staff.ana.id, fixture.assignments.completedAna.id, fixture.users.businessOwner.id, ] ); await client.query( ` INSERT INTO domain_events (tenant_id, aggregate_type, aggregate_id, sequence, event_type, actor_user_id, payload) VALUES ($1, 'order', $2, 1, 'ORDER_CREATED', $3, '{"seeded":true}'::jsonb), ($1, 'assignment', $4, 1, 'STAFF_ASSIGNED', $3, '{"seeded":true}'::jsonb) `, [ fixture.tenant.id, fixture.orders.completed.id, fixture.users.businessOwner.id, fixture.assignments.completedAna.id, ] ); await client.query('COMMIT'); // eslint-disable-next-line no-console console.log(JSON.stringify({ tenantId: fixture.tenant.id, businessId: fixture.business.id, vendorId: fixture.vendor.id, staffId: fixture.staff.ana.id, staffUserId: fixture.users.staffAna.id, workforceId: fixture.workforce.ana.id, openOrderId: fixture.orders.open.id, openShiftId: fixture.shifts.open.id, openShiftRoleId: fixture.shiftRoles.openBarista.id, openApplicationId: fixture.applications.openAna.id, completedOrderId: fixture.orders.completed.id, completedAssignmentId: fixture.assignments.completedAna.id, clockPointId: fixture.clockPoint.id, nfcTagUid: fixture.clockPoint.nfcTagUid, businessOwnerUid: fixture.users.businessOwner.id, }, null, 2)); } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); await pool.end(); } } main().catch((error) => { // eslint-disable-next-line no-console console.error(error); process.exit(1); });