Files
Krow-workspace/backend/command-api/scripts/seed-v2-demo-data.mjs

996 lines
36 KiB
JavaScript

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 assignedStartsAt = hoursFromNow(0.1);
const assignedEndsAt = hoursFromNow(8.1);
const availableStartsAt = hoursFromNow(30);
const availableEndsAt = hoursFromNow(38);
const cancelledStartsAt = hoursFromNow(20);
const cancelledEndsAt = hoursFromNow(28);
const noShowStartsAt = hoursFromNow(-18);
const noShowEndsAt = hoursFromNow(-10);
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 staff_benefit_history (
id, tenant_id, staff_id, benefit_id, benefit_type, title, status,
effective_at, ended_at, tracked_hours, target_hours, notes, metadata
)
VALUES
(
$1, $3, $4, $5, 'COMMUTER', $6, 'PENDING',
NOW() - INTERVAL '45 days', NOW() - INTERVAL '10 days', 18, 40,
'Hours were below threshold for payout window.',
'{"source":"seed-v2-demo","period":"previous"}'::jsonb
),
(
$2, $3, $4, $5, 'COMMUTER', $6, 'ACTIVE',
NOW() - INTERVAL '9 days', NULL, 32, 40,
'Current active commuter stipend tracking.',
'{"source":"seed-v2-demo","period":"current"}'::jsonb
)
`,
[
fixture.benefitHistory.commuterPending.id,
fixture.benefitHistory.commuterActive.id,
fixture.tenant.id,
fixture.staff.ana.id,
fixture.benefits.commuter.id,
fixture.benefits.commuter.title,
]
);
await client.query(
`
INSERT INTO emergency_contacts (
id, tenant_id, staff_id, full_name, phone, relationship_type, is_primary, metadata
)
VALUES ($1, $2, $3, 'Maria Barista', '+15550007777', 'SIBLING', TRUE, '{"seeded":true}'::jsonb)
`,
[fixture.emergencyContacts.primary.id, fixture.tenant.id, fixture.staff.ana.id]
);
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, default_clock_in_mode, allow_clock_in_override, status, metadata
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, 'ACTIVE', $13::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,
fixture.clockPoint.defaultClockInMode,
fixture.clockPoint.allowClockInOverride,
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 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, $2, $3, $4, $5, $6, 'Active order used to populate assigned, available, cancelled, and no-show shift states',
'ACTIVE', 'RESTAURANT', $7, $8, 'Google Cafe', $9, $10, $11, 'Mixed state scenario order', $12,
'{"slice":"active","orderType":"ONE_TIME"}'::jsonb
)
`,
[
fixture.orders.active.id,
fixture.tenant.id,
fixture.business.id,
fixture.vendor.id,
fixture.orders.active.number,
fixture.orders.active.title,
assignedStartsAt,
availableEndsAt,
fixture.clockPoint.address,
fixture.clockPoint.latitude,
fixture.clockPoint.longitude,
fixture.users.operationsManager.id,
]
);
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, clock_in_mode, allow_clock_in_override,
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, NULL, NULL, 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, NULL, NULL, 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 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, clock_in_mode, allow_clock_in_override,
required_workers, assigned_workers, notes, metadata
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, 'OPEN', $9, $10, 'America/Los_Angeles', 'Google Cafe', $11, $12, $13, $14, NULL, NULL, 1, 0, 'Available shift for staff marketplace', '{"slice":"available"}'::jsonb),
($15, $2, $3, $4, $5, $6, $16, $17, 'ASSIGNED', $18, $19, 'America/Los_Angeles', 'Google Cafe', $11, $12, $13, $14, $30, $31, 1, 1, 'Assigned shift waiting for staff confirmation', '{"slice":"assigned"}'::jsonb),
($20, $2, $3, $4, $5, $6, $21, $22, 'CANCELLED', $23, $24, 'America/Los_Angeles', 'Google Cafe', $11, $12, $13, $14, NULL, NULL, 1, 0, 'Cancelled shift history sample', '{"slice":"cancelled"}'::jsonb),
($25, $2, $3, $4, $5, $6, $26, $27, 'COMPLETED', $28, $29, 'America/Los_Angeles', 'Google Cafe', $11, $12, $13, $14, 'GEO_REQUIRED', TRUE, 1, 0, 'No-show historical sample', '{"slice":"no_show"}'::jsonb)
`,
[
fixture.shifts.available.id,
fixture.tenant.id,
fixture.orders.active.id,
fixture.business.id,
fixture.vendor.id,
fixture.clockPoint.id,
fixture.shifts.available.code,
fixture.shifts.available.title,
availableStartsAt,
availableEndsAt,
fixture.clockPoint.address,
fixture.clockPoint.latitude,
fixture.clockPoint.longitude,
fixture.clockPoint.geofenceRadiusMeters,
fixture.shifts.assigned.id,
fixture.shifts.assigned.code,
fixture.shifts.assigned.title,
assignedStartsAt,
assignedEndsAt,
fixture.shifts.cancelled.id,
fixture.shifts.cancelled.code,
fixture.shifts.cancelled.title,
cancelledStartsAt,
cancelledEndsAt,
fixture.shifts.noShow.id,
fixture.shifts.noShow.code,
fixture.shifts.noShow.title,
noShowStartsAt,
noShowEndsAt,
fixture.shifts.assigned.clockInMode,
fixture.shifts.assigned.allowClockInOverride,
]
);
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 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, $7, $8, $9, 1, 0, 2200, 3500, '{"slice":"available"}'::jsonb),
($3, $4, $7, $8, $9, 1, 1, 2300, 3600, '{"slice":"assigned"}'::jsonb),
($5, $6, $7, $8, $9, 1, 0, 2200, 3500, '{"slice":"cancelled"}'::jsonb),
($10, $11, $7, $8, $9, 1, 0, 2200, 3500, '{"slice":"no_show"}'::jsonb)
`,
[
fixture.shiftRoles.availableBarista.id,
fixture.shifts.available.id,
fixture.shiftRoles.assignedBarista.id,
fixture.shifts.assigned.id,
fixture.shiftRoles.cancelledBarista.id,
fixture.shifts.cancelled.id,
fixture.roles.barista.id,
fixture.roles.barista.code,
fixture.roles.barista.name,
fixture.shiftRoles.noShowBarista.id,
fixture.shifts.noShow.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 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, 'ASSIGNED', NOW(), NULL, NULL, NULL, '{"slice":"assigned"}'::jsonb),
($9, $2, $3, $4, $10, $11, $7, $8, 'CANCELLED', NOW(), NULL, NULL, NULL, '{"slice":"cancelled","cancellationReason":"Client cancelled"}'::jsonb),
($12, $2, $3, $4, $13, $14, $7, $8, 'NO_SHOW', $15, NULL, NULL, NULL, '{"slice":"no_show"}'::jsonb)
`,
[
fixture.assignments.assignedAna.id,
fixture.tenant.id,
fixture.business.id,
fixture.vendor.id,
fixture.shifts.assigned.id,
fixture.shiftRoles.assignedBarista.id,
fixture.workforce.ana.id,
fixture.staff.ana.id,
fixture.assignments.cancelledAna.id,
fixture.shifts.cancelled.id,
fixture.shiftRoles.cancelledBarista.id,
fixture.assignments.noShowAna.id,
fixture.shifts.noShow.id,
fixture.shiftRoles.noShowBarista.id,
noShowStartsAt,
]
);
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, 'GOVERNMENT_ID', $3, $10, '{"seeded":true,"description":"State ID or passport","required":true}'::jsonb),
($4, $2, 'CERTIFICATION', $5, $10, '{"seeded":true}'::jsonb),
($6, $2, 'ATTIRE', $7, $10, '{"seeded":true,"description":"Upload a photo of your black shirt","required":true}'::jsonb),
($8, $2, 'TAX_FORM', $9, $10, '{"seeded":true}'::jsonb),
($11, $2, 'TAX_FORM', $12, $10, '{"seeded":true}'::jsonb)
`,
[
fixture.documents.governmentId.id,
fixture.tenant.id,
fixture.documents.governmentId.name,
fixture.documents.foodSafety.id,
fixture.documents.foodSafety.name,
fixture.documents.attireBlackShirt.id,
fixture.documents.attireBlackShirt.name,
fixture.documents.taxFormI9.id,
fixture.documents.taxFormI9.name,
fixture.roles.barista.code,
fixture.documents.taxFormW4.id,
fixture.documents.taxFormW4.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, 'PENDING', $6, '{"seeded":true,"verificationStatus":"PENDING_REVIEW"}'::jsonb),
($7, $2, $3, $8, $9, 'VERIFIED', $10, '{"seeded":true,"verificationStatus":"APPROVED"}'::jsonb),
($11, $2, $3, $12, $13, 'VERIFIED', NULL, '{"seeded":true,"verificationStatus":"APPROVED"}'::jsonb),
($14, $2, $3, $15, $16, 'VERIFIED', NULL, '{"seeded":true,"formStatus":"SUBMITTED","fields":{"ssnLast4":"1234","filingStatus":"single"}}'::jsonb),
($17, $2, $3, $18, $19, 'PENDING', NULL, '{"seeded":true,"formStatus":"DRAFT","fields":{"section1Complete":true}}'::jsonb)
`,
[
fixture.staffDocuments.governmentId.id,
fixture.tenant.id,
fixture.staff.ana.id,
fixture.documents.governmentId.id,
`gs://krow-workforce-dev-v2-private/uploads/${fixture.staff.ana.id}/government-id-front.jpg`,
hoursFromNow(24 * 365),
fixture.staffDocuments.foodSafety.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.taxFormW4.id,
fixture.documents.taxFormW4.id,
`gs://krow-workforce-dev-v2-private/uploads/${fixture.staff.ana.id}/w4-form.pdf`,
fixture.staffDocuments.taxFormI9.id,
fixture.documents.taxFormI9.id,
`gs://krow-workforce-dev-v2-private/uploads/${fixture.staff.ana.id}/i9-form.pdf`,
]
);
await client.query(
`
INSERT INTO certificates (
id, tenant_id, staff_id, certificate_type, certificate_number, issued_at, expires_at, status, file_uri, metadata
)
VALUES ($1, $2, $3, 'FOOD_SAFETY', 'FH-ANA-2026', $4, $5, 'VERIFIED', $6, $7::jsonb)
`,
[
fixture.certificates.foodSafety.id,
fixture.tenant.id,
fixture.staff.ana.id,
hoursFromNow(-24 * 30),
hoursFromNow(24 * 180),
`gs://krow-workforce-dev-v2-private/uploads/${fixture.staff.ana.id}/food-safety-certificate.pdf`,
JSON.stringify({
seeded: true,
name: 'Food Safety Certificate',
issuer: 'ServSafe',
verificationStatus: 'APPROVED',
}),
]
);
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(
`
INSERT INTO location_stream_batches (
id, tenant_id, business_id, vendor_id, shift_id, assignment_id, staff_id, actor_user_id,
source_type, device_id, object_uri, point_count, out_of_geofence_count, missing_coordinate_count,
max_distance_to_clock_point_meters, started_at, ended_at, metadata
)
VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, 'GEO', 'seed-device',
$9, 4, 2, 0, 910, $10, $11, '{"seeded":true,"source":"seed-v2-demo"}'::jsonb
)
`,
[
fixture.locationStreamBatches.noShowSample.id,
fixture.tenant.id,
fixture.business.id,
fixture.vendor.id,
fixture.shifts.noShow.id,
fixture.assignments.noShowAna.id,
fixture.staff.ana.id,
fixture.users.staffAna.id,
`gs://krow-workforce-dev-v2-private/location-streams/${fixture.tenant.id}/${fixture.staff.ana.id}/${fixture.assignments.noShowAna.id}/${fixture.locationStreamBatches.noShowSample.id}.json`,
hoursFromNow(-18.25),
hoursFromNow(-17.75),
]
);
await client.query(
`
INSERT INTO geofence_incidents (
id, tenant_id, business_id, vendor_id, shift_id, assignment_id, staff_id, actor_user_id, location_stream_batch_id,
incident_type, severity, status, effective_clock_in_mode, source_type, device_id,
latitude, longitude, accuracy_meters, distance_to_clock_point_meters, within_geofence,
override_reason, message, occurred_at, metadata
)
VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9,
'OUTSIDE_GEOFENCE', 'CRITICAL', 'OPEN', 'GEO_REQUIRED', 'GEO', 'seed-device',
$10, $11, 12, 910, FALSE, NULL, 'Worker drifted outside hub geofence during active monitoring',
$12, '{"seeded":true,"outOfGeofenceCount":2}'::jsonb
)
`,
[
fixture.geofenceIncidents.noShowOutsideGeofence.id,
fixture.tenant.id,
fixture.business.id,
fixture.vendor.id,
fixture.shifts.noShow.id,
fixture.assignments.noShowAna.id,
fixture.staff.ana.id,
fixture.users.staffAna.id,
fixture.locationStreamBatches.noShowSample.id,
fixture.clockPoint.latitude + 0.0065,
fixture.clockPoint.longitude + 0.0065,
hoursFromNow(-17.9),
]
);
await client.query(
`
INSERT INTO notification_outbox (
id, tenant_id, business_id, shift_id, assignment_id, related_incident_id, audience_type,
recipient_user_id, recipient_business_membership_id, channel, notification_type, priority, dedupe_key,
subject, body, payload, status, scheduled_at, created_at, updated_at
)
SELECT
$1, $2, $3, $4, $5, $6, 'USER',
bm.user_id, bm.id, 'PUSH', 'GEOFENCE_BREACH_ALERT', 'CRITICAL', $7,
'Worker left the workplace geofence',
'Seeded alert for coverage incident review',
jsonb_build_object('seeded', TRUE, 'batchId', $8::text),
'PENDING', NOW(), NOW(), NOW()
FROM business_memberships bm
WHERE bm.tenant_id = $2
AND bm.business_id = $3
AND bm.user_id = $9
`,
[
fixture.notificationOutbox.noShowManagerAlert.id,
fixture.tenant.id,
fixture.business.id,
fixture.shifts.noShow.id,
fixture.assignments.noShowAna.id,
fixture.geofenceIncidents.noShowOutsideGeofence.id,
`seed-geofence-breach:${fixture.geofenceIncidents.noShowOutsideGeofence.id}:${fixture.users.operationsManager.id}`,
fixture.locationStreamBatches.noShowSample.id,
fixture.users.operationsManager.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);
});