Files
lunarfront-app/packages/backend/src/db/seeds/music-store-seed.ts
ryan 1673e18fe8
Some checks failed
CI / ci (pull_request) Failing after 20s
CI / e2e (pull_request) Has been skipped
fix: require open drawer to complete transactions, fix product price field
- Backend enforces open drawer at location before completing any transaction
- Frontend disables payment buttons when drawer is closed with warning message
- Fix product price field name (price, not sellingPrice) in POS API types
- Fix seed UUIDs to use valid UUID v4 format (version nibble must be 1-8)
- Fix Vite allowedHosts for dev.lunarfront.tech access
- Add e2e test for drawer enforcement (39 POS tests now pass)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 19:54:07 +00:00

1410 lines
89 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Music store seed — populates the dev database with music-store-specific data.
* Run: bun run src/db/seeds/music-store-seed.ts
*
* Prerequisites: dev-seed must have been run first (company, accounts, RBAC already exist).
* This adds music-specific repair templates, tickets, and batch data.
*/
import postgres from 'postgres'
const DB_URL = process.env.DATABASE_URL ?? 'postgresql://lunarfront:lunarfront@localhost:5432/lunarfront'
const COMPANY_ID = 'a0000000-0000-4000-8000-000000000001'
const sql = postgres(DB_URL)
async function seed() {
console.log('Seeding music store data...')
// Verify company exists (dev-seed must run first)
const [company] = await sql`SELECT id FROM company WHERE id = ${COMPANY_ID}`
if (!company) {
console.error('Company not found — run dev-seed first: bun run db:seed-dev')
process.exit(1)
}
// Update company name to music store
await sql`UPDATE company SET name = 'Harmony Music Shop' WHERE id = ${COMPANY_ID}`
console.log(' Updated company name: Harmony Music Shop')
// --- Music Repair Service Templates ---
const templates = [
// Strings — Violin
{ name: 'Bow Rehair', itemCategory: 'Violin', size: '4/4', itemType: 'flat_rate', price: '65.00', cost: '15.00' },
{ name: 'Bow Rehair', itemCategory: 'Violin', size: '3/4', itemType: 'flat_rate', price: '55.00', cost: '12.00' },
{ name: 'Bow Rehair', itemCategory: 'Violin', size: '1/2', itemType: 'flat_rate', price: '50.00', cost: '10.00' },
{ name: 'Bridge Setup', itemCategory: 'Violin', size: '4/4', itemType: 'flat_rate', price: '40.00', cost: '10.00' },
{ name: 'Bridge Replacement', itemCategory: 'Violin', size: '4/4', itemType: 'flat_rate', price: '75.00', cost: '25.00' },
{ name: 'String Change', itemCategory: 'Violin', size: '4/4', itemType: 'flat_rate', price: '35.00', cost: '12.00' },
{ name: 'Peg Fitting', itemCategory: 'Violin', size: null, itemType: 'labor', price: '30.00', cost: null },
{ name: 'Soundpost Adjustment', itemCategory: 'Violin', size: null, itemType: 'labor', price: '25.00', cost: null },
{ name: 'Seam Repair', itemCategory: 'Violin', size: null, itemType: 'labor', price: '45.00', cost: null },
// Strings — Viola
{ name: 'Bow Rehair', itemCategory: 'Viola', size: null, itemType: 'flat_rate', price: '65.00', cost: '15.00' },
{ name: 'Bridge Setup', itemCategory: 'Viola', size: null, itemType: 'flat_rate', price: '45.00', cost: '12.00' },
{ name: 'String Change', itemCategory: 'Viola', size: null, itemType: 'flat_rate', price: '40.00', cost: '15.00' },
// Strings — Cello
{ name: 'Bow Rehair', itemCategory: 'Cello', size: null, itemType: 'flat_rate', price: '80.00', cost: '20.00' },
{ name: 'Bridge Setup', itemCategory: 'Cello', size: null, itemType: 'flat_rate', price: '55.00', cost: '15.00' },
{ name: 'String Change', itemCategory: 'Cello', size: null, itemType: 'flat_rate', price: '50.00', cost: '20.00' },
{ name: 'Endpin Repair', itemCategory: 'Cello', size: null, itemType: 'labor', price: '35.00', cost: null },
// Strings — Bass
{ name: 'Bow Rehair', itemCategory: 'Bass', size: null, itemType: 'flat_rate', price: '90.00', cost: '25.00' },
{ name: 'Bridge Setup', itemCategory: 'Bass', size: null, itemType: 'flat_rate', price: '65.00', cost: '20.00' },
{ name: 'String Change', itemCategory: 'Bass', size: null, itemType: 'flat_rate', price: '60.00', cost: '25.00' },
// Guitar
{ name: 'String Change', itemCategory: 'Guitar', size: 'Acoustic', itemType: 'flat_rate', price: '25.00', cost: '8.00' },
{ name: 'String Change', itemCategory: 'Guitar', size: 'Electric', itemType: 'flat_rate', price: '25.00', cost: '7.00' },
{ name: 'String Change', itemCategory: 'Guitar', size: 'Classical', itemType: 'flat_rate', price: '25.00', cost: '10.00' },
{ name: 'Full Setup', itemCategory: 'Guitar', size: 'Acoustic', itemType: 'flat_rate', price: '65.00', cost: '5.00' },
{ name: 'Full Setup', itemCategory: 'Guitar', size: 'Electric', itemType: 'flat_rate', price: '65.00', cost: '5.00' },
{ name: 'Fret Level & Crown', itemCategory: 'Guitar', size: null, itemType: 'labor', price: '150.00', cost: null },
{ name: 'Pickup Installation', itemCategory: 'Guitar', size: null, itemType: 'labor', price: '45.00', cost: null },
{ name: 'Nut Replacement', itemCategory: 'Guitar', size: null, itemType: 'flat_rate', price: '35.00', cost: '8.00' },
{ name: 'Tuning Machine Replacement', itemCategory: 'Guitar', size: null, itemType: 'flat_rate', price: '40.00', cost: '15.00' },
// Brass
{ name: 'Valve Overhaul', itemCategory: 'Trumpet', size: null, itemType: 'labor', price: '85.00', cost: null },
{ name: 'Chemical Cleaning', itemCategory: 'Trumpet', size: null, itemType: 'flat_rate', price: '55.00', cost: '10.00' },
{ name: 'Dent Removal', itemCategory: 'Trumpet', size: null, itemType: 'labor', price: '50.00', cost: null },
{ name: 'Slide Repair', itemCategory: 'Trombone', size: null, itemType: 'labor', price: '75.00', cost: null },
{ name: 'Chemical Cleaning', itemCategory: 'Trombone', size: null, itemType: 'flat_rate', price: '65.00', cost: '12.00' },
{ name: 'Dent Removal', itemCategory: 'Trombone', size: null, itemType: 'labor', price: '60.00', cost: null },
{ name: 'Valve Overhaul', itemCategory: 'French Horn', size: null, itemType: 'labor', price: '120.00', cost: null },
{ name: 'Chemical Cleaning', itemCategory: 'French Horn', size: null, itemType: 'flat_rate', price: '75.00', cost: '15.00' },
{ name: 'Valve Overhaul', itemCategory: 'Tuba', size: null, itemType: 'labor', price: '150.00', cost: null },
// Woodwinds
{ name: 'Pad Replacement', itemCategory: 'Clarinet', size: null, itemType: 'flat_rate', price: '120.00', cost: '30.00' },
{ name: 'Cork Replacement', itemCategory: 'Clarinet', size: null, itemType: 'flat_rate', price: '45.00', cost: '5.00' },
{ name: 'Key Adjustment', itemCategory: 'Clarinet', size: null, itemType: 'labor', price: '35.00', cost: null },
{ name: 'Pad Replacement', itemCategory: 'Flute', size: null, itemType: 'flat_rate', price: '110.00', cost: '25.00' },
{ name: 'Headjoint Cork', itemCategory: 'Flute', size: null, itemType: 'flat_rate', price: '30.00', cost: '5.00' },
{ name: 'Key Adjustment', itemCategory: 'Flute', size: null, itemType: 'labor', price: '35.00', cost: null },
{ name: 'Pad Replacement', itemCategory: 'Saxophone', size: 'Alto', itemType: 'flat_rate', price: '150.00', cost: '40.00' },
{ name: 'Pad Replacement', itemCategory: 'Saxophone', size: 'Tenor', itemType: 'flat_rate', price: '175.00', cost: '50.00' },
{ name: 'Cork & Felt Replacement', itemCategory: 'Saxophone', size: null, itemType: 'flat_rate', price: '65.00', cost: '10.00' },
{ name: 'Neck Cork', itemCategory: 'Saxophone', size: null, itemType: 'flat_rate', price: '20.00', cost: '3.00' },
{ name: 'Pad Replacement', itemCategory: 'Oboe', size: null, itemType: 'flat_rate', price: '200.00', cost: '60.00' },
{ name: 'Reed Adjustment', itemCategory: 'Oboe', size: null, itemType: 'labor', price: '15.00', cost: null },
{ name: 'Pad Replacement', itemCategory: 'Bassoon', size: null, itemType: 'flat_rate', price: '250.00', cost: '80.00' },
// General
{ name: 'General Cleaning', itemCategory: null, size: null, itemType: 'flat_rate', price: '30.00', cost: '5.00' },
{ name: 'Diagnostic Evaluation', itemCategory: null, size: null, itemType: 'flat_rate', price: '25.00', cost: null },
]
// Deactivate generic templates first
await sql`UPDATE repair_service_template SET is_active = false`
console.log(' Deactivated generic templates')
for (const t of templates) {
const existing = await sql`SELECT id FROM repair_service_template WHERE name = ${t.name} AND COALESCE(item_category, '') = ${t.itemCategory ?? ''} AND COALESCE(size, '') = ${t.size ?? ''}`
if (existing.length > 0) continue
await sql`INSERT INTO repair_service_template (name, item_category, size, item_type, default_price, default_cost, is_active) VALUES (${t.name}, ${t.itemCategory}, ${t.size}, ${t.itemType}, ${t.price}, ${t.cost}, true)`
console.log(` Template: ${t.name}${t.itemCategory ?? 'General'} ${t.size ?? ''}`)
}
// --- Music Repair Tickets ---
// First clear any generic tickets
await sql`DELETE FROM repair_ticket WHERE id NOT IN (SELECT DISTINCT repair_ticket_id FROM repair_note)`
const acctRows = await sql`SELECT id, name FROM account`
const acctIds: Record<string, string> = {}
for (const a of acctRows) acctIds[a.name] = a.id
const tickets = [
{ customer: 'Mike Thompson', item: 'Fender Stratocaster', serial: 'US22-045891', problem: 'Fret buzz on 3rd and 5th fret, needs full setup', condition: 'good', status: 'in_progress', estimate: '65.00' },
{ customer: 'Emily Chen', item: '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', item: 'German Workshop Violin 4/4', serial: null, problem: 'Bow needs rehair, bridge slightly warped', condition: 'fair', status: 'ready', estimate: '105.00' },
{ customer: 'Carlos Garcia', item: 'Martin D-28 Acoustic Guitar', serial: 'M2284563', problem: 'Broken tuning peg, needs replacement', condition: 'good', status: 'new', estimate: null },
{ customer: 'Lisa Johnson', item: 'Yamaha YCL-255 Clarinet', serial: null, problem: 'Several pads worn, keys sticking', condition: 'poor', status: 'diagnosing', estimate: null },
{ customer: 'Walk-In Customer', item: 'Gemeinhardt 2SP Flute', serial: null, problem: 'Squeaks on high notes, headjoint cork may need replacing', condition: 'fair', status: 'intake', estimate: null },
{ customer: 'Smith Family', item: 'Suzuki Student Violin 1/2', serial: null, problem: 'Pegs slipping, bridge leaning forward', condition: 'fair', status: 'new', estimate: null },
{ customer: 'Johnson Family', item: 'Selmer AS500 Alto Saxophone', serial: 'AS-99231', problem: 'Neck cork loose, low notes not speaking', condition: 'good', status: 'in_progress', estimate: '85.00' },
]
for (const t of tickets) {
const existing = await sql`SELECT id FROM repair_ticket WHERE 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 (ticket_number, customer_name, account_id, item_description, serial_number, problem_description, condition_in, status, estimated_cost) VALUES (${num}, ${t.customer}, ${acctId}, ${t.item}, ${t.serial}, ${t.problem}, ${t.condition}, ${t.status}, ${t.estimate})`
console.log(` Ticket: ${t.customer}${t.item} [${t.status}]`)
}
// --- School Band Batch ---
const batchExists = await sql`SELECT id FROM repair_batch WHERE contact_name = 'Mr. Williams'`
if (batchExists.length === 0) {
const schoolId = acctIds['Lincoln High School']
if (schoolId) {
const batchNum = String(Math.floor(100000 + Math.random() * 900000))
const [batch] = await sql`INSERT INTO repair_batch (batch_number, account_id, contact_name, contact_phone, contact_email, item_count, notes, status) VALUES (${batchNum}, ${schoolId}, 'Mr. Williams', '555-0210', 'williams@lincoln.edu', 6, 'Annual band instrument checkup — 6 instruments for fall semester', 'intake') RETURNING id`
const batchTickets = [
{ item: 'Student Flute — Gemeinhardt 2SP', problem: 'Pads worn, headjoint cork needs check', condition: 'fair' },
{ item: 'Student Clarinet — Yamaha YCL-255', problem: 'Keys sticking, barrel cork dried out', condition: 'fair' },
{ item: 'Student Clarinet — Buffet B12', problem: 'Barrel crack, needs assessment', condition: 'poor' },
{ item: 'Student Trumpet — Bach TR300', problem: 'Valve alignment off, general cleaning needed', condition: 'good' },
{ item: 'Student Trombone — Yamaha YSL-354', problem: 'Slide dent near bell, sluggish movement', condition: 'fair' },
{ item: 'Student Alto Sax — Selmer AS500', problem: 'Neck cork loose, octave key sticky', condition: 'fair' },
]
for (const bt of batchTickets) {
const num = String(Math.floor(100000 + Math.random() * 900000))
await sql`INSERT INTO repair_ticket (ticket_number, customer_name, account_id, repair_batch_id, item_description, problem_description, condition_in, status) VALUES (${num}, 'Lincoln High School', ${schoolId}, ${batch.id}, ${bt.item}, ${bt.problem}, ${bt.condition}, 'new')`
console.log(` Batch ticket: ${bt.item}`)
}
console.log(' Batch: Lincoln High School — 6 instruments')
}
}
// --- Enable inventory module ---
await sql`UPDATE module_config SET enabled = true WHERE slug = 'inventory'`
console.log(' Enabled inventory module')
// --- Inventory seed data ---
await seedInventory(sql)
// --- Enable lessons module ---
await sql`UPDATE module_config SET enabled = true WHERE slug = 'lessons'`
console.log(' Enabled lessons module')
// --- Lesson seed data ---
await seedLessons(sql)
console.log('\nMusic store seed complete!')
await sql.end()
}
async function seedInventory(sql: any) {
console.log('\nSeeding inventory data...')
// ── Suppliers ─────────────────────────────────────────────────────────────
const supplierDefs = [
{ name: 'Shar Music', contactName: 'Sales Desk', email: 'orders@sharmusic.com', phone: '800-248-7427', website: 'sharmusic.com', paymentTerms: 'Net 30' },
{ name: 'Southwest Strings', contactName: 'Dealer Rep', email: 'dealer@swstrings.com', phone: '800-528-3430', website: 'swstrings.com', paymentTerms: 'Net 30' },
{ name: 'Connolly Music', contactName: 'Account Rep', email: 'wholesale@connollymusic.com', phone: '800-644-5268', website: 'connollymusic.com', paymentTerms: 'Net 60' },
{ name: 'D\'Addario', contactName: 'Dealer Services', email: 'dealer@daddario.com', phone: '800-323-2746', website: 'daddario.com', paymentTerms: 'Net 30' },
{ name: 'Pirastro', contactName: 'US Distributor', email: 'us@pirastro.com', phone: '800-223-2500', website: 'pirastro.com', paymentTerms: 'Net 30' },
]
const supplierIds: Record<string, string> = {}
for (const s of supplierDefs) {
const [existing] = await sql`SELECT id FROM supplier WHERE name = ${s.name}`
if (existing) { supplierIds[s.name] = existing.id; continue }
const [row] = await sql`INSERT INTO supplier (name, contact_name, email, phone, website, payment_terms) VALUES (${s.name}, ${s.contactName}, ${s.email}, ${s.phone}, ${s.website}, ${s.paymentTerms}) RETURNING id`
supplierIds[s.name] = row.id
console.log(` Supplier: ${s.name}`)
}
// ── Categories ─────────────────────────────────────────────────────────────
const categoryDefs = [
{ name: 'Violins', sortOrder: 1 },
{ name: 'Violas', sortOrder: 2 },
{ name: 'Cellos', sortOrder: 3 },
{ name: 'Basses', sortOrder: 4 },
{ name: 'Bows', sortOrder: 5 },
{ name: 'Strings & Accessories', sortOrder: 6 },
{ name: 'Cases & Bags', sortOrder: 7 },
{ name: 'Rosin', sortOrder: 8 },
{ name: 'Shoulder Rests', sortOrder: 9 },
{ name: 'Sheet Music & Books', sortOrder: 10 },
{ name: 'Maintenance & Care', sortOrder: 11 },
]
const catIds: Record<string, string> = {}
for (const c of categoryDefs) {
const [existing] = await sql`SELECT id FROM category WHERE name = ${c.name}`
if (existing) { catIds[c.name] = existing.id; continue }
const [row] = await sql`INSERT INTO category (name, sort_order) VALUES (${c.name}, ${c.sortOrder}) RETURNING id`
catIds[c.name] = row.id
console.log(` Category: ${c.name}`)
}
// ── Products ───────────────────────────────────────────────────────────────
// isSerialized=true → track individual units; isRental=true → available for rental
type ProductDef = {
name: string; sku: string; brand: string; model: string; category: string
price: string; minPrice?: string; rentalRateMonthly?: string
isSerialized: boolean; isRental: boolean; isDualUseRepair?: boolean
qtyOnHand?: number; qtyReorderPoint?: number; description?: string
}
const productDefs: ProductDef[] = [
// ── Violins ──
{
name: 'Violin 4/4 — Student Outfit', sku: 'VLN-44-STU', brand: 'Eastman', model: 'VL80',
category: 'Violins', price: '399.00', minPrice: '350.00', rentalRateMonthly: '25.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: 'Full-size student violin outfit includes bow, rosin, and lightweight case.',
},
{
name: 'Violin 3/4 — Student Outfit', sku: 'VLN-34-STU', brand: 'Eastman', model: 'VL80',
category: 'Violins', price: '349.00', minPrice: '300.00', rentalRateMonthly: '22.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '3/4 size student violin outfit — ideal for players ages 710.',
},
{
name: 'Violin 1/2 — Student Outfit', sku: 'VLN-12-STU', brand: 'Eastman', model: 'VL80',
category: 'Violins', price: '299.00', minPrice: '260.00', rentalRateMonthly: '20.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '1/2 size student violin outfit — ideal for players ages 57.',
},
{
name: 'Violin 1/4 — Student Outfit', sku: 'VLN-14-STU', brand: 'Eastman', model: 'VL80',
category: 'Violins', price: '249.00', minPrice: '220.00', rentalRateMonthly: '18.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '1/4 size student violin outfit — ideal for players ages 46.',
},
{
name: 'Violin 4/4 — Intermediate', sku: 'VLN-44-INT', brand: 'Eastman', model: 'VL305',
category: 'Violins', price: '895.00', minPrice: '800.00', rentalRateMonthly: '45.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: 'Carved spruce top and maple back/sides. Excellent step-up instrument.',
},
{
name: 'Violin 4/4 — Advanced', sku: 'VLN-44-ADV', brand: 'Eastman', model: 'VL605',
category: 'Violins', price: '2495.00', minPrice: '2200.00',
isSerialized: true, isRental: false, isDualUseRepair: false,
description: 'Hand-carved workshop violin with Dominant strings. Concert-ready.',
},
// ── Violas ──
{
name: 'Viola 15" — Student Outfit', sku: 'VLA-15-STU', brand: 'Eastman', model: 'VA80',
category: 'Violas', price: '449.00', minPrice: '400.00', rentalRateMonthly: '28.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '15-inch student viola outfit with bow and lightweight case.',
},
{
name: 'Viola 16" — Student Outfit', sku: 'VLA-16-STU', brand: 'Eastman', model: 'VA80',
category: 'Violas', price: '449.00', minPrice: '400.00', rentalRateMonthly: '28.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '16-inch student viola outfit with bow and lightweight case.',
},
{
name: 'Viola 16" — Intermediate', sku: 'VLA-16-INT', brand: 'Eastman', model: 'VA305',
category: 'Violas', price: '1095.00', minPrice: '950.00', rentalRateMonthly: '55.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: 'Step-up viola with carved top, warm dark tone. Includes quality bow and case.',
},
// ── Cellos ──
{
name: 'Cello 4/4 — Student Outfit', sku: 'CLO-44-STU', brand: 'Eastman', model: 'VC80',
category: 'Cellos', price: '649.00', minPrice: '580.00', rentalRateMonthly: '40.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: 'Full-size student cello outfit includes fiberglass bow and padded bag.',
},
{
name: 'Cello 3/4 — Student Outfit', sku: 'CLO-34-STU', brand: 'Eastman', model: 'VC80',
category: 'Cellos', price: '599.00', minPrice: '530.00', rentalRateMonthly: '36.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '3/4 student cello outfit — ideal for players ages 912.',
},
{
name: 'Cello 1/2 — Student Outfit', sku: 'CLO-12-STU', brand: 'Eastman', model: 'VC80',
category: 'Cellos', price: '549.00', minPrice: '490.00', rentalRateMonthly: '32.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '1/2 student cello outfit — ideal for players ages 710.',
},
{
name: 'Cello 4/4 — Intermediate', sku: 'CLO-44-INT', brand: 'Eastman', model: 'VC305',
category: 'Cellos', price: '1495.00', minPrice: '1300.00', rentalRateMonthly: '75.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: 'Carved spruce top intermediate cello. Powerful projection, warm tone.',
},
// ── Basses ──
{
name: 'Bass 3/4 — Student Outfit', sku: 'BAS-34-STU', brand: 'Engelhardt', model: 'ES-1',
category: 'Basses', price: '1299.00', minPrice: '1150.00', rentalRateMonthly: '65.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '3/4 student laminate bass outfit. Standard school/orchestra size.',
},
{
name: 'Bass 1/2 — Student', sku: 'BAS-12-STU', brand: 'Eastman', model: 'VB80',
category: 'Basses', price: '1099.00', minPrice: '980.00', rentalRateMonthly: '55.00',
isSerialized: true, isRental: true, isDualUseRepair: false,
description: '1/2 size student double bass — ideal for younger players.',
},
// ── Bows ──
{
name: 'Violin Bow — Student Brazilwood', sku: 'BOW-VLN-STU', brand: 'CodaBow', model: 'Joule',
category: 'Bows', price: '89.00', minPrice: '75.00',
isSerialized: false, isRental: false,
qtyOnHand: 8, qtyReorderPoint: 3,
description: 'Brazilwood student violin bow with nickel-silver winding.',
},
{
name: 'Violin Bow — Carbon Fiber', sku: 'BOW-VLN-CF', brand: 'CodaBow', model: 'Diamond NX',
category: 'Bows', price: '295.00', minPrice: '265.00',
isSerialized: false, isRental: false,
qtyOnHand: 4, qtyReorderPoint: 2,
description: 'Lightweight carbon fiber violin bow. Excellent balance and response.',
},
{
name: 'Viola Bow — Student Brazilwood', sku: 'BOW-VLA-STU', brand: 'CodaBow', model: 'Joule',
category: 'Bows', price: '95.00', minPrice: '80.00',
isSerialized: false, isRental: false,
qtyOnHand: 5, qtyReorderPoint: 2,
},
{
name: 'Cello Bow — Student Brazilwood', sku: 'BOW-CLO-STU', brand: 'CodaBow', model: 'Joule',
category: 'Bows', price: '110.00', minPrice: '95.00',
isSerialized: false, isRental: false,
qtyOnHand: 5, qtyReorderPoint: 2,
},
{
name: 'Bass Bow — Student Brazilwood (German)', sku: 'BOW-BAS-STU-G', brand: 'Glasser', model: 'German',
category: 'Bows', price: '85.00', minPrice: '70.00',
isSerialized: false, isRental: false,
qtyOnHand: 4, qtyReorderPoint: 2,
},
// ── Strings (also used as repair line items) ──
{
name: 'Violin Strings — Dominant 4/4 Set', sku: 'STR-VLN-DOM-44', brand: 'Thomastik', model: 'Dominant',
category: 'Strings & Accessories', price: '38.00', minPrice: '32.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 20, qtyReorderPoint: 6,
description: 'Industry-standard synthetic core violin string set. Full size.',
},
{
name: 'Violin Strings — Dominant 3/4 Set', sku: 'STR-VLN-DOM-34', brand: 'Thomastik', model: 'Dominant',
category: 'Strings & Accessories', price: '38.00', minPrice: '32.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 12, qtyReorderPoint: 4,
},
{
name: 'Violin Strings — Prelude 4/4 Set', sku: 'STR-VLN-PRE-44', brand: "D'Addario", model: 'Prelude',
category: 'Strings & Accessories', price: '18.00', minPrice: '15.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 24, qtyReorderPoint: 8,
description: 'Budget-friendly solid steel core strings. Great for beginners.',
},
{
name: 'Viola Strings — Dominant Set', sku: 'STR-VLA-DOM', brand: 'Thomastik', model: 'Dominant',
category: 'Strings & Accessories', price: '52.00', minPrice: '45.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 10, qtyReorderPoint: 3,
},
{
name: 'Cello Strings — Prelude Set', sku: 'STR-CLO-PRE', brand: "D'Addario", model: 'Prelude',
category: 'Strings & Accessories', price: '38.00', minPrice: '32.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 12, qtyReorderPoint: 4,
},
{
name: 'Cello Strings — Larsen Set', sku: 'STR-CLO-LAR', brand: 'Larsen', model: 'Original',
category: 'Strings & Accessories', price: '95.00', minPrice: '85.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 6, qtyReorderPoint: 2,
},
{
name: 'Bass Strings — Helicore Orchestral 3/4 Set', sku: 'STR-BAS-HEL', brand: "D'Addario", model: 'Helicore Orchestral',
category: 'Strings & Accessories', price: '110.00', minPrice: '95.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 8, qtyReorderPoint: 2,
},
// ── Rosin ──
{
name: 'Rosin — Hill Light', sku: 'RSN-HILL-L', brand: 'Hill', model: 'Light',
category: 'Rosin', price: '8.00', minPrice: '6.50',
isSerialized: false, isRental: false,
qtyOnHand: 30, qtyReorderPoint: 10,
description: 'Light rosin for violin and viola. Low dust.',
},
{
name: 'Rosin — Hill Dark', sku: 'RSN-HILL-D', brand: 'Hill', model: 'Dark',
category: 'Rosin', price: '8.00', minPrice: '6.50',
isSerialized: false, isRental: false,
qtyOnHand: 20, qtyReorderPoint: 8,
description: 'Dark rosin for cello and bass.',
},
{
name: 'Rosin — Pirastro Goldflex', sku: 'RSN-PIR-GF', brand: 'Pirastro', model: 'Goldflex',
category: 'Rosin', price: '14.00', minPrice: '11.00',
isSerialized: false, isRental: false,
qtyOnHand: 15, qtyReorderPoint: 5,
},
// ── Shoulder Rests ──
{
name: 'Shoulder Rest — Kun Original 4/4', sku: 'SR-KUN-44', brand: 'Kun', model: 'Original',
category: 'Shoulder Rests', price: '28.00', minPrice: '23.00',
isSerialized: false, isRental: false,
qtyOnHand: 12, qtyReorderPoint: 4,
description: 'Collapsible violin shoulder rest with rubber feet.',
},
{
name: 'Shoulder Rest — Kun Collapsible 3/44/4', sku: 'SR-KUN-C', brand: 'Kun', model: 'Collapsible',
category: 'Shoulder Rests', price: '35.00', minPrice: '29.00',
isSerialized: false, isRental: false,
qtyOnHand: 8, qtyReorderPoint: 3,
},
{
name: 'Shoulder Rest — Bon Musica 4/4', sku: 'SR-BON-44', brand: 'Bon Musica', model: '4/4',
category: 'Shoulder Rests', price: '42.00', minPrice: '36.00',
isSerialized: false, isRental: false,
qtyOnHand: 6, qtyReorderPoint: 2,
description: 'Curved metal shoulder rest with contoured padding. Great for players with shoulder issues.',
},
// ── Maintenance ──
{
name: 'String Cleaner — Ultra X-Cleaner', sku: 'MNT-CLN-STR', brand: 'Ultra', model: 'X-Cleaner',
category: 'Maintenance & Care', price: '12.00', minPrice: '9.00',
isSerialized: false, isRental: false,
qtyOnHand: 18, qtyReorderPoint: 6,
},
{
name: 'Instrument Polish — Hill', sku: 'MNT-POL-HILL', brand: 'Hill', model: 'Polish',
category: 'Maintenance & Care', price: '10.00', minPrice: '8.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 15, qtyReorderPoint: 5,
},
// ── Repair Parts ──
{
name: 'Bridge — Violin 4/4 Blank', sku: 'RPR-BRG-VLN-44', brand: 'Aubert', model: 'Mirecourt',
category: 'Maintenance & Care', price: '18.00', minPrice: '14.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 10, qtyReorderPoint: 4,
description: 'Aubert Mirecourt violin bridge blank. Requires fitting.',
},
{
name: 'Bridge — Violin 3/4 Blank', sku: 'RPR-BRG-VLN-34', brand: 'Aubert', model: 'Mirecourt',
category: 'Maintenance & Care', price: '16.00', minPrice: '12.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 6, qtyReorderPoint: 2,
},
{
name: 'Bridge — Viola Blank', sku: 'RPR-BRG-VLA', brand: 'Aubert', model: 'Mirecourt',
category: 'Maintenance & Care', price: '22.00', minPrice: '17.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 6, qtyReorderPoint: 2,
},
{
name: 'Bridge — Cello Blank', sku: 'RPR-BRG-CLO', brand: 'Aubert', model: 'Mirecourt',
category: 'Maintenance & Care', price: '32.00', minPrice: '25.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 4, qtyReorderPoint: 2,
},
{
name: 'Bridge — Bass Blank', sku: 'RPR-BRG-BAS', brand: 'Aubert', model: 'Mirecourt',
category: 'Maintenance & Care', price: '48.00', minPrice: '38.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 3, qtyReorderPoint: 1,
},
{
name: 'Tailpiece — Violin 4/4 Composite', sku: 'RPR-TLP-VLN-44', brand: 'Wittner', model: 'Ultra',
category: 'Maintenance & Care', price: '14.00', minPrice: '11.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 8, qtyReorderPoint: 3,
description: 'Wittner Ultra composite tailpiece with integrated fine tuners.',
},
{
name: 'Tailpiece — Cello 4/4 Composite', sku: 'RPR-TLP-CLO-44', brand: 'Wittner', model: 'Ultra',
category: 'Maintenance & Care', price: '22.00', minPrice: '17.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 4, qtyReorderPoint: 2,
},
{
name: 'Pegs — Violin Boxwood Set (4)', sku: 'RPR-PEG-VLN-BX', brand: 'Generic', model: 'Boxwood',
category: 'Maintenance & Care', price: '12.00', minPrice: '9.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 12, qtyReorderPoint: 4,
description: 'Boxwood violin pegs, set of 4. Requires fitting and reaming.',
},
{
name: 'Pegs — Viola Boxwood Set (4)', sku: 'RPR-PEG-VLA-BX', brand: 'Generic', model: 'Boxwood',
category: 'Maintenance & Care', price: '14.00', minPrice: '11.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 8, qtyReorderPoint: 3,
},
{
name: 'Pegs — Cello Boxwood Set (4)', sku: 'RPR-PEG-CLO-BX', brand: 'Generic', model: 'Boxwood',
category: 'Maintenance & Care', price: '18.00', minPrice: '14.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 6, qtyReorderPoint: 2,
},
{
name: 'Fine Tuner — Single Violin', sku: 'RPR-FTN-VLN', brand: 'Wittner', model: 'Finetune',
category: 'Maintenance & Care', price: '4.00', minPrice: '3.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 20, qtyReorderPoint: 8,
},
{
name: 'Endpin — Cello Replacement', sku: 'RPR-EPN-CLO', brand: 'Wittner', model: 'Finetune',
category: 'Maintenance & Care', price: '24.00', minPrice: '18.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 4, qtyReorderPoint: 2,
description: 'Replacement cello endpin with aluminum sleeve.',
},
{
name: 'Endpin — Bass Replacement', sku: 'RPR-EPN-BAS', brand: 'Wittner', model: 'Finetune',
category: 'Maintenance & Care', price: '32.00', minPrice: '24.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 3, qtyReorderPoint: 1,
},
{
name: 'Nut — Violin Ebony Blank', sku: 'RPR-NUT-VLN', brand: 'Generic', model: 'Ebony',
category: 'Maintenance & Care', price: '6.00', minPrice: '4.50',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 10, qtyReorderPoint: 4,
},
{
name: 'Seam Glue — Hide Glue (4 oz)', sku: 'RPR-GLU-HIDE', brand: 'Franklin', model: 'Hide Glue',
category: 'Maintenance & Care', price: '8.00', minPrice: '6.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 6, qtyReorderPoint: 2,
description: 'Traditional hide glue for open seam and structural repairs.',
},
{
name: 'Sound Post — Violin', sku: 'RPR-SPS-VLN', brand: 'Generic', model: 'Spruce',
category: 'Maintenance & Care', price: '5.00', minPrice: '3.50',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 8, qtyReorderPoint: 3,
},
{
name: 'Sound Post — Cello', sku: 'RPR-SPS-CLO', brand: 'Generic', model: 'Spruce',
category: 'Maintenance & Care', price: '8.00', minPrice: '6.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 4, qtyReorderPoint: 2,
},
{
name: 'Bow Hair — Mongolian White (hank)', sku: 'RPR-HAIR-WH', brand: 'Generic', model: 'Mongolian White',
category: 'Maintenance & Care', price: '18.00', minPrice: '14.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 10, qtyReorderPoint: 3,
description: 'Mongolian white bow hair for rehairing violin, viola, cello, or bass bows.',
},
{
name: 'Peg Compound — Hill Peg Dope', sku: 'RPR-PEG-DOC', brand: 'Hill', model: 'Peg Compound',
category: 'Maintenance & Care', price: '6.00', minPrice: '4.50',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 12, qtyReorderPoint: 4,
description: 'Hill peg compound for slipping or sticking pegs.',
},
{
name: 'Fingerboard Oil — Lemon Oil (2 oz)', sku: 'RPR-OIL-LMN', brand: 'Music Nomad', model: 'Lemon Oil',
category: 'Maintenance & Care', price: '9.00', minPrice: '7.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 10, qtyReorderPoint: 4,
description: 'Conditions and cleans ebony and rosewood fingerboards.',
},
{
name: 'Chin Rest — Violin Guarneri Style', sku: 'RPR-CHN-VLN-GU', brand: 'Wittner', model: 'Guarneri',
category: 'Maintenance & Care', price: '16.00', minPrice: '12.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 8, qtyReorderPoint: 3,
description: 'Wittner composite Guarneri-style violin chin rest. Center mount.',
},
{
name: 'Chin Rest — Viola Side Mount', sku: 'RPR-CHN-VLA-SD', brand: 'Wittner', model: 'Augsburg',
category: 'Maintenance & Care', price: '18.00', minPrice: '14.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 6, qtyReorderPoint: 2,
},
{
name: 'Chin Rest Screws — Replacement Set', sku: 'RPR-CHN-SCR', brand: 'Generic', model: 'Brass',
category: 'Maintenance & Care', price: '3.00', minPrice: '2.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 20, qtyReorderPoint: 8,
description: 'Replacement brass chin rest barrel screws, set of 2.',
},
{
name: 'Bow Grip — Leather Violin/Viola', sku: 'RPR-GRP-VLN', brand: 'Generic', model: 'Leather',
category: 'Maintenance & Care', price: '5.00', minPrice: '3.50',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 15, qtyReorderPoint: 6,
description: 'Replacement leather bow grip for violin or viola bow.',
},
{
name: 'Bow Grip — Leather Cello/Bass', sku: 'RPR-GRP-CLO', brand: 'Generic', model: 'Leather',
category: 'Maintenance & Care', price: '6.00', minPrice: '4.50',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 10, qtyReorderPoint: 4,
},
{
name: 'Bow Winding — Silver Lapping', sku: 'RPR-WND-SLV', brand: 'Generic', model: 'Silver',
category: 'Maintenance & Care', price: '8.00', minPrice: '6.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 8, qtyReorderPoint: 3,
description: 'Silver lapping wire for bow winding replacement.',
},
{
name: 'Bow Tip Plate — Violin Ivory-Style', sku: 'RPR-TIP-VLN', brand: 'Generic', model: 'Mammoth Ivory',
category: 'Maintenance & Care', price: '7.00', minPrice: '5.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 10, qtyReorderPoint: 4,
description: 'Replacement bow tip plate (mammoth ivory substitute).',
},
{
name: 'Saddle — Violin Ebony', sku: 'RPR-SDL-VLN', brand: 'Generic', model: 'Ebony',
category: 'Maintenance & Care', price: '5.00', minPrice: '3.50',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 10, qtyReorderPoint: 4,
},
{
name: 'Saddle — Cello Ebony', sku: 'RPR-SDL-CLO', brand: 'Generic', model: 'Ebony',
category: 'Maintenance & Care', price: '8.00', minPrice: '6.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 6, qtyReorderPoint: 2,
},
{
name: 'Retouching Varnish — Violin/Viola', sku: 'RPR-VRN-RTH', brand: 'Hammerl', model: 'Retouching',
category: 'Maintenance & Care', price: '22.00', minPrice: '17.00',
isSerialized: false, isRental: false, isDualUseRepair: true,
qtyOnHand: 4, qtyReorderPoint: 2,
description: 'Spirit-based retouching varnish for cosmetic repairs and touch-ups.',
},
{
name: 'Humidifier — Dampit Violin', sku: 'MNT-HUM-VLN', brand: 'Dampit', model: 'Violin',
category: 'Maintenance & Care', price: '16.00', minPrice: '13.00',
isSerialized: false, isRental: false,
qtyOnHand: 12, qtyReorderPoint: 4,
},
{
name: 'Humidifier — Dampit Cello', sku: 'MNT-HUM-CLO', brand: 'Dampit', model: 'Cello',
category: 'Maintenance & Care', price: '18.00', minPrice: '15.00',
isSerialized: false, isRental: false,
qtyOnHand: 8, qtyReorderPoint: 3,
},
]
const productIds: Record<string, string> = {}
for (const p of productDefs) {
const [existing] = await sql`SELECT id FROM product WHERE sku = ${p.sku}`
if (existing) { productIds[p.sku] = existing.id; continue }
const catId = catIds[p.category] ?? null
const [row] = await sql`
INSERT INTO product (sku, name, brand, model, category_id, price, min_price, rental_rate_monthly,
is_serialized, is_rental, is_dual_use_repair, qty_on_hand, qty_reorder_point, description, is_active)
VALUES (${p.sku}, ${p.name}, ${p.brand}, ${p.model}, ${catId}, ${p.price}, ${p.minPrice ?? null},
${p.rentalRateMonthly ?? null}, ${p.isSerialized}, ${p.isRental}, ${p.isDualUseRepair ?? false},
${p.qtyOnHand ?? 0}, ${p.qtyReorderPoint ?? null}, ${p.description ?? null}, true)
RETURNING id`
productIds[p.sku] = row.id
console.log(` Product: ${p.name}`)
}
// ── Inventory Units (serialized instruments) ──────────────────────────────
type UnitDef = { sku: string; serial: string; condition: string; status: string; purchaseDate: string; cost: string; notes?: string }
const unitDefs: UnitDef[] = [
// Student violin 4/4 — rental fleet
{ sku: 'VLN-44-STU', serial: 'EA-V80-001', condition: 'good', status: 'available', purchaseDate: '2022-08-15', cost: '185.00' },
{ sku: 'VLN-44-STU', serial: 'EA-V80-002', condition: 'good', status: 'rented', purchaseDate: '2022-08-15', cost: '185.00' },
{ sku: 'VLN-44-STU', serial: 'EA-V80-003', condition: 'fair', status: 'rented', purchaseDate: '2022-08-15', cost: '185.00', notes: 'Minor scratches on back' },
{ sku: 'VLN-44-STU', serial: 'EA-V80-004', condition: 'good', status: 'rented', purchaseDate: '2022-08-15', cost: '185.00' },
{ sku: 'VLN-44-STU', serial: 'EA-V80-005', condition: 'excellent', status: 'available', purchaseDate: '2023-06-01', cost: '195.00' },
{ sku: 'VLN-44-STU', serial: 'EA-V80-006', condition: 'fair', status: 'in_repair', purchaseDate: '2021-08-01', cost: '175.00', notes: 'Pegs slipping, in shop' },
// Student violin 3/4
{ sku: 'VLN-34-STU', serial: 'EA-V80-3Q-001', condition: 'good', status: 'available', purchaseDate: '2022-09-01', cost: '160.00' },
{ sku: 'VLN-34-STU', serial: 'EA-V80-3Q-002', condition: 'good', status: 'rented', purchaseDate: '2022-09-01', cost: '160.00' },
{ sku: 'VLN-34-STU', serial: 'EA-V80-3Q-003', condition: 'fair', status: 'rented', purchaseDate: '2021-09-01', cost: '155.00', notes: 'Repaired open seam 2023' },
// Student violin 1/2
{ sku: 'VLN-12-STU', serial: 'EA-V80-HF-001', condition: 'good', status: 'rented', purchaseDate: '2022-09-01', cost: '140.00' },
{ sku: 'VLN-12-STU', serial: 'EA-V80-HF-002', condition: 'good', status: 'available', purchaseDate: '2023-07-01', cost: '145.00' },
// Student violin 1/4
{ sku: 'VLN-14-STU', serial: 'EA-V80-QT-001', condition: 'good', status: 'rented', purchaseDate: '2023-01-15', cost: '120.00' },
{ sku: 'VLN-14-STU', serial: 'EA-V80-QT-002', condition: 'fair', status: 'available', purchaseDate: '2022-01-15', cost: '115.00', notes: 'Replaced tailpiece 2023' },
// Intermediate violin
{ sku: 'VLN-44-INT', serial: 'EA-V305-001', condition: 'excellent', status: 'available', purchaseDate: '2023-08-01', cost: '420.00' },
{ sku: 'VLN-44-INT', serial: 'EA-V305-002', condition: 'good', status: 'rented', purchaseDate: '2023-08-01', cost: '420.00' },
// Advanced violin
{ sku: 'VLN-44-ADV', serial: 'EA-V605-001', condition: 'new', status: 'available', purchaseDate: '2024-02-01', cost: '1200.00' },
// Violas
{ sku: 'VLA-15-STU', serial: 'EA-VA80-15-001', condition: 'good', status: 'rented', purchaseDate: '2022-09-01', cost: '210.00' },
{ sku: 'VLA-15-STU', serial: 'EA-VA80-15-002', condition: 'excellent', status: 'available', purchaseDate: '2023-08-01', cost: '220.00' },
{ sku: 'VLA-16-STU', serial: 'EA-VA80-16-001', condition: 'good', status: 'rented', purchaseDate: '2022-09-01', cost: '210.00' },
{ sku: 'VLA-16-INT', serial: 'EA-VA305-001', condition: 'new', status: 'available', purchaseDate: '2024-01-15', cost: '520.00' },
// Cellos
{ sku: 'CLO-44-STU', serial: 'EA-VC80-001', condition: 'good', status: 'rented', purchaseDate: '2021-08-15', cost: '300.00' },
{ sku: 'CLO-44-STU', serial: 'EA-VC80-002', condition: 'good', status: 'rented', purchaseDate: '2021-08-15', cost: '300.00' },
{ sku: 'CLO-44-STU', serial: 'EA-VC80-003', condition: 'fair', status: 'in_repair', purchaseDate: '2020-08-15', cost: '285.00', notes: 'Endpin mechanism worn, awaiting part' },
{ sku: 'CLO-44-STU', serial: 'EA-VC80-004', condition: 'excellent', status: 'available', purchaseDate: '2023-09-01', cost: '315.00' },
{ sku: 'CLO-34-STU', serial: 'EA-VC80-3Q-001', condition: 'good', status: 'rented', purchaseDate: '2022-08-15', cost: '275.00' },
{ sku: 'CLO-12-STU', serial: 'EA-VC80-HF-001', condition: 'good', status: 'available', purchaseDate: '2023-08-01', cost: '255.00' },
{ sku: 'CLO-44-INT', serial: 'EA-VC305-001', condition: 'new', status: 'available', purchaseDate: '2024-03-01', cost: '720.00' },
// Basses
{ sku: 'BAS-34-STU', serial: 'ENG-ES1-001', condition: 'good', status: 'rented', purchaseDate: '2020-08-01', cost: '600.00', notes: 'New strings installed 2024' },
{ sku: 'BAS-34-STU', serial: 'ENG-ES1-002', condition: 'fair', status: 'available', purchaseDate: '2019-08-01', cost: '575.00', notes: 'Repaired bass bar 2022' },
{ sku: 'BAS-34-STU', serial: 'ENG-ES1-003', condition: 'excellent', status: 'available', purchaseDate: '2023-09-01', cost: '620.00' },
{ sku: 'BAS-12-STU', serial: 'EA-VB80-HF-001', condition: 'good', status: 'rented', purchaseDate: '2022-08-01', cost: '510.00' },
]
for (const u of unitDefs) {
const productId = productIds[u.sku]
if (!productId) continue
const [existing] = await sql`SELECT id FROM inventory_unit WHERE serial_number = ${u.serial}`
if (existing) continue
await sql`
INSERT INTO inventory_unit (product_id, serial_number, condition, status, purchase_date, purchase_cost, notes)
VALUES (${productId}, ${u.serial}, ${u.condition}, ${u.status}, ${u.purchaseDate}, ${u.cost}, ${u.notes ?? null})`
console.log(` Unit: ${u.serial} (${u.sku}) [${u.status}]`)
}
// ── Price History ──────────────────────────────────────────────────────────
// Simulate a couple of price changes per key product over the past year
type PriceHistoryDef = { sku: string; previousPrice: string; newPrice: string; previousMinPrice?: string; newMinPrice?: string; daysAgo: number }
const priceHistoryDefs: PriceHistoryDef[] = [
// Student violins — small price increases over two cycles
{ sku: 'VLN-44-STU', previousPrice: '379.00', newPrice: '389.00', previousMinPrice: '330.00', newMinPrice: '340.00', daysAgo: 365 },
{ sku: 'VLN-44-STU', previousPrice: '389.00', newPrice: '399.00', previousMinPrice: '340.00', newMinPrice: '350.00', daysAgo: 180 },
{ sku: 'VLN-34-STU', previousPrice: '329.00', newPrice: '349.00', previousMinPrice: '285.00', newMinPrice: '300.00', daysAgo: 180 },
{ sku: 'VLN-12-STU', previousPrice: '279.00', newPrice: '299.00', previousMinPrice: '245.00', newMinPrice: '260.00', daysAgo: 180 },
{ sku: 'VLN-14-STU', previousPrice: '229.00', newPrice: '249.00', previousMinPrice: '200.00', newMinPrice: '220.00', daysAgo: 180 },
// Intermediate violin — one increase
{ sku: 'VLN-44-INT', previousPrice: '849.00', newPrice: '895.00', previousMinPrice: '759.00', newMinPrice: '800.00', daysAgo: 270 },
// Advanced violin — introduced at lower price, increased
{ sku: 'VLN-44-ADV', previousPrice: '2295.00', newPrice: '2495.00', previousMinPrice: '2050.00', newMinPrice: '2200.00', daysAgo: 120 },
// Violas
{ sku: 'VLA-15-STU', previousPrice: '419.00', newPrice: '449.00', previousMinPrice: '370.00', newMinPrice: '400.00', daysAgo: 200 },
{ sku: 'VLA-16-STU', previousPrice: '419.00', newPrice: '449.00', previousMinPrice: '370.00', newMinPrice: '400.00', daysAgo: 200 },
{ sku: 'VLA-16-INT', previousPrice: '995.00', newPrice: '1095.00', previousMinPrice: '875.00', newMinPrice: '950.00', daysAgo: 150 },
// Cellos
{ sku: 'CLO-44-STU', previousPrice: '599.00', newPrice: '649.00', previousMinPrice: '535.00', newMinPrice: '580.00', daysAgo: 220 },
{ sku: 'CLO-34-STU', previousPrice: '549.00', newPrice: '599.00', previousMinPrice: '490.00', newMinPrice: '530.00', daysAgo: 220 },
{ sku: 'CLO-44-INT', previousPrice: '1349.00', newPrice: '1495.00', previousMinPrice: '1195.00', newMinPrice: '1300.00', daysAgo: 160 },
// Basses
{ sku: 'BAS-34-STU', previousPrice: '1199.00', newPrice: '1299.00', previousMinPrice: '1075.00', newMinPrice: '1150.00', daysAgo: 240 },
// Strings — supplier cost increases
{ sku: 'STR-VLN-DOM-44', previousPrice: '34.00', newPrice: '38.00', previousMinPrice: '28.00', newMinPrice: '32.00', daysAgo: 90 },
{ sku: 'STR-CLO-LAR', previousPrice: '88.00', newPrice: '95.00', previousMinPrice: '78.00', newMinPrice: '85.00', daysAgo: 90 },
{ sku: 'STR-BAS-HEL', previousPrice: '99.00', newPrice: '110.00', previousMinPrice: '87.00', newMinPrice: '95.00', daysAgo: 90 },
// Bridges — material cost increase
{ sku: 'RPR-BRG-VLN-44', previousPrice: '15.00', newPrice: '18.00', daysAgo: 120 },
{ sku: 'RPR-BRG-CLO', previousPrice: '28.00', newPrice: '32.00', daysAgo: 120 },
// Bow hair
{ sku: 'RPR-HAIR-WH', previousPrice: '15.00', newPrice: '18.00', daysAgo: 60 },
]
for (const h of priceHistoryDefs) {
const productId = productIds[h.sku]
if (!productId) continue
const changedAt = new Date(Date.now() - h.daysAgo * 24 * 60 * 60 * 1000).toISOString()
const [existing] = await sql`
SELECT id FROM price_history WHERE product_id = ${productId} AND new_price = ${h.newPrice} AND previous_price = ${h.previousPrice}`
if (existing) continue
await sql`
INSERT INTO price_history (product_id, previous_price, new_price, previous_min_price, new_min_price, created_at)
VALUES (${productId}, ${h.previousPrice}, ${h.newPrice}, ${h.previousMinPrice ?? null}, ${h.newMinPrice ?? null}, ${changedAt})`
console.log(` Price history: ${h.sku} $${h.previousPrice}$${h.newPrice}`)
}
// ── Product-Supplier Links ─────────────────────────────────────────────────
type SupplierLink = { sku: string; supplier: string; supplierSku?: string; isPreferred: boolean }
const supplierLinks: SupplierLink[] = [
// Violins — Shar preferred, Southwest alternate
{ sku: 'VLN-44-STU', supplier: 'Shar Music', supplierSku: 'EA-VL80-44', isPreferred: true },
{ sku: 'VLN-44-STU', supplier: 'Southwest Strings', supplierSku: 'EAST-VL80-4', isPreferred: false },
{ sku: 'VLN-34-STU', supplier: 'Shar Music', supplierSku: 'EA-VL80-34', isPreferred: true },
{ sku: 'VLN-34-STU', supplier: 'Southwest Strings', supplierSku: 'EAST-VL80-3', isPreferred: false },
{ sku: 'VLN-12-STU', supplier: 'Shar Music', supplierSku: 'EA-VL80-12', isPreferred: true },
{ sku: 'VLN-14-STU', supplier: 'Shar Music', supplierSku: 'EA-VL80-14', isPreferred: true },
{ sku: 'VLN-44-INT', supplier: 'Shar Music', supplierSku: 'EA-VL305', isPreferred: true },
{ sku: 'VLN-44-INT', supplier: 'Southwest Strings', supplierSku: 'EAST-VL305', isPreferred: false },
{ sku: 'VLN-44-ADV', supplier: 'Shar Music', supplierSku: 'EA-VL605', isPreferred: true },
// Violas — Shar preferred, Connolly alternate
{ sku: 'VLA-15-STU', supplier: 'Shar Music', supplierSku: 'EA-VA80-15', isPreferred: true },
{ sku: 'VLA-15-STU', supplier: 'Connolly Music', supplierSku: 'EAST-VA80-15', isPreferred: false },
{ sku: 'VLA-16-STU', supplier: 'Shar Music', supplierSku: 'EA-VA80-16', isPreferred: true },
{ sku: 'VLA-16-INT', supplier: 'Shar Music', supplierSku: 'EA-VA305-16', isPreferred: true },
{ sku: 'VLA-16-INT', supplier: 'Connolly Music', supplierSku: 'EAST-VA305', isPreferred: false },
// Cellos — Connolly preferred, Shar alternate
{ sku: 'CLO-44-STU', supplier: 'Connolly Music', supplierSku: 'EAST-VC80-44', isPreferred: true },
{ sku: 'CLO-44-STU', supplier: 'Shar Music', supplierSku: 'EA-VC80-44', isPreferred: false },
{ sku: 'CLO-34-STU', supplier: 'Connolly Music', supplierSku: 'EAST-VC80-34', isPreferred: true },
{ sku: 'CLO-12-STU', supplier: 'Connolly Music', supplierSku: 'EAST-VC80-12', isPreferred: true },
{ sku: 'CLO-44-INT', supplier: 'Connolly Music', supplierSku: 'EAST-VC305', isPreferred: true },
{ sku: 'CLO-44-INT', supplier: 'Shar Music', supplierSku: 'EA-VC305', isPreferred: false },
// Basses — Connolly preferred
{ sku: 'BAS-34-STU', supplier: 'Connolly Music', supplierSku: 'ENG-ES1', isPreferred: true },
{ sku: 'BAS-34-STU', supplier: 'Southwest Strings', supplierSku: 'ENG-ES1-SW', isPreferred: false },
{ sku: 'BAS-12-STU', supplier: 'Connolly Music', supplierSku: 'EAST-VB80-12', isPreferred: true },
// Strings — D'Addario for Prelude/Helicore, Shar for Dominant, Pirastro for Larsen
{ sku: 'STR-VLN-PRE-44', supplier: "D'Addario", supplierSku: 'J810-4/4M', isPreferred: true },
{ sku: 'STR-VLN-DOM-44', supplier: 'Shar Music', supplierSku: 'TH-132B', isPreferred: true },
{ sku: 'STR-VLN-DOM-34', supplier: 'Shar Music', supplierSku: 'TH-132B-34', isPreferred: true },
{ sku: 'STR-VLA-DOM', supplier: 'Shar Music', supplierSku: 'TH-141B', isPreferred: true },
{ sku: 'STR-CLO-PRE', supplier: "D'Addario", supplierSku: 'J1010-4/4M', isPreferred: true },
{ sku: 'STR-CLO-LAR', supplier: 'Pirastro', supplierSku: 'LAR-SET-4', isPreferred: true },
{ sku: 'STR-BAS-HEL', supplier: "D'Addario", supplierSku: 'J6103/4M', isPreferred: true },
// Rosin
{ sku: 'RSN-HILL-L', supplier: 'Shar Music', supplierSku: 'HILL-RSN-L', isPreferred: true },
{ sku: 'RSN-HILL-D', supplier: 'Shar Music', supplierSku: 'HILL-RSN-D', isPreferred: true },
{ sku: 'RSN-PIR-GF', supplier: 'Pirastro', supplierSku: '900500', isPreferred: true },
// Bows
{ sku: 'BOW-VLN-STU', supplier: 'Shar Music', supplierSku: 'CB-JOULE-VLN', isPreferred: true },
{ sku: 'BOW-VLN-STU', supplier: 'Southwest Strings', supplierSku: 'CODA-JOULE-V', isPreferred: false },
{ sku: 'BOW-VLN-CF', supplier: 'Shar Music', supplierSku: 'CB-DNX-VLN', isPreferred: true },
{ sku: 'BOW-VLA-STU', supplier: 'Shar Music', supplierSku: 'CB-JOULE-VLA', isPreferred: true },
{ sku: 'BOW-CLO-STU', supplier: 'Shar Music', supplierSku: 'CB-JOULE-CLO', isPreferred: true },
{ sku: 'BOW-BAS-STU-G', supplier: 'Southwest Strings', supplierSku: 'GL-GER-BAS', isPreferred: true },
// Maintenance
{ sku: 'MNT-POL-HILL', supplier: 'Shar Music', supplierSku: 'HILL-POL', isPreferred: true },
{ sku: 'MNT-HUM-VLN', supplier: 'Shar Music', supplierSku: 'DAMP-VLN', isPreferred: true },
{ sku: 'MNT-HUM-CLO', supplier: 'Shar Music', supplierSku: 'DAMP-CLO', isPreferred: true },
]
for (const link of supplierLinks) {
const productId = productIds[link.sku]
const supplierId = supplierIds[link.supplier]
if (!productId || !supplierId) continue
const [existing] = await sql`SELECT id FROM product_supplier WHERE product_id = ${productId} AND supplier_id = ${supplierId}`
if (existing) continue
await sql`
INSERT INTO product_supplier (product_id, supplier_id, supplier_sku, is_preferred)
VALUES (${productId}, ${supplierId}, ${link.supplierSku ?? null}, ${link.isPreferred})`
console.log(` Linked ${link.sku}${link.supplier}${link.isPreferred ? ' (preferred)' : ''}`)
}
// ── Stock Receipts ─────────────────────────────────────────────────────────
type ReceiptDef = { sku: string; supplier?: string; qty: number; costPerUnit: string; receivedDate: string; invoiceNumber?: string; notes?: string }
const receiptDefs: ReceiptDef[] = [
// Initial instrument purchases
{ sku: 'VLN-44-STU', supplier: 'Shar Music', qty: 6, costPerUnit: '185.00', receivedDate: '2022-08-01', invoiceNumber: 'SHAR-2022-0801' },
{ sku: 'VLN-34-STU', supplier: 'Shar Music', qty: 3, costPerUnit: '160.00', receivedDate: '2022-09-01', invoiceNumber: 'SHAR-2022-0901' },
{ sku: 'VLN-12-STU', supplier: 'Shar Music', qty: 2, costPerUnit: '140.00', receivedDate: '2022-09-01', invoiceNumber: 'SHAR-2022-0901' },
{ sku: 'VLN-14-STU', supplier: 'Shar Music', qty: 2, costPerUnit: '115.00', receivedDate: '2022-09-01', invoiceNumber: 'SHAR-2022-0901' },
{ sku: 'VLN-44-INT', supplier: 'Shar Music', qty: 2, costPerUnit: '420.00', receivedDate: '2023-08-01', invoiceNumber: 'SHAR-2023-0801' },
{ sku: 'VLN-44-ADV', supplier: 'Shar Music', qty: 1, costPerUnit: '1200.00', receivedDate: '2024-02-01', invoiceNumber: 'SHAR-2024-0201' },
{ sku: 'VLA-15-STU', supplier: 'Shar Music', qty: 2, costPerUnit: '210.00', receivedDate: '2022-09-01', invoiceNumber: 'SHAR-2022-0901' },
{ sku: 'VLA-16-STU', supplier: 'Shar Music', qty: 1, costPerUnit: '210.00', receivedDate: '2022-09-01', invoiceNumber: 'SHAR-2022-0901' },
{ sku: 'VLA-16-INT', supplier: 'Shar Music', qty: 1, costPerUnit: '520.00', receivedDate: '2024-01-15', invoiceNumber: 'SHAR-2024-0115' },
{ sku: 'CLO-44-STU', supplier: 'Connolly Music', qty: 4, costPerUnit: '300.00', receivedDate: '2021-08-01', invoiceNumber: 'CON-2021-0801' },
{ sku: 'CLO-34-STU', supplier: 'Connolly Music', qty: 1, costPerUnit: '275.00', receivedDate: '2022-08-01', invoiceNumber: 'CON-2022-0801' },
{ sku: 'CLO-12-STU', supplier: 'Connolly Music', qty: 1, costPerUnit: '255.00', receivedDate: '2023-08-01', invoiceNumber: 'CON-2023-0801' },
{ sku: 'CLO-44-INT', supplier: 'Connolly Music', qty: 1, costPerUnit: '720.00', receivedDate: '2024-03-01', invoiceNumber: 'CON-2024-0301' },
{ sku: 'BAS-34-STU', supplier: 'Connolly Music', qty: 3, costPerUnit: '595.00', receivedDate: '2019-08-01', invoiceNumber: 'CON-2019-0801' },
{ sku: 'BAS-12-STU', supplier: 'Connolly Music', qty: 1, costPerUnit: '510.00', receivedDate: '2022-08-01', invoiceNumber: 'CON-2022-0802' },
// Bow stock
{ sku: 'BOW-VLN-STU', supplier: 'Shar Music', qty: 8, costPerUnit: '52.00', receivedDate: '2024-01-10', invoiceNumber: 'SHAR-2024-0110' },
{ sku: 'BOW-VLN-CF', supplier: 'Shar Music', qty: 4, costPerUnit: '175.00', receivedDate: '2024-01-10', invoiceNumber: 'SHAR-2024-0110' },
{ sku: 'BOW-VLA-STU', supplier: 'Shar Music', qty: 5, costPerUnit: '56.00', receivedDate: '2024-01-10', invoiceNumber: 'SHAR-2024-0110' },
{ sku: 'BOW-CLO-STU', supplier: 'Shar Music', qty: 5, costPerUnit: '65.00', receivedDate: '2024-01-10', invoiceNumber: 'SHAR-2024-0110' },
{ sku: 'BOW-BAS-STU-G', supplier: 'Southwest Strings', qty: 4, costPerUnit: '48.00', receivedDate: '2024-02-01', invoiceNumber: 'SW-2024-0201' },
// String restocks
{ sku: 'STR-VLN-DOM-44', supplier: 'Shar Music', qty: 24, costPerUnit: '22.00', receivedDate: '2025-09-01', invoiceNumber: 'SHAR-2025-0901' },
{ sku: 'STR-VLN-DOM-44', supplier: 'Shar Music', qty: 12, costPerUnit: '22.50', receivedDate: '2026-01-15', invoiceNumber: 'SHAR-2026-0115' },
{ sku: 'STR-VLN-DOM-34', supplier: 'Shar Music', qty: 12, costPerUnit: '22.00', receivedDate: '2025-09-01', invoiceNumber: 'SHAR-2025-0901' },
{ sku: 'STR-VLN-PRE-44', supplier: "D'Addario", qty: 36, costPerUnit: '10.50', receivedDate: '2025-09-01', invoiceNumber: 'DAD-2025-0901' },
{ sku: 'STR-VLN-PRE-44', supplier: "D'Addario", qty: 24, costPerUnit: '11.00', receivedDate: '2026-02-01', invoiceNumber: 'DAD-2026-0201' },
{ sku: 'STR-VLA-DOM', supplier: 'Shar Music', qty: 12, costPerUnit: '32.00', receivedDate: '2025-10-01', invoiceNumber: 'SHAR-2025-1001' },
{ sku: 'STR-CLO-PRE', supplier: "D'Addario", qty: 18, costPerUnit: '22.00', receivedDate: '2025-09-01', invoiceNumber: 'DAD-2025-0901' },
{ sku: 'STR-CLO-LAR', supplier: 'Pirastro', qty: 6, costPerUnit: '58.00', receivedDate: '2025-11-01', invoiceNumber: 'PIR-2025-1101' },
{ sku: 'STR-BAS-HEL', supplier: "D'Addario", qty: 10, costPerUnit: '65.00', receivedDate: '2025-09-01', invoiceNumber: 'DAD-2025-0901' },
// Rosin
{ sku: 'RSN-HILL-L', supplier: 'Shar Music', qty: 36, costPerUnit: '5.00', receivedDate: '2025-08-15', invoiceNumber: 'SHAR-2025-0815' },
{ sku: 'RSN-HILL-D', supplier: 'Shar Music', qty: 24, costPerUnit: '5.00', receivedDate: '2025-08-15', invoiceNumber: 'SHAR-2025-0815' },
{ sku: 'RSN-PIR-GF', supplier: 'Pirastro', qty: 18, costPerUnit: '8.50', receivedDate: '2025-08-15', invoiceNumber: 'PIR-2025-0815' },
// Repair parts
{ sku: 'RPR-BRG-VLN-44', supplier: 'Shar Music', qty: 12, costPerUnit: '10.00', receivedDate: '2025-07-01', invoiceNumber: 'SHAR-2025-0701' },
{ sku: 'RPR-BRG-VLN-34', supplier: 'Shar Music', qty: 8, costPerUnit: '9.00', receivedDate: '2025-07-01', invoiceNumber: 'SHAR-2025-0701' },
{ sku: 'RPR-BRG-VLA', supplier: 'Shar Music', qty: 8, costPerUnit: '13.00', receivedDate: '2025-07-01', invoiceNumber: 'SHAR-2025-0701' },
{ sku: 'RPR-BRG-CLO', supplier: 'Shar Music', qty: 6, costPerUnit: '19.00', receivedDate: '2025-07-01', invoiceNumber: 'SHAR-2025-0701' },
{ sku: 'RPR-BRG-BAS', supplier: 'Shar Music', qty: 4, costPerUnit: '29.00', receivedDate: '2025-07-01', invoiceNumber: 'SHAR-2025-0701' },
{ sku: 'RPR-PEG-VLN-BX', supplier: 'Shar Music', qty: 15, costPerUnit: '7.00', receivedDate: '2025-07-01', invoiceNumber: 'SHAR-2025-0701' },
{ sku: 'RPR-PEG-VLA-BX', supplier: 'Shar Music', qty: 10, costPerUnit: '8.00', receivedDate: '2025-07-01', invoiceNumber: 'SHAR-2025-0701' },
{ sku: 'RPR-PEG-CLO-BX', supplier: 'Shar Music', qty: 8, costPerUnit: '10.00', receivedDate: '2025-07-01', invoiceNumber: 'SHAR-2025-0701' },
{ sku: 'RPR-HAIR-WH', supplier: 'Shar Music', qty: 12, costPerUnit: '11.00', receivedDate: '2025-10-01', invoiceNumber: 'SHAR-2025-1001' },
{ sku: 'RPR-TLP-VLN-44', supplier: 'Shar Music', qty: 10, costPerUnit: '8.50', receivedDate: '2025-09-15', invoiceNumber: 'SHAR-2025-0915' },
{ sku: 'RPR-GLU-HIDE', supplier: 'Shar Music', qty: 8, costPerUnit: '4.50', receivedDate: '2025-09-15', invoiceNumber: 'SHAR-2025-0915' },
{ sku: 'RPR-POL-HILL', supplier: 'Shar Music', qty: 18, costPerUnit: '6.00', receivedDate: '2025-09-15', invoiceNumber: 'SHAR-2025-0915', notes: 'SKU changed from MNT-POL-HILL' },
{ sku: 'MNT-POL-HILL', supplier: 'Shar Music', qty: 18, costPerUnit: '6.00', receivedDate: '2025-09-15', invoiceNumber: 'SHAR-2025-0915' },
{ sku: 'RPR-PEG-DOC', supplier: 'Shar Music', qty: 15, costPerUnit: '3.50', receivedDate: '2025-09-15', invoiceNumber: 'SHAR-2025-0915' },
// Shoulder rests
{ sku: 'SR-KUN-44', supplier: 'Shar Music', qty: 12, costPerUnit: '17.00', receivedDate: '2025-08-01', invoiceNumber: 'SHAR-2025-0801' },
{ sku: 'SR-KUN-C', supplier: 'Shar Music', qty: 8, costPerUnit: '21.00', receivedDate: '2025-08-01', invoiceNumber: 'SHAR-2025-0801' },
{ sku: 'SR-BON-44', supplier: 'Shar Music', qty: 6, costPerUnit: '26.00', receivedDate: '2025-08-01', invoiceNumber: 'SHAR-2025-0801' },
]
for (const r of receiptDefs) {
const productId = productIds[r.sku]
const supplierId = r.supplier ? supplierIds[r.supplier] : null
if (!productId) continue
const [existing] = await sql`
SELECT id FROM stock_receipt WHERE product_id = ${productId} AND received_date = ${r.receivedDate} AND cost_per_unit = ${r.costPerUnit} AND qty = ${r.qty}`
if (existing) continue
const totalCost = (parseFloat(r.costPerUnit) * r.qty).toFixed(2)
await sql`
INSERT INTO stock_receipt (product_id, supplier_id, qty, cost_per_unit, total_cost, received_date, invoice_number, notes)
VALUES (${productId}, ${supplierId ?? null}, ${r.qty}, ${r.costPerUnit}, ${totalCost}, ${r.receivedDate}, ${r.invoiceNumber ?? null}, ${r.notes ?? null})`
console.log(` Receipt: ${r.sku} qty=${r.qty} @ $${r.costPerUnit}`)
}
console.log(' Inventory seed complete.')
}
async function seedLessons(sql: any) {
console.log('\nSeeding lessons data...')
// ── Clear old non-string lesson data ──────────────────────────────────────
// Delete in dependency order so FK constraints don't block
await sql`DELETE FROM lesson_plan_item`
await sql`DELETE FROM lesson_plan_section`
await sql`DELETE FROM member_lesson_plan`
await sql`DELETE FROM lesson_plan_template_item`
await sql`DELETE FROM lesson_plan_template_section`
await sql`DELETE FROM lesson_plan_template`
await sql`DELETE FROM lesson_session`
await sql`DELETE FROM enrollment`
await sql`DELETE FROM schedule_slot`
await sql`DELETE FROM lesson_type`
await sql`DELETE FROM instructor`
console.log(' Cleared old lesson data')
// ── Grading scale ──────────────────────────────────────────────────────────
const [existingScale] = await sql`SELECT id FROM grading_scale WHERE name = 'Standard Progress'`
let scaleId: string
if (existingScale) {
scaleId = existingScale.id
} else {
const [scale] = await sql`
INSERT INTO grading_scale (name, description, is_default, is_active)
VALUES ('Standard Progress', 'Four-level scale used across all instruments', true, true)
RETURNING id`
scaleId = scale.id
const levels = [
{ value: '1', label: 'Introduced', numericValue: 1, colorHex: null, sortOrder: 1 },
{ value: '2', label: 'Developing', numericValue: 2, colorHex: '#EAB308', sortOrder: 2 },
{ value: '3', label: 'Proficient', numericValue: 3, colorHex: '#3B82F6', sortOrder: 3 },
{ value: '4', label: 'Mastered', numericValue: 4, colorHex: '#22C55E', sortOrder: 4 },
]
for (const lv of levels) {
await sql`INSERT INTO grading_scale_level (grading_scale_id, value, label, numeric_value, color_hex, sort_order)
VALUES (${scaleId}, ${lv.value}, ${lv.label}, ${lv.numericValue}, ${lv.colorHex}, ${lv.sortOrder})`
}
console.log(' Grading scale: Standard Progress')
}
// ── Instructors ────────────────────────────────────────────────────────────
const instructorDefs = [
{
displayName: 'Sarah Mitchell',
bio: 'Violin and viola instructor with 12 years of teaching experience. Suzuki-certified through Book 6. Member of the City Symphony Orchestra.',
instruments: ['Violin', 'Viola'],
},
{
displayName: 'James Carter',
bio: 'Cellist and chamber musician with a masters in performance from Eastman School of Music. Teaches all levels from beginner through pre-college.',
instruments: ['Cello'],
},
{
displayName: 'Beth Romero',
bio: 'Double bassist with 10 years of orchestral and jazz experience. Former section principal. Warm, patient teaching style for all ages.',
instruments: ['Bass'],
},
]
const instrIds: Record<string, string> = {}
for (const def of instructorDefs) {
const [existing] = await sql`SELECT id FROM instructor WHERE display_name = ${def.displayName}`
if (existing) { instrIds[def.displayName] = existing.id; continue }
const [row] = await sql`
INSERT INTO instructor (display_name, bio, instruments, is_active)
VALUES (${def.displayName}, ${def.bio}, ${def.instruments}, true)
RETURNING id`
instrIds[def.displayName] = row.id
console.log(` Instructor: ${def.displayName}`)
}
// ── Lesson types ───────────────────────────────────────────────────────────
const lessonTypeDefs = [
{ name: '30-min Private Violin', instrument: 'Violin', durationMinutes: 30, lessonFormat: 'private', rateWeekly: '35.00', rateMonthly: '120.00', rateQuarterly: '330.00' },
{ name: '45-min Private Violin', instrument: 'Violin', durationMinutes: 45, lessonFormat: 'private', rateWeekly: '50.00', rateMonthly: '175.00', rateQuarterly: '480.00' },
{ name: '30-min Private Viola', instrument: 'Viola', durationMinutes: 30, lessonFormat: 'private', rateWeekly: '35.00', rateMonthly: '120.00', rateQuarterly: '330.00' },
{ name: '30-min Private Cello', instrument: 'Cello', durationMinutes: 30, lessonFormat: 'private', rateWeekly: '35.00', rateMonthly: '120.00', rateQuarterly: '330.00' },
{ name: '45-min Private Cello', instrument: 'Cello', durationMinutes: 45, lessonFormat: 'private', rateWeekly: '50.00', rateMonthly: '175.00', rateQuarterly: '480.00' },
{ name: '30-min Private Bass', instrument: 'Bass', durationMinutes: 30, lessonFormat: 'private', rateWeekly: '35.00', rateMonthly: '120.00', rateQuarterly: '330.00' },
{ name: '60-min String Ensemble', instrument: null, durationMinutes: 60, lessonFormat: 'group', rateWeekly: null, rateMonthly: '65.00', rateQuarterly: '180.00' },
]
const ltIds: Record<string, string> = {}
for (const lt of lessonTypeDefs) {
const [existing] = await sql`SELECT id FROM lesson_type WHERE name = ${lt.name}`
if (existing) { ltIds[lt.name] = existing.id; continue }
const [row] = await sql`
INSERT INTO lesson_type (name, instrument, duration_minutes, lesson_format, rate_weekly, rate_monthly, rate_quarterly, is_active)
VALUES (${lt.name}, ${lt.instrument}, ${lt.durationMinutes}, ${lt.lessonFormat}, ${lt.rateWeekly}, ${lt.rateMonthly}, ${lt.rateQuarterly}, true)
RETURNING id`
ltIds[lt.name] = row.id
console.log(` Lesson type: ${lt.name}`)
}
// ── Schedule slots ─────────────────────────────────────────────────────────
// dayOfWeek: 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat
const slotDefs = [
// Sarah Mitchell — Violin & Viola
{ key: 'sarah_mon_1530_vln30', instructor: 'Sarah Mitchell', lessonType: '30-min Private Violin', dayOfWeek: 1, startTime: '15:30', room: 'Studio 1', maxStudents: 1, rateMonthly: null },
{ key: 'sarah_mon_1600_vln30', instructor: 'Sarah Mitchell', lessonType: '30-min Private Violin', dayOfWeek: 1, startTime: '16:00', room: 'Studio 1', maxStudents: 1, rateMonthly: null },
{ key: 'sarah_wed_1530_vln30', instructor: 'Sarah Mitchell', lessonType: '30-min Private Violin', dayOfWeek: 3, startTime: '15:30', room: 'Studio 1', maxStudents: 1, rateMonthly: null },
{ key: 'sarah_wed_1600_vln45', instructor: 'Sarah Mitchell', lessonType: '45-min Private Violin', dayOfWeek: 3, startTime: '16:00', room: 'Studio 1', maxStudents: 1, rateMonthly: '155.00' },
{ key: 'sarah_fri_1600_vla30', instructor: 'Sarah Mitchell', lessonType: '30-min Private Viola', dayOfWeek: 5, startTime: '16:00', room: 'Studio 1', maxStudents: 1, rateMonthly: null },
{ key: 'sarah_sat_1000_vln30', instructor: 'Sarah Mitchell', lessonType: '30-min Private Violin', dayOfWeek: 6, startTime: '10:00', room: 'Studio 1', maxStudents: 1, rateMonthly: null },
// James Carter — Cello
{ key: 'james_tue_1500_clo30', instructor: 'James Carter', lessonType: '30-min Private Cello', dayOfWeek: 2, startTime: '15:00', room: 'Studio 2', maxStudents: 1, rateMonthly: null },
{ key: 'james_tue_1600_clo30', instructor: 'James Carter', lessonType: '30-min Private Cello', dayOfWeek: 2, startTime: '16:00', room: 'Studio 2', maxStudents: 1, rateMonthly: null },
{ key: 'james_thu_1530_clo45', instructor: 'James Carter', lessonType: '45-min Private Cello', dayOfWeek: 4, startTime: '15:30', room: 'Studio 2', maxStudents: 1, rateMonthly: '155.00' },
{ key: 'james_sat_1000_clo30', instructor: 'James Carter', lessonType: '30-min Private Cello', dayOfWeek: 6, startTime: '10:00', room: 'Studio 2', maxStudents: 1, rateMonthly: null },
// Beth Romero — Bass
{ key: 'beth_wed_1700_bas30', instructor: 'Beth Romero', lessonType: '30-min Private Bass', dayOfWeek: 3, startTime: '17:00', room: 'Studio 3', maxStudents: 1, rateMonthly: null },
{ key: 'beth_fri_1500_bas30', instructor: 'Beth Romero', lessonType: '30-min Private Bass', dayOfWeek: 5, startTime: '15:00', room: 'Studio 3', maxStudents: 1, rateMonthly: null },
{ key: 'beth_sat_1100_bas30', instructor: 'Beth Romero', lessonType: '30-min Private Bass', dayOfWeek: 6, startTime: '11:00', room: 'Studio 3', maxStudents: 1, rateMonthly: null },
// String Ensemble — Saturdays (all three instructors rotate supervision; assign to Sarah)
{ key: 'ensemble_sat_1300', instructor: 'Sarah Mitchell', lessonType: '60-min String Ensemble', dayOfWeek: 6, startTime: '13:00', room: 'Main Hall', maxStudents: 12, rateMonthly: null },
]
const slotIds: Record<string, string> = {}
for (const s of slotDefs) {
const instrId = instrIds[s.instructor]
const ltId = ltIds[s.lessonType]
const [existing] = await sql`
SELECT id FROM schedule_slot
WHERE instructor_id = ${instrId} AND day_of_week = ${s.dayOfWeek} AND start_time = ${s.startTime + ':00'} AND is_active = true`
if (existing) { slotIds[s.key] = existing.id; continue }
const [row] = await sql`
INSERT INTO schedule_slot (instructor_id, lesson_type_id, day_of_week, start_time, room, max_students, rate_monthly, is_active)
VALUES (${instrId}, ${ltId}, ${s.dayOfWeek}, ${s.startTime}, ${s.room}, ${s.maxStudents}, ${s.rateMonthly}, true)
RETURNING id`
slotIds[s.key] = row.id
console.log(` Slot: ${s.instructor}${s.lessonType} (${['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][s.dayOfWeek]} ${s.startTime})`)
}
// ── Look up member IDs ─────────────────────────────────────────────────────
const memberRows = await sql`SELECT m.id, m.first_name, m.last_name, m.account_id FROM member m`
const memberMap: Record<string, { id: string; accountId: string }> = {}
for (const m of memberRows) memberMap[`${m.first_name} ${m.last_name}`] = { id: m.id, accountId: m.account_id }
// ── Enrollments ────────────────────────────────────────────────────────────
const enrollmentDefs = [
{
member: 'Tommy Smith', slotKey: 'sarah_mon_1530_vln30', startDate: '2026-01-05',
rate: '120.00', billingInterval: 1, billingUnit: 'month',
notes: 'Tommy is 8 years old, complete beginner. Using a 1/2 size rental. Parents prefer Monday after school.',
},
{
member: 'Jake Johnson', slotKey: 'sarah_wed_1530_vln30', startDate: '2026-01-07',
rate: '120.00', billingInterval: 1, billingUnit: 'month',
notes: 'Jake (age 10) started Suzuki Book 1 at school. Moving to Book 2. Uses a 3/4 rental.',
},
{
member: 'Emily Chen', slotKey: 'sarah_fri_1600_vla30', startDate: '2026-01-09',
rate: '120.00', billingInterval: 1, billingUnit: 'month',
notes: 'Emily played violin through high school, now switching to viola. Strong reader, working on tone and shifting.',
},
{
member: 'Mike Thompson', slotKey: 'james_sat_1000_clo30', startDate: '2026-02-01',
rate: '120.00', billingInterval: 1, billingUnit: 'month',
notes: 'Mike is an adult beginner on cello. Very motivated. Renting a full-size cello.',
},
{
member: 'Carlos Garcia', slotKey: 'beth_fri_1500_bas30', startDate: '2026-01-09',
rate: '120.00', billingInterval: 1, billingUnit: 'month',
notes: 'Carlos played bass in high school orchestra. Returning student — solid fundamentals, working on thumb position.',
},
]
const enrollmentIds: Record<string, string> = {}
for (const e of enrollmentDefs) {
const m = memberMap[e.member]
if (!m) { console.log(` ⚠ Member not found: ${e.member} — skipping enrollment`); continue }
const slotId = slotIds[e.slotKey]
if (!slotId) { console.log(` ⚠ Slot not found: ${e.slotKey} — skipping enrollment`); continue }
const [slot] = await sql`SELECT instructor_id FROM schedule_slot WHERE id = ${slotId}`
const [existing] = await sql`
SELECT id FROM enrollment WHERE member_id = ${m.id} AND schedule_slot_id = ${slotId}`
if (existing) { enrollmentIds[e.member] = existing.id; continue }
const [row] = await sql`
INSERT INTO enrollment (member_id, account_id, schedule_slot_id, instructor_id, status, start_date, rate, billing_interval, billing_unit, notes)
VALUES (${m.id}, ${m.accountId}, ${slotId}, ${slot.instructor_id}, 'active', ${e.startDate}, ${e.rate}, ${e.billingInterval}, ${e.billingUnit}, ${e.notes})
RETURNING id`
enrollmentIds[e.member] = row.id
console.log(` Enrollment: ${e.member}`)
}
// ── Lesson sessions ────────────────────────────────────────────────────────
type SessionEntry = [string, string, string?]
interface SessionSet { enrollmentKey: string; slotKey: string; entries: SessionEntry[] }
const sessionSets: SessionSet[] = [
{
enrollmentKey: 'Tommy Smith',
slotKey: 'sarah_mon_1530_vln30',
entries: [
['2026-01-05', 'attended', 'First lesson. Introduced bow hold and open string bowing. Tommy is enthusiastic!'],
['2026-01-12', 'attended'],
['2026-01-19', 'attended', 'Bow arm improving. Introduced left hand finger placement — E and A strings.'],
['2026-01-26', 'missed'],
['2026-02-02', 'attended'],
['2026-02-09', 'attended', 'Playing Twinkle Variation A slowly with good intonation. Bow hold still needs reminders.'],
['2026-02-16', 'attended'],
['2026-02-23', 'attended'],
['2026-03-02', 'attended', 'Tommy played Twinkle Theme hands-down with confidence — great milestone!'],
['2026-03-09', 'attended'],
['2026-03-16', 'attended', 'Introduced Lightly Row. Working on smooth bow changes.'],
['2026-03-23', 'attended'],
['2026-03-30', 'scheduled'],
['2026-04-06', 'scheduled'],
['2026-04-13', 'scheduled'],
],
},
{
enrollmentKey: 'Jake Johnson',
slotKey: 'sarah_wed_1530_vln30',
entries: [
['2026-01-07', 'attended', 'Assessment: solid Book 1 foundation. Starting Book 2 — Chorus from Judas Maccabeus.'],
['2026-01-14', 'attended'],
['2026-01-21', 'attended', 'Shifting to 3rd position introduced. Intonation on D string needs focus.'],
['2026-01-28', 'attended'],
['2026-02-04', 'missed'],
['2026-02-11', 'attended', 'Third position becoming more reliable. Vibrato exercises started.'],
['2026-02-18', 'attended'],
['2026-02-25', 'attended'],
['2026-03-04', 'attended', 'Chorus from Judas Maccabeus up to performance tempo. Vibrato emerging nicely.'],
['2026-03-11', 'attended'],
['2026-03-18', 'attended', 'Jake performed the Chorus at slow recital tempo — very clean. Starting Hunters\' Chorus.'],
['2026-03-25', 'attended'],
['2026-04-01', 'scheduled'],
['2026-04-08', 'scheduled'],
['2026-04-15', 'scheduled'],
],
},
{
enrollmentKey: 'Emily Chen',
slotKey: 'sarah_fri_1600_vla30',
entries: [
['2026-01-09', 'attended', 'Assessment: violin background is strong. Adjusted chin rest and shoulder rest for viola. Alto clef introduced.'],
['2026-01-16', 'attended'],
['2026-01-23', 'attended', 'Alto clef reading improving quickly. Tone production on C string needs work — bowing angle adjustment.'],
['2026-01-30', 'attended'],
['2026-02-06', 'attended', 'Significantly richer tone on low strings. Started Telemann Viola Concerto in G major, 1st mvt.'],
['2026-02-13', 'attended'],
['2026-02-20', 'missed'],
['2026-02-27', 'attended'],
['2026-03-06', 'attended', 'Telemann 1st movement up to tempo. Emily\'s vibrato really coming through on viola — beautiful sound.'],
['2026-03-13', 'attended'],
['2026-03-20', 'attended', 'Starting 2nd movement. Discussing potential solo performance at spring recital.'],
['2026-03-27', 'attended'],
['2026-04-03', 'scheduled'],
['2026-04-10', 'scheduled'],
['2026-04-17', 'scheduled'],
],
},
{
enrollmentKey: 'Mike Thompson',
slotKey: 'james_sat_1000_clo30',
entries: [
['2026-02-01', 'attended', 'First lesson — proper sitting posture, bow hold, open string bowing. Mike is a quick learner.'],
['2026-02-08', 'attended'],
['2026-02-15', 'attended', 'Left hand frame introduced. Thumb position on neck. Playing first simple melodies.'],
['2026-02-22', 'attended'],
['2026-03-01', 'attended', 'Bass clef reading going well. Playing "Lightly Row" in D major. Intonation on A string improving.'],
['2026-03-08', 'attended'],
['2026-03-15', 'attended', 'Mike nailed the first cello piece from Mooney\'s "Position Pieces." Really motivated — practices daily.'],
['2026-03-22', 'attended'],
['2026-03-29', 'attended', 'Starting Suzuki Cello Book 1 formally. Tone on D and A strings sounding great.'],
['2026-04-05', 'scheduled'],
['2026-04-12', 'scheduled'],
['2026-04-19', 'scheduled'],
],
},
{
enrollmentKey: 'Carlos Garcia',
slotKey: 'beth_fri_1500_bas30',
entries: [
['2026-01-09', 'attended', 'Assessment — Carlos has solid orchestra foundation. Reviewing shifting, extensions, and bow distribution.'],
['2026-01-16', 'attended'],
['2026-01-23', 'attended', 'Thumb position work started. Carlos adapting well — good ear for intonation.'],
['2026-01-30', 'attended'],
['2026-02-06', 'attended', 'Working through Simandl Book 1 exercises in thumb position. Stamina building.'],
['2026-02-13', 'attended'],
['2026-02-20', 'attended', 'Thumb position solid up to 4th. Starting to work on Dragonetti Concerto opening.'],
['2026-02-27', 'attended'],
['2026-03-06', 'attended', 'Dragonetti opening phrase sounding musical. Bow arm and tone in upper positions much improved.'],
['2026-03-13', 'missed'],
['2026-03-20', 'attended'],
['2026-03-27', 'attended', 'Carlos brought in a jazz bassline he transcribed from Ray Brown — spent half the lesson on groove and feel. Excellent initiative.'],
['2026-04-03', 'scheduled'],
['2026-04-10', 'scheduled'],
['2026-04-17', 'scheduled'],
],
},
]
for (const ss of sessionSets) {
const enrollmentId = enrollmentIds[ss.enrollmentKey]
if (!enrollmentId) continue
const [slot] = await sql`SELECT start_time FROM schedule_slot WHERE id = ${slotIds[ss.slotKey]}`
const scheduledTime = slot.start_time
for (const [date, status, instructorNotes] of ss.entries) {
const [existing] = await sql`
SELECT id FROM lesson_session WHERE enrollment_id = ${enrollmentId} AND scheduled_date = ${date}`
if (existing) continue
await sql`
INSERT INTO lesson_session (enrollment_id, scheduled_date, scheduled_time, status, instructor_notes, notes_completed_at)
VALUES (
${enrollmentId}, ${date}, ${scheduledTime}, ${status},
${instructorNotes ?? null},
${instructorNotes && status !== 'scheduled' ? new Date(date + 'T20:00:00Z').toISOString() : null}
)`
}
console.log(` Sessions: ${ss.enrollmentKey} (${ss.entries.length})`)
}
// ── Lesson plan template — Beginner Violin (Suzuki Book 1) ─────────────────
const [existingTemplate] = await sql`SELECT id FROM lesson_plan_template WHERE name = 'Beginner Violin — Suzuki Book 1'`
if (!existingTemplate) {
const [tmpl] = await sql`
INSERT INTO lesson_plan_template (name, description, instrument, skill_level, is_active)
VALUES (
'Beginner Violin — Suzuki Book 1',
'Core skills for first-year violin students following the Suzuki method — from bow hold and posture through the full Book 1 repertoire.',
'Violin', 'beginner', true
) RETURNING id`
const sections = [
{
title: 'Posture & Setup',
sortOrder: 0,
items: [
{ title: 'Proper standing/sitting posture', sortOrder: 0 },
{ title: 'Violin hold and chin rest position', sortOrder: 1 },
{ title: 'Shoulder rest adjustment', sortOrder: 2 },
{ title: 'Bow hold (Suzuki style)', sortOrder: 3 },
{ title: 'Relaxed arm weight into string', sortOrder: 4 },
],
},
{
title: 'Bowing Technique',
sortOrder: 1,
items: [
{ title: 'Open string bowing — all four strings', sortOrder: 0 },
{ title: 'Straight bow path', sortOrder: 1 },
{ title: 'Smooth bow changes at frog and tip', sortOrder: 2 },
{ title: 'Detaché stroke', sortOrder: 3 },
{ title: 'Staccato introduction', sortOrder: 4 },
],
},
{
title: 'Left Hand & Intonation',
sortOrder: 2,
items: [
{ title: 'Finger placement — E and A strings', sortOrder: 0 },
{ title: 'Finger placement — D and G strings', sortOrder: 1 },
{ title: 'First finger frame', sortOrder: 2 },
{ title: 'Low 2nd finger (B♭, E♭)', sortOrder: 3 },
{ title: 'High 3rd finger', sortOrder: 4 },
],
},
{
title: 'Suzuki Book 1 Repertoire',
sortOrder: 3,
items: [
{ title: 'Twinkle Twinkle — Variation A', sortOrder: 0 },
{ title: 'Twinkle Twinkle — Theme', sortOrder: 1 },
{ title: 'Lightly Row', sortOrder: 2 },
{ title: 'Song of the Wind', sortOrder: 3 },
{ title: 'Go Tell Aunt Rhody', sortOrder: 4 },
{ title: 'O Come Little Children', sortOrder: 5 },
{ title: 'May Song', sortOrder: 6 },
{ title: 'Long, Long Ago', sortOrder: 7 },
{ title: 'Allegro (Suzuki)', sortOrder: 8 },
{ title: 'Perpetual Motion', sortOrder: 9 },
{ title: 'Allegretto (Suzuki)', sortOrder: 10 },
],
},
]
for (const sec of sections) {
const [s] = await sql`
INSERT INTO lesson_plan_template_section (template_id, title, sort_order)
VALUES (${tmpl.id}, ${sec.title}, ${sec.sortOrder})
RETURNING id`
for (const item of sec.items) {
await sql`
INSERT INTO lesson_plan_template_item (section_id, title, grading_scale_id, sort_order)
VALUES (${s.id}, ${item.title}, ${scaleId}, ${item.sortOrder})`
}
}
console.log(' Template: Beginner Violin — Suzuki Book 1')
}
// ── Lesson plan for Tommy Smith (violin beginner) ──────────────────────────
const tommyEnrollmentId = enrollmentIds['Tommy Smith']
const tommyMember = memberMap['Tommy Smith']
if (tommyEnrollmentId && tommyMember) {
const [existingPlan] = await sql`SELECT id FROM member_lesson_plan WHERE enrollment_id = ${tommyEnrollmentId} AND is_active = true`
if (!existingPlan) {
const [plan] = await sql`
INSERT INTO member_lesson_plan (member_id, enrollment_id, title, description, is_active, started_date)
VALUES (${tommyMember.id}, ${tommyEnrollmentId}, 'Violin Suzuki Book 1 — Tommy Smith', 'Working through Suzuki Book 1.', true, '2026-01-05')
RETURNING id`
const [sec1] = await sql`INSERT INTO lesson_plan_section (lesson_plan_id, title, sort_order) VALUES (${plan.id}, 'Posture & Setup', 0) RETURNING id`
for (const it of [
{ title: 'Proper standing/sitting posture', status: 'mastered', sortOrder: 0 },
{ title: 'Violin hold and chin rest position', status: 'mastered', sortOrder: 1 },
{ title: 'Shoulder rest adjustment', status: 'mastered', sortOrder: 2 },
{ title: 'Bow hold (Suzuki style)', status: 'in_progress', sortOrder: 3 },
{ title: 'Relaxed arm weight into string', status: 'in_progress', sortOrder: 4 },
]) {
await sql`INSERT INTO lesson_plan_item (section_id, title, status, grading_scale_id, current_grade_value, sort_order)
VALUES (${sec1.id}, ${it.title}, ${it.status}, ${scaleId}, ${it.status === 'mastered' ? '4' : it.status === 'in_progress' ? '2' : null}, ${it.sortOrder})`
}
const [sec2] = await sql`INSERT INTO lesson_plan_section (lesson_plan_id, title, sort_order) VALUES (${plan.id}, 'Bowing Technique', 1) RETURNING id`
for (const it of [
{ title: 'Open string bowing — all four strings', status: 'mastered', sortOrder: 0 },
{ title: 'Straight bow path', status: 'in_progress', sortOrder: 1 },
{ title: 'Smooth bow changes at frog and tip', status: 'in_progress', sortOrder: 2 },
{ title: 'Detaché stroke', status: 'not_started', sortOrder: 3 },
{ title: 'Staccato introduction', status: 'not_started', sortOrder: 4 },
]) {
await sql`INSERT INTO lesson_plan_item (section_id, title, status, grading_scale_id, current_grade_value, sort_order)
VALUES (${sec2.id}, ${it.title}, ${it.status}, ${scaleId}, ${it.status === 'mastered' ? '4' : it.status === 'in_progress' ? '2' : null}, ${it.sortOrder})`
}
const [sec3] = await sql`INSERT INTO lesson_plan_section (lesson_plan_id, title, sort_order) VALUES (${plan.id}, 'Suzuki Book 1 Repertoire', 2) RETURNING id`
for (const it of [
{ title: 'Twinkle Twinkle — Variation A', status: 'mastered', sortOrder: 0 },
{ title: 'Twinkle Twinkle — Theme', status: 'in_progress', sortOrder: 1 },
{ title: 'Lightly Row', status: 'in_progress', sortOrder: 2 },
{ title: 'Song of the Wind', status: 'not_started', sortOrder: 3 },
{ title: 'Go Tell Aunt Rhody', status: 'not_started', sortOrder: 4 },
]) {
await sql`INSERT INTO lesson_plan_item (section_id, title, status, grading_scale_id, current_grade_value, sort_order)
VALUES (${sec3.id}, ${it.title}, ${it.status}, ${scaleId}, ${it.status === 'mastered' ? '4' : it.status === 'in_progress' ? '2' : null}, ${it.sortOrder})`
}
console.log(' Lesson plan: Tommy Smith — Violin Suzuki Book 1')
}
}
console.log('Lessons seed complete.')
}
seed().catch((err) => {
console.error('Seed failed:', err)
process.exit(1)
})