You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
108 lines
7.4 KiB
TypeScript
108 lines
7.4 KiB
TypeScript
/**
|
|
* Demo seed — populates a fresh HQ_DATA_DIR with a realistic SiMS dataset so the
|
|
* app can be shown with live screens. Run: HQ_DATA_DIR=<dir> npx tsx scripts/demo-seed.ts
|
|
* Not shipped; a presentation helper.
|
|
*/
|
|
import { openDb } from '../src/db'
|
|
import { seedIfEmpty } from '../src/seed'
|
|
import { createStaff } from '../src/auth'
|
|
import { setSetting } from '../src/repos-reminders'
|
|
import { createClient } from '../src/repos-clients'
|
|
import { createModule, setPrice, assignModule } from '../src/repos-modules'
|
|
import { createDraft, issueDocument, markStatus } from '../src/repos-documents'
|
|
import { recordPayment } from '../src/repos-payments'
|
|
import { mintShare } from '../src/repos-shares'
|
|
|
|
void (async () => {
|
|
const db = openDb(process.env['HQ_DATA_DIR'])
|
|
|
|
// Known owner so we can log in for screenshots (before seedIfEmpty, so it skips its own).
|
|
await createStaff(db, { email: 'owner@sims.com', displayName: 'Thomas (Owner)', role: 'owner', password: 'demo-owner-2026' })
|
|
await seedIfEmpty(db)
|
|
const U = 'demo'
|
|
|
|
// A simple monogram logo as an SVG data URI (renders in <img> and puppeteer).
|
|
const LOGO = 'data:image/svg+xml;base64,' + Buffer.from(
|
|
`<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120"><rect width="120" height="120" rx="20" fill="#1e3a5f"/><text x="60" y="78" font-family="Segoe UI,Arial" font-size="54" font-weight="700" fill="#ffffff" text-anchor="middle">TS</text></svg>`,
|
|
).toString('base64')
|
|
|
|
// Company + template letterhead data (what the Document Template page edits).
|
|
const settings: Record<string, string> = {
|
|
'company.name': 'SiMS Solutions Pvt Ltd',
|
|
'company.address': '2nd Floor, Cyber Park, Kakkanad, Kochi, Kerala 682030',
|
|
'company.gstin': '32ABCDE1234F1Z9',
|
|
'company.state_code': '32',
|
|
'company.phone': '+91 98470 12345',
|
|
'company.email': 'accounts@sims.com',
|
|
'company.bank': 'HDFC Bank · A/c 50200012345678 · IFSC HDFC0001234 · Kakkanad',
|
|
'template.accent': '#1e3a5f',
|
|
'template.logo': LOGO,
|
|
'template.terms': '1. Prices are valid for 15 days from the date of this quotation.\n2. 50% advance on confirmation, balance before go-live.\n3. Annual subscription renews on the anniversary of activation.',
|
|
'template.declaration': 'We declare that this invoice shows the actual price of the services described and that all particulars are true and correct.',
|
|
'template.jurisdiction': 'Subject to Ernakulam jurisdiction',
|
|
'template.footer_note': 'Thank you for your business. For support: support@sims.com · +91 98470 12345',
|
|
'template.signatory_label': 'For SiMS Solutions Pvt Ltd',
|
|
}
|
|
for (const [k, v] of Object.entries(settings)) await setSetting(db, U, k, v)
|
|
|
|
// Clients — one out-of-state (Karnataka, 29) to show IGST; one lead.
|
|
const malabar = (await createClient(db, U, { name: 'Malabar Supermarket', stateCode: '32', address: 'MG Road, Kozhikode, Kerala 673001', status: 'active', contacts: [{ name: 'Rajesh Nair', phone: '9847011111', email: 'rajesh@malabarsuper.in' }] })).id
|
|
const lulu = (await createClient(db, U, { name: 'Lulu Hyper Kochi', stateCode: '32', address: 'Edappally, Kochi, Kerala 682024', status: 'active', contacts: [{ name: 'Fathima K', phone: '9847022222', email: 'it@lulukochi.in' }] })).id
|
|
const blr = (await createClient(db, U, { name: 'Bengaluru Fresh Mart', stateCode: '29', address: 'Indiranagar, Bengaluru, Karnataka 560038', status: 'active', contacts: [{ name: 'Anil Kumar', phone: '9880033333', email: 'anil@blrfresh.in' }] })).id
|
|
await createClient(db, U, { name: 'Green Valley Stores', stateCode: '32', address: 'Thrissur, Kerala', status: 'lead', contacts: [{ name: 'Suresh', phone: '9847044444' }] })
|
|
|
|
// Modules + dated prices (GST-exclusive; the engine adds 18%).
|
|
const pos = (await createModule(db, U, { code: 'POS', name: 'POS Billing', sac: '998314', allowedKinds: ['yearly', 'one_time'], quoteContent: ['Unlimited counters & cashiers', 'Offline billing with auto-sync', 'GST invoices + thermal print', 'Barcode & weighing-scale support'] })).id
|
|
const inv = (await createModule(db, U, { code: 'INV', name: 'Inventory & GST Filing', sac: '998314', allowedKinds: ['yearly'], quoteContent: ['Stock, purchases, suppliers', 'GSTR-1 / 3B ready exports', 'Expiry & reorder alerts'] })).id
|
|
const cloud = (await createModule(db, U, { code: 'CLOUD', name: 'Cloud Sync & Backup', sac: '998315', allowedKinds: ['monthly', 'yearly'], multiSubscription: true, quoteContent: ['Real-time multi-store sync', 'Nightly encrypted cloud backup'] })).id
|
|
const sms = (await createModule(db, U, { code: 'SMS', name: 'SMS Pack', sac: '998414', allowedKinds: ['one_time'], quoteContent: ['Bill alerts & payment reminders to customers', 'Sender ID + DLT registration included'] })).id
|
|
|
|
const FROM = '2026-04-01'
|
|
await setPrice(db, U, { moduleId: pos, kind: 'yearly', pricePaise: 18_000_00, effectiveFrom: FROM })
|
|
await setPrice(db, U, { moduleId: pos, kind: 'one_time', pricePaise: 25_000_00, effectiveFrom: FROM })
|
|
await setPrice(db, U, { moduleId: inv, kind: 'yearly', pricePaise: 12_000_00, effectiveFrom: FROM })
|
|
await setPrice(db, U, { moduleId: cloud, kind: 'monthly', pricePaise: 1_500_00, effectiveFrom: FROM })
|
|
await setPrice(db, U, { moduleId: sms, edition: 'SMS-50K', kind: 'one_time', pricePaise: 4_000_00, effectiveFrom: FROM })
|
|
await setPrice(db, U, { moduleId: sms, edition: 'SMS-1L', kind: 'one_time', pricePaise: 7_000_00, effectiveFrom: FROM })
|
|
|
|
// Client 360 modules
|
|
await assignModule(db, U, { clientId: malabar, moduleId: pos, kind: 'yearly' })
|
|
await assignModule(db, U, { clientId: malabar, moduleId: inv, kind: 'yearly' })
|
|
|
|
// A quotation for Malabar (POS yearly + Inventory + SMS 1-Lakh pack) → issued + share link.
|
|
const qt = await createDraft(db, U, {
|
|
docType: 'QUOTATION', clientId: malabar,
|
|
lines: [
|
|
{ moduleId: pos, qty: 1, kind: 'yearly' },
|
|
{ moduleId: inv, qty: 1, kind: 'yearly' },
|
|
{ moduleId: sms, qty: 1, kind: 'one_time', edition: 'SMS-1L' },
|
|
],
|
|
terms: 'Prices valid for 15 days. 50% advance on confirmation.',
|
|
})
|
|
const qtIssued = await issueDocument(db, U, qt.id)
|
|
await markStatus(db, U, qtIssued.id, 'sent')
|
|
await mintShare(db, U, qtIssued.id, { expiresDays: 30 })
|
|
|
|
// An invoice for Lulu (POS one-time + AMC) → issued, part-paid (drives the dashboard).
|
|
const amcMod = ((await db.get<{ id: string }>(`SELECT id FROM module WHERE code='AMC'`))!).id
|
|
await setPrice(db, U, { moduleId: amcMod, kind: 'yearly', pricePaise: 6_000_00, effectiveFrom: FROM })
|
|
const inv1 = await issueDocument(db, U, (await createDraft(db, U, {
|
|
docType: 'INVOICE', clientId: lulu,
|
|
lines: [{ moduleId: pos, qty: 1, kind: 'one_time' }, { moduleId: amcMod, qty: 1, kind: 'yearly' }],
|
|
})).id)
|
|
await recordPayment(db, U, { clientId: lulu, receivedOn: '2026-07-05', mode: 'bank', reference: 'NEFT-88213', amountPaise: 20_000_00 })
|
|
|
|
// An out-of-state quotation (IGST) for Bengaluru.
|
|
await issueDocument(db, U, (await createDraft(db, U, {
|
|
docType: 'QUOTATION', clientId: blr,
|
|
lines: [{ moduleId: pos, qty: 1, kind: 'yearly' }, { moduleId: cloud, qty: 12, kind: 'monthly' }],
|
|
})).id)
|
|
|
|
const shareToken = ((await db.get<{ token: string }>(`SELECT token FROM document_share LIMIT 1`))!).token
|
|
console.log('DEMO_READY')
|
|
console.log('login: owner@sims.com / demo-owner-2026')
|
|
console.log('quotation_id:', qtIssued.id, 'doc_no:', qtIssued.docNo)
|
|
console.log('invoice_id:', inv1.id, 'doc_no:', inv1.docNo)
|
|
console.log('share_token:', shareToken)
|
|
})()
|