Add dev seed data, batch status progress bar, pre-fill account from batch
Dev seed script creates 8 accounts, 8 members, 16 repair templates, 6 repair tickets in various statuses, and a school batch with 5 instruments. Run with bun run db:seed-dev. Batch detail page now has a status progress bar matching the ticket detail pattern. Add Repair from batch pre-fills the account and contact name. New repair form reads accountId and contactName from search params when linked from a batch.
This commit is contained in:
@@ -0,0 +1,79 @@
|
|||||||
|
import { Check, ClipboardList, Wrench, Package, Truck, Ban } from 'lucide-react'
|
||||||
|
|
||||||
|
const STEPS = [
|
||||||
|
{ key: 'intake', label: 'Intake', icon: ClipboardList },
|
||||||
|
{ key: 'in_progress', label: 'In Progress', icon: Wrench },
|
||||||
|
{ key: 'completed', label: 'Completed', icon: Package },
|
||||||
|
{ key: 'delivered', label: 'Delivered', icon: Truck },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
interface BatchStatusProgressProps {
|
||||||
|
currentStatus: string
|
||||||
|
onStatusClick?: (status: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BatchStatusProgress({ currentStatus, onStatusClick }: BatchStatusProgressProps) {
|
||||||
|
const isCancelled = currentStatus === 'cancelled'
|
||||||
|
const currentIdx = STEPS.findIndex((s) => s.key === currentStatus)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
{STEPS.map((step, idx) => {
|
||||||
|
const isCompleted = !isCancelled && currentIdx > idx
|
||||||
|
const isCurrent = !isCancelled && currentIdx === idx
|
||||||
|
const isFuture = isCancelled || currentIdx < idx
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={step.key} className="flex items-center flex-1 last:flex-none">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!onStatusClick || isCancelled}
|
||||||
|
onClick={() => onStatusClick?.(step.key)}
|
||||||
|
className={`relative flex flex-col items-center gap-1 group ${onStatusClick && !isCancelled ? 'cursor-pointer' : 'cursor-default'}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`flex items-center justify-center h-9 w-9 rounded-full border-2 transition-colors
|
||||||
|
${isCompleted ? 'bg-primary border-primary text-primary-foreground' : ''}
|
||||||
|
${isCurrent ? 'border-primary bg-primary/10 text-primary ring-2 ring-primary/30' : ''}
|
||||||
|
${isFuture ? 'border-muted-foreground/30 text-muted-foreground/40' : ''}
|
||||||
|
${isCancelled ? 'border-destructive/30 text-destructive/40' : ''}
|
||||||
|
${onStatusClick && !isCancelled ? 'group-hover:border-primary group-hover:text-primary' : ''}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{isCompleted ? <Check className="h-4 w-4" /> : <step.icon className="h-4 w-4" />}
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className={`text-[10px] font-medium text-center leading-tight max-w-[70px]
|
||||||
|
${isCompleted ? 'text-primary' : ''}
|
||||||
|
${isCurrent ? 'text-primary font-semibold' : ''}
|
||||||
|
${isFuture ? 'text-muted-foreground/50' : ''}
|
||||||
|
${isCancelled ? 'text-destructive/50' : ''}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{step.label}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{idx < STEPS.length - 1 && (
|
||||||
|
<div
|
||||||
|
className={`flex-1 h-0.5 mx-1 mt-[-18px]
|
||||||
|
${!isCancelled && currentIdx > idx ? 'bg-primary' : 'bg-muted-foreground/20'}
|
||||||
|
${isCancelled ? 'bg-destructive/20' : ''}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isCancelled && (
|
||||||
|
<div className="flex items-center gap-2 pl-4">
|
||||||
|
<Ban className="h-4 w-4 text-destructive" />
|
||||||
|
<span className="text-sm font-medium text-destructive">Cancelled</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import { Badge } from '@/components/ui/badge'
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
import { ArrowLeft, Check, X, Plus, FileText, Download } from 'lucide-react'
|
import { ArrowLeft, Check, X, Plus, FileText, Download } from 'lucide-react'
|
||||||
|
import { BatchStatusProgress } from '@/components/repairs/batch-status-progress'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { useAuthStore } from '@/stores/auth.store'
|
import { useAuthStore } from '@/stores/auth.store'
|
||||||
import jsPDF from 'jspdf'
|
import jsPDF from 'jspdf'
|
||||||
@@ -99,8 +100,8 @@ function RepairBatchDetailPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleAddRepair() {
|
function handleAddRepair() {
|
||||||
// Navigate to new repair with batch pre-linked
|
// Navigate to new repair with batch and account pre-linked
|
||||||
navigate({ to: '/repairs/new', search: { batchId, batchName: batch!.batchNumber ?? '' } as any })
|
navigate({ to: '/repairs/new', search: { batchId, batchName: batch!.batchNumber ?? '', accountId: batch!.accountId, contactName: batch!.contactName ?? '' } as any })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateBatchPdf() {
|
async function generateBatchPdf() {
|
||||||
@@ -248,6 +249,16 @@ function RepairBatchDetailPage() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Status Progress Bar */}
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-6 pb-4">
|
||||||
|
<BatchStatusProgress
|
||||||
|
currentStatus={batch.status}
|
||||||
|
onStatusClick={hasPermission('repairs.edit') ? (status) => statusMutation.mutate(status) : undefined}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
{hasPermission('repairs.admin') && batch.approvalStatus === 'pending' && (
|
{hasPermission('repairs.admin') && batch.approvalStatus === 'pending' && (
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export const Route = createFileRoute('/_authenticated/repairs/new')({
|
|||||||
validateSearch: (search: Record<string, unknown>) => ({
|
validateSearch: (search: Record<string, unknown>) => ({
|
||||||
batchId: (search.batchId as string) || undefined,
|
batchId: (search.batchId as string) || undefined,
|
||||||
batchName: (search.batchName as string) || undefined,
|
batchName: (search.batchName as string) || undefined,
|
||||||
|
accountId: (search.accountId as string) || undefined,
|
||||||
|
contactName: (search.contactName as string) || undefined,
|
||||||
}),
|
}),
|
||||||
component: NewRepairPage,
|
component: NewRepairPage,
|
||||||
})
|
})
|
||||||
@@ -42,6 +44,8 @@ function NewRepairPage() {
|
|||||||
const search = Route.useSearch()
|
const search = Route.useSearch()
|
||||||
const linkedBatchId = search.batchId
|
const linkedBatchId = search.batchId
|
||||||
const linkedBatchName = search.batchName
|
const linkedBatchName = search.batchName
|
||||||
|
const linkedAccountId = search.accountId
|
||||||
|
const linkedContactName = search.contactName
|
||||||
|
|
||||||
// Account search
|
// Account search
|
||||||
const [accountSearch, setAccountSearch] = useState('')
|
const [accountSearch, setAccountSearch] = useState('')
|
||||||
@@ -82,14 +86,14 @@ function NewRepairPage() {
|
|||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(RepairTicketCreateSchema),
|
resolver: zodResolver(RepairTicketCreateSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
customerName: '',
|
customerName: linkedContactName ?? '',
|
||||||
customerPhone: '',
|
customerPhone: '',
|
||||||
instrumentDescription: '',
|
instrumentDescription: '',
|
||||||
serialNumber: '',
|
serialNumber: '',
|
||||||
problemDescription: '',
|
problemDescription: '',
|
||||||
conditionIn: undefined,
|
conditionIn: undefined,
|
||||||
estimatedCost: undefined,
|
estimatedCost: undefined,
|
||||||
accountId: undefined,
|
accountId: linkedAccountId ?? undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"db:generate": "bunx drizzle-kit generate",
|
"db:generate": "bunx drizzle-kit generate",
|
||||||
"db:migrate": "bunx drizzle-kit migrate",
|
"db:migrate": "bunx drizzle-kit migrate",
|
||||||
|
"db:seed-dev": "bun run src/db/seeds/dev-seed.ts",
|
||||||
"db:seed": "bun run src/db/seed.ts"
|
"db:seed": "bun run src/db/seed.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
182
packages/backend/src/db/seeds/dev-seed.ts
Normal file
182
packages/backend/src/db/seeds/dev-seed.ts
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* Dev seed script — populates the dev database with sample data for manual testing.
|
||||||
|
* Run: bun run src/db/seeds/dev-seed.ts
|
||||||
|
*
|
||||||
|
* Prerequisites: backend must have been started at least once (migrations + RBAC seeds applied).
|
||||||
|
*/
|
||||||
|
import postgres from 'postgres'
|
||||||
|
|
||||||
|
const DB_URL = process.env.DATABASE_URL ?? 'postgresql://forte:forte@localhost:5432/forte'
|
||||||
|
const COMPANY_ID = 'a0000000-0000-0000-0000-000000000001'
|
||||||
|
|
||||||
|
const sql = postgres(DB_URL)
|
||||||
|
|
||||||
|
async function seed() {
|
||||||
|
console.log('Seeding dev database...')
|
||||||
|
|
||||||
|
// Create company and location if they don't exist
|
||||||
|
const [company] = await sql`SELECT id FROM company WHERE id = ${COMPANY_ID}`
|
||||||
|
if (!company) {
|
||||||
|
await sql`INSERT INTO company (id, name, timezone) VALUES (${COMPANY_ID}, 'Forte Music Store', 'America/Chicago')`
|
||||||
|
await sql`INSERT INTO location (id, company_id, name) VALUES ('a0000000-0000-0000-0000-000000000002', ${COMPANY_ID}, 'Main Store')`
|
||||||
|
console.log(' Created company and location')
|
||||||
|
|
||||||
|
// Seed RBAC
|
||||||
|
const { SYSTEM_PERMISSIONS, DEFAULT_ROLES } = await import('../seeds/rbac.js')
|
||||||
|
for (const p of SYSTEM_PERMISSIONS) {
|
||||||
|
await sql`INSERT INTO permission (slug, domain, action, description) VALUES (${p.slug}, ${p.domain}, ${p.action}, ${p.description}) ON CONFLICT (slug) DO NOTHING`
|
||||||
|
}
|
||||||
|
const permRows = await sql`SELECT id, slug FROM permission`
|
||||||
|
const permMap = new Map(permRows.map((r: any) => [r.slug, r.id]))
|
||||||
|
for (const roleDef of DEFAULT_ROLES) {
|
||||||
|
const [role] = await sql`INSERT INTO role (company_id, name, slug, description, is_system) VALUES (${COMPANY_ID}, ${roleDef.name}, ${roleDef.slug}, ${roleDef.description}, true) RETURNING id`
|
||||||
|
for (const permSlug of roleDef.permissions) {
|
||||||
|
const permId = permMap.get(permSlug)
|
||||||
|
if (permId) await sql`INSERT INTO role_permission (role_id, permission_id) VALUES (${role.id}, ${permId}) ON CONFLICT DO NOTHING`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(' Seeded RBAC permissions and roles')
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Admin user (if not exists) ---
|
||||||
|
const [adminUser] = await sql`SELECT id FROM "user" WHERE email = 'admin@forte.dev'`
|
||||||
|
if (!adminUser) {
|
||||||
|
const bcrypt = await import('bcrypt')
|
||||||
|
const hashedPw = await (bcrypt.default || bcrypt).hash('admin1234', 10)
|
||||||
|
const [user] = await sql`INSERT INTO "user" (company_id, email, password_hash, first_name, last_name, role) VALUES (${COMPANY_ID}, 'admin@forte.dev', ${hashedPw}, 'Admin', 'User', 'admin') RETURNING id`
|
||||||
|
const [adminRole] = await sql`SELECT id FROM role WHERE company_id = ${COMPANY_ID} AND slug = 'admin' LIMIT 1`
|
||||||
|
if (adminRole) {
|
||||||
|
await sql`INSERT INTO user_role_assignment (user_id, role_id) VALUES (${user.id}, ${adminRole.id}) ON CONFLICT DO NOTHING`
|
||||||
|
}
|
||||||
|
console.log(' Created admin user: admin@forte.dev / admin1234')
|
||||||
|
} else {
|
||||||
|
// Make sure admin role is assigned
|
||||||
|
const [adminRole] = await sql`SELECT id FROM role WHERE company_id = ${COMPANY_ID} AND slug = 'admin' LIMIT 1`
|
||||||
|
if (adminRole) {
|
||||||
|
await sql`INSERT INTO user_role_assignment (user_id, role_id) VALUES (${adminUser.id}, ${adminRole.id}) ON CONFLICT DO NOTHING`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Accounts ---
|
||||||
|
const accounts = [
|
||||||
|
{ name: 'Smith Family', email: 'smith@example.com', phone: '555-0101' },
|
||||||
|
{ name: 'Johnson Family', email: 'johnson@example.com', phone: '555-0102' },
|
||||||
|
{ name: 'Lincoln High School', email: 'band@lincoln.edu', phone: '555-0200' },
|
||||||
|
{ name: 'Garcia Music Studio', email: 'garcia@studio.com', phone: '555-0103' },
|
||||||
|
{ name: 'Mike Thompson', email: 'mike.t@email.com', phone: '555-0104' },
|
||||||
|
{ name: 'Emily Chen', email: 'emily.chen@email.com', phone: '555-0105' },
|
||||||
|
{ name: 'Westside Church', email: 'music@westsidechurch.org', phone: '555-0300' },
|
||||||
|
{ name: 'Oak Elementary', email: 'office@oakelementary.edu', phone: '555-0201' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const acctIds: Record<string, string> = {}
|
||||||
|
for (const a of accounts) {
|
||||||
|
const existing = await sql`SELECT id FROM account WHERE company_id = ${COMPANY_ID} AND name = ${a.name}`
|
||||||
|
if (existing.length > 0) {
|
||||||
|
acctIds[a.name] = existing[0].id
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const num = String(Math.floor(100000 + Math.random() * 900000))
|
||||||
|
const [row] = await sql`INSERT INTO account (company_id, name, email, phone, account_number, billing_mode) VALUES (${COMPANY_ID}, ${a.name}, ${a.email}, ${a.phone}, ${num}, 'consolidated') RETURNING id`
|
||||||
|
acctIds[a.name] = row.id
|
||||||
|
console.log(` Account: ${a.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Members ---
|
||||||
|
const members = [
|
||||||
|
{ accountName: 'Smith Family', firstName: 'David', lastName: 'Smith', email: 'david@example.com' },
|
||||||
|
{ accountName: 'Smith Family', firstName: 'Sarah', lastName: 'Smith', email: 'sarah@example.com' },
|
||||||
|
{ accountName: 'Smith Family', firstName: 'Tommy', lastName: 'Smith', isMinor: true },
|
||||||
|
{ accountName: 'Johnson Family', firstName: 'Lisa', lastName: 'Johnson', email: 'lisa.j@example.com' },
|
||||||
|
{ accountName: 'Johnson Family', firstName: 'Jake', lastName: 'Johnson', isMinor: true },
|
||||||
|
{ accountName: 'Garcia Music Studio', firstName: 'Carlos', lastName: 'Garcia', email: 'carlos@studio.com' },
|
||||||
|
{ accountName: 'Mike Thompson', firstName: 'Mike', lastName: 'Thompson', email: 'mike.t@email.com' },
|
||||||
|
{ accountName: 'Emily Chen', firstName: 'Emily', lastName: 'Chen', email: 'emily.chen@email.com' },
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const m of members) {
|
||||||
|
const acctId = acctIds[m.accountName]
|
||||||
|
const existing = await sql`SELECT id FROM member WHERE company_id = ${COMPANY_ID} AND first_name = ${m.firstName} AND last_name = ${m.lastName}`
|
||||||
|
if (existing.length > 0) continue
|
||||||
|
const num = String(Math.floor(100000 + Math.random() * 900000))
|
||||||
|
await sql`INSERT INTO member (company_id, account_id, first_name, last_name, email, member_number, is_minor) VALUES (${COMPANY_ID}, ${acctId}, ${m.firstName}, ${m.lastName}, ${m.email ?? null}, ${num}, ${m.isMinor ?? false})`
|
||||||
|
console.log(` Member: ${m.firstName} ${m.lastName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Repair Service Templates ---
|
||||||
|
const templates = [
|
||||||
|
{ name: 'Bow Rehair', instrumentType: 'Violin', size: '4/4', itemType: 'flat_rate', price: '65.00', cost: '15.00' },
|
||||||
|
{ name: 'Bow Rehair', instrumentType: 'Violin', size: '3/4', itemType: 'flat_rate', price: '55.00', cost: '12.00' },
|
||||||
|
{ name: 'Bow Rehair', instrumentType: 'Cello', size: null, itemType: 'flat_rate', price: '80.00', cost: '20.00' },
|
||||||
|
{ name: 'Bow Rehair', instrumentType: 'Bass', size: null, itemType: 'flat_rate', price: '90.00', cost: '25.00' },
|
||||||
|
{ name: 'String Change', instrumentType: 'Guitar', size: 'Acoustic', itemType: 'flat_rate', price: '25.00', cost: '8.00' },
|
||||||
|
{ name: 'String Change', instrumentType: 'Guitar', size: 'Electric', itemType: 'flat_rate', price: '25.00', cost: '7.00' },
|
||||||
|
{ name: 'String Change', instrumentType: 'Violin', size: '4/4', itemType: 'flat_rate', price: '35.00', cost: '12.00' },
|
||||||
|
{ name: 'Valve Overhaul', instrumentType: 'Trumpet', size: null, itemType: 'labor', price: '85.00', cost: null },
|
||||||
|
{ name: 'Pad Replacement', instrumentType: 'Clarinet', size: null, itemType: 'flat_rate', price: '120.00', cost: '30.00' },
|
||||||
|
{ name: 'Pad Replacement', instrumentType: 'Flute', size: null, itemType: 'flat_rate', price: '110.00', cost: '25.00' },
|
||||||
|
{ name: 'Cork Replacement', instrumentType: 'Clarinet', size: null, itemType: 'flat_rate', price: '45.00', cost: '5.00' },
|
||||||
|
{ name: 'Slide Repair', instrumentType: 'Trombone', size: null, itemType: 'labor', price: '75.00', cost: null },
|
||||||
|
{ name: 'Bridge Setup', instrumentType: 'Violin', size: '4/4', itemType: 'flat_rate', price: '40.00', cost: '10.00' },
|
||||||
|
{ name: 'Guitar Setup', instrumentType: 'Guitar', size: null, itemType: 'flat_rate', price: '65.00', cost: '5.00' },
|
||||||
|
{ name: 'Dent Removal', instrumentType: 'Brass', size: null, itemType: 'labor', price: '50.00', cost: null },
|
||||||
|
{ name: 'General Cleaning', instrumentType: null, size: null, itemType: 'flat_rate', price: '30.00', cost: '5.00' },
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const t of templates) {
|
||||||
|
const existing = await sql`SELECT id FROM repair_service_template WHERE company_id = ${COMPANY_ID} AND name = ${t.name} AND COALESCE(instrument_type, '') = ${t.instrumentType ?? ''} AND COALESCE(size, '') = ${t.size ?? ''}`
|
||||||
|
if (existing.length > 0) continue
|
||||||
|
await sql`INSERT INTO repair_service_template (company_id, name, instrument_type, size, item_type, default_price, default_cost, is_active) VALUES (${COMPANY_ID}, ${t.name}, ${t.instrumentType}, ${t.size}, ${t.itemType}, ${t.price}, ${t.cost}, true)`
|
||||||
|
console.log(` Template: ${t.name} ${t.instrumentType ?? ''} ${t.size ?? ''}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Repair Tickets ---
|
||||||
|
const tickets = [
|
||||||
|
{ customer: 'Mike Thompson', instrument: 'Fender Stratocaster', serial: 'US22-045891', problem: 'Fret buzz on 3rd and 5th fret, needs setup', condition: 'good', status: 'in_progress', estimate: '65.00' },
|
||||||
|
{ customer: 'Emily Chen', instrument: 'Yamaha YTR-2330 Trumpet', serial: 'YTR-78432', problem: 'Stuck 2nd valve, sluggish action on all valves', condition: 'fair', status: 'pending_approval', estimate: '85.00' },
|
||||||
|
{ customer: 'David Smith', instrument: 'Stradivarius Copy Violin', serial: null, problem: 'Bow needs rehair, bridge slightly warped', condition: 'fair', status: 'ready', estimate: '105.00' },
|
||||||
|
{ customer: 'Carlos Garcia', instrument: 'Martin D-28 Acoustic Guitar', serial: 'M2284563', problem: 'Broken tuning peg, needs replacement', condition: 'good', status: 'new', estimate: null },
|
||||||
|
{ customer: 'Lisa Johnson', instrument: 'Yamaha YCL-255 Clarinet', serial: null, problem: 'Several pads worn, keys sticking', condition: 'poor', status: 'diagnosing', estimate: null },
|
||||||
|
{ customer: 'Walk-In Customer', instrument: 'Unknown Flute', serial: null, problem: 'Customer says it squeaks on high notes', condition: 'fair', status: 'intake', estimate: null },
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const t of tickets) {
|
||||||
|
const existing = await sql`SELECT id FROM repair_ticket WHERE company_id = ${COMPANY_ID} AND customer_name = ${t.customer} AND problem_description = ${t.problem}`
|
||||||
|
if (existing.length > 0) continue
|
||||||
|
const num = String(Math.floor(100000 + Math.random() * 900000))
|
||||||
|
const acctId = acctIds[t.customer] ?? null
|
||||||
|
await sql`INSERT INTO repair_ticket (company_id, ticket_number, customer_name, account_id, instrument_description, serial_number, problem_description, condition_in, status, estimated_cost) VALUES (${COMPANY_ID}, ${num}, ${t.customer}, ${acctId}, ${t.instrument}, ${t.serial}, ${t.problem}, ${t.condition}, ${t.status}, ${t.estimate})`
|
||||||
|
console.log(` Ticket: ${t.customer} — ${t.instrument} [${t.status}]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Repair Batch ---
|
||||||
|
const batchExists = await sql`SELECT id FROM repair_batch WHERE company_id = ${COMPANY_ID} AND contact_name = 'Mr. Williams'`
|
||||||
|
if (batchExists.length === 0) {
|
||||||
|
const batchNum = String(Math.floor(100000 + Math.random() * 900000))
|
||||||
|
const schoolId = acctIds['Lincoln High School']
|
||||||
|
const [batch] = await sql`INSERT INTO repair_batch (company_id, batch_number, account_id, contact_name, contact_phone, contact_email, instrument_count, notes, status) VALUES (${COMPANY_ID}, ${batchNum}, ${schoolId}, 'Mr. Williams', '555-0210', 'williams@lincoln.edu', 5, 'Annual band instrument checkup — 5 instruments', 'intake') RETURNING id`
|
||||||
|
|
||||||
|
const batchTickets = [
|
||||||
|
{ instrument: 'Student Flute', problem: 'Pads worn, needs replacement check', condition: 'fair' },
|
||||||
|
{ instrument: 'Student Clarinet #1', problem: 'Keys sticking, cork dried out', condition: 'fair' },
|
||||||
|
{ instrument: 'Student Clarinet #2', problem: 'Barrel crack, needs assessment', condition: 'poor' },
|
||||||
|
{ instrument: 'Student Trumpet', problem: 'Valve oil needed, general checkup', condition: 'good' },
|
||||||
|
{ instrument: 'Student Trombone', problem: 'Slide dent, sluggish movement', condition: 'fair' },
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const bt of batchTickets) {
|
||||||
|
const num = String(Math.floor(100000 + Math.random() * 900000))
|
||||||
|
await sql`INSERT INTO repair_ticket (company_id, ticket_number, customer_name, account_id, repair_batch_id, instrument_description, problem_description, condition_in, status) VALUES (${COMPANY_ID}, ${num}, 'Lincoln High School', ${schoolId}, ${batch.id}, ${bt.instrument}, ${bt.problem}, ${bt.condition}, 'new')`
|
||||||
|
console.log(` Batch ticket: ${bt.instrument}`)
|
||||||
|
}
|
||||||
|
console.log(` Batch: Lincoln High School — 5 instruments`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\nDev seed complete!')
|
||||||
|
await sql.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
seed().catch((err) => {
|
||||||
|
console.error('Seed failed:', err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user