From d7ab370bb5f699b0b1f69055558dd564d18920dd Mon Sep 17 00:00:00 2001 From: kevin-asprec Date: Mon, 4 May 2026 14:55:55 +0800 Subject: [PATCH] chore: sync seed with overdue invoices for new signups --- packages/db/prisma/seed.ts | 324 +++++++++++++++++++++++++++---------- 1 file changed, 236 insertions(+), 88 deletions(-) diff --git a/packages/db/prisma/seed.ts b/packages/db/prisma/seed.ts index 86b3d81..96d84d9 100644 --- a/packages/db/prisma/seed.ts +++ b/packages/db/prisma/seed.ts @@ -250,6 +250,12 @@ async function main() { { first: 'Ricardo', last: 'Flores', phone: '09291234567', email: null, address: '55 Katipunan Rd, San Isidro', area: 2, plan: 2, type: 'postpaid', latOff: 0.001, lngOff: -0.002 }, { first: 'Teresa', last: 'Navarro', phone: '09301234567', email: 'teresa@email.com', address: '66 Makabayan St, Riverside', area: 3, plan: 1, type: 'prepaid', latOff: -0.003, lngOff: 0.001 }, { first: 'Fernando', last: 'Castillo', phone: '09311234567', email: null, address: '77 Silang Blvd, Hilltop', area: 4, plan: 2, type: 'postpaid', latOff: 0.002, lngOff: -0.001 }, + // New signups — pending installation (no lat/lng, pending status, open tickets) + { first: 'Rafael', last: 'Dimaculangan', phone: '09321234567', email: null, address: '88 Burgos St, Centro', area: 0, plan: 1, type: 'postpaid', latOff: 0.0015, lngOff: -0.001 }, + { first: 'Lorna', last: 'Perez', phone: '09331234567', email: 'lorna@email.com', address: '99 Villareal St, Poblacion', area: 1, plan: 2, type: 'postpaid', latOff: -0.001, lngOff: 0.002 }, + { first: 'Danilo', last: 'Rivera', phone: '09341234567', email: null, address: '101 Kapitan St, San Isidro', area: 2, plan: 0, type: 'prepaid', latOff: 0.002, lngOff: 0.001 }, + { first: 'Grace', last: 'Sison', phone: '09351234567', email: 'grace@email.com', address: '202 Magdalena St, Riverside', area: 3, plan: 3, type: 'postpaid', latOff: -0.001, lngOff: -0.002 }, + { first: 'Allan', last: 'Vergara', phone: '09361234567', email: null, address: '303 Gomez St, Hilltop', area: 4, plan: 2, type: 'postpaid', latOff: 0.001, lngOff: 0.0015 }, ]; const clients: any[] = []; @@ -260,6 +266,7 @@ async function main() { const c = clientDefs[i]; const accountNumber = `C-${String(i + 1).padStart(6, '0')}`; const plan = plans[c.plan]; + const isNewSignup = i >= 15; // last 5 are new signups pending installation const client = await prisma.client.create({ data: { @@ -271,108 +278,202 @@ async function main() { email: c.email, address: c.address, areaId: areas[c.area].id, - latitude: areaCoords[c.area][0] + c.latOff, - longitude: areaCoords[c.area][1] + c.lngOff, + status: isNewSignup ? 'pending' : 'active', + latitude: isNewSignup ? null : (areaCoords[c.area][0] + c.latOff), + longitude: isNewSignup ? null : (areaCoords[c.area][1] + c.lngOff), }, }); clients.push(client); - // Create subscription - const installedAt = new Date(now); - installedAt.setDate(installedAt.getDate() - (30 + Math.floor(Math.random() * 60))); // 30-90 days ago + if (isNewSignup) { + // ── New signup: pending subscription + open installation ticket ── - const activatedAt = new Date(installedAt); - activatedAt.setDate(activatedAt.getDate() + 2); + await prisma.subscription.create({ + data: { + tenantId: tenant.id, + clientId: client.id, + planId: plan.id, + type: c.type, + status: 'pending', + }, + }); - await prisma.subscription.create({ - data: { - tenantId: tenant.id, - clientId: client.id, - planId: plan.id, - type: c.type, - status: 'active', - installedAt, - activatedAt, - startDate: activatedAt, - }, - }); + // Open installation ticket (alternating assigned/unassigned) + const assignedTech = i % 2 === 0 ? users.technician.id : null; + await prisma.ticket.create({ + data: { + tenantId: tenant.id, + clientId: client.id, + createdById: users.tenant_admin.id, + assigneeId: assignedTech, + type: 'installation', + title: `Installation for ${c.first} ${c.last}`, + description: `New installation at ${c.address}`, + status: assignedTech ? 'in_progress' : 'open', + priority: 'high', + }, + }); - // Create resolved installation + activation tickets - await prisma.ticket.create({ - data: { - tenantId: tenant.id, - clientId: client.id, - createdById: users.tenant_admin.id, - assigneeId: users.technician.id, - type: 'installation', - title: `Installation for ${c.first} ${c.last}`, - description: `Installation at ${c.address}`, - status: 'resolved', - priority: 'high', - resolvedAt: new Date(installedAt.getTime() + 86400000), - }, - }); - - await prisma.ticket.create({ - data: { - tenantId: tenant.id, - clientId: client.id, - createdById: users.tenant_admin.id, - assigneeId: users.technician.id, - type: 'activation', - title: `Activation for ${c.first} ${c.last}`, - status: 'resolved', - priority: 'high', - resolvedAt: activatedAt, - }, - }); - - // Create 2 invoices per client - for (let m = 0; m < 2; m++) { + // Overdue invoice for new signup (installation fee / first billing) invoiceCount++; - const periodStart = new Date(activatedAt); - periodStart.setMonth(periodStart.getMonth() + m); - const periodEnd = new Date(periodStart); - periodEnd.setDate(periodEnd.getDate() + 30); - const dueDate = new Date(periodStart); - dueDate.setDate(dueDate.getDate() + 15); - - const isPaid = m === 0 || Math.random() > 0.4; // First invoice always paid, second 60% chance - const balance = isPaid ? 0 : Number(plan.price); - - const invoice = await prisma.invoice.create({ + const overdueDate = new Date(now); + overdueDate.setDate(overdueDate.getDate() - 7); + await prisma.invoice.create({ data: { tenantId: tenant.id, clientId: client.id, number: `INV-${String(invoiceCount).padStart(6, '0')}`, - amount: plan.price, - balance, - status: isPaid ? 'paid' : (dueDate < now ? 'overdue' : 'sent'), - dueDate, - paidAt: isPaid ? new Date(dueDate.getTime() - 86400000 * 3) : null, - periodStart, - periodEnd, + amount: plan ? Number(plan.price) : 999, + balance: plan ? Number(plan.price) : 999, + status: 'overdue', + dueDate: overdueDate, + periodStart: new Date(now.getTime() - 30 * 86400000), + periodEnd: new Date(now.getTime()), + }, + }); + } else { + // ── Existing client: active subscription, resolved tickets, invoices ── + + // Create subscription + const installedAt = new Date(now); + installedAt.setDate(installedAt.getDate() - (30 + Math.floor(Math.random() * 60))); // 30-90 days ago + + const activatedAt = new Date(installedAt); + activatedAt.setDate(activatedAt.getDate() + 2); + + await prisma.subscription.create({ + data: { + tenantId: tenant.id, + clientId: client.id, + planId: plan.id, + type: c.type, + status: 'active', + installedAt, + activatedAt, + startDate: activatedAt, }, }); - // Create payment for paid invoices - if (isPaid) { - const methods = ['gcash', 'maya', 'cash', 'bank_transfer']; - const method = methods[Math.floor(Math.random() * methods.length)]; - const paidDate = new Date(dueDate.getTime() - 86400000 * Math.floor(Math.random() * 5)); + // Create resolved installation + activation tickets + await prisma.ticket.create({ + data: { + tenantId: tenant.id, + clientId: client.id, + createdById: users.tenant_admin.id, + assigneeId: users.technician.id, + type: 'installation', + title: `Installation for ${c.first} ${c.last}`, + description: `Installation at ${c.address}`, + status: 'resolved', + priority: 'high', + resolvedAt: new Date(installedAt.getTime() + 86400000), + }, + }); - await prisma.payment.create({ + await prisma.ticket.create({ + data: { + tenantId: tenant.id, + clientId: client.id, + createdById: users.tenant_admin.id, + assigneeId: users.technician.id, + type: 'activation', + title: `Activation for ${c.first} ${c.last}`, + status: 'resolved', + priority: 'high', + resolvedAt: activatedAt, + }, + }); + + // Create invoices per client (3-4 per client with varied statuses) + for (let m = 0; m < 4; m++) { + invoiceCount++; + const periodStart = new Date(activatedAt); + periodStart.setMonth(periodStart.getMonth() + m); + const periodEnd = new Date(periodStart); + periodEnd.setDate(periodEnd.getDate() + 30); + const dueDate = new Date(periodStart); + dueDate.setDate(dueDate.getDate() + 15); + + // Determine invoice status based on month + let status: string; + let balance: number; + let paidAt: Date | null = null; + const amount = Number(plan.price); + + if (m === 0) { + // Month 1: always paid + status = 'paid'; + balance = 0; + paidAt = new Date(dueDate.getTime() - 86400000 * 3); + } else if (m === 1) { + // Month 2: overdue (unpaid, past due) + status = 'overdue'; + balance = amount; + } else if (m === 2) { + // Month 3: 50% paid → partial + status = 'partial'; + balance = Math.round(amount / 2); + } else { + // Month 4: upcoming (due in near future) + const futureDue = new Date(now); + futureDue.setDate(futureDue.getDate() + 3); + status = 'sent'; + balance = amount; + dueDate.setTime(futureDue.getTime()); + } + + const invoice = await prisma.invoice.create({ data: { tenantId: tenant.id, clientId: client.id, - invoiceId: invoice.id, - collectedById: users.technician.id, - amount: plan.price, - method, - referenceNo: method !== 'cash' ? `REF-${String(Math.floor(Math.random() * 99999)).padStart(5, '0')}` : null, - createdAt: paidDate, + number: `INV-${String(invoiceCount).padStart(6, '0')}`, + amount, + balance, + status, + dueDate, + paidAt, + periodStart, + periodEnd, }, }); + + // Create payment(s) for paid/partial invoices + if (status === 'paid') { + const methods = ['gcash', 'maya', 'cash', 'bank_transfer']; + const method = methods[Math.floor(Math.random() * methods.length)]; + const paidDate = new Date(dueDate.getTime() - 86400000 * Math.floor(Math.random() * 5)); + + await prisma.payment.create({ + data: { + tenantId: tenant.id, + clientId: client.id, + invoiceId: invoice.id, + collectedById: users.technician.id, + amount, + method, + referenceNo: method !== 'cash' ? `REF-${String(Math.floor(Math.random() * 99999)).padStart(5, '0')}` : null, + createdAt: paidDate, + }, + }); + } else if (status === 'partial') { + // Partial payment - half the amount + const methods = ['gcash', 'cash']; + const method = methods[Math.floor(Math.random() * methods.length)]; + const paidDate = new Date(dueDate.getTime() - 86400000 * 2); + + await prisma.payment.create({ + data: { + tenantId: tenant.id, + clientId: client.id, + invoiceId: invoice.id, + collectedById: users.technician.id, + amount: Math.round(amount / 2), + method, + referenceNo: method !== 'cash' ? `REF-${String(Math.floor(Math.random() * 99999)).padStart(5, '0')}` : null, + createdAt: paidDate, + }, + }); + } } } } @@ -573,14 +674,61 @@ async function main() { }); console.log('Fund transfers: 2'); - // ─── Remittances ─────────────────────────────────────── - await prisma.remittance.create({ - data: { tenantId: tenant.id, collectorId: users.technician.id, confirmedById: users.tenant_admin.id, totalAmount: 8995, status: 'confirmed', confirmedAt: new Date() }, + // ─── Remittances (properly linked to payments) ────────── + // Get all payments that were for paid invoices (these are candidates for remittances) + const allPayments = await prisma.payment.findMany({ + where: { tenantId: tenant.id }, + orderBy: { createdAt: 'asc' }, }); - await prisma.remittance.create({ - data: { tenantId: tenant.id, collectorId: users.technician.id, totalAmount: 5497, status: 'pending' }, - }); - console.log('Remittances: 2'); + + // Split payments: first 60% → remitted (confirmed), next 20% → remitted (pending), last 20% → unremitted + const confirmedEnd = Math.floor(allPayments.length * 0.6); + const pendingEnd = Math.floor(allPayments.length * 0.8); + + const confirmedPayments = allPayments.slice(0, confirmedEnd); + const pendingPayments = allPayments.slice(confirmedEnd, pendingEnd); + // remaining payments (pendingEnd onward) stay unremitted + + // Create confirmed remittance + if (confirmedPayments.length > 0) { + const confirmedTotal = confirmedPayments.reduce((s, p) => s + Number(p.amount), 0); + const confirmedRemittance = await prisma.remittance.create({ + data: { + tenantId: tenant.id, + collectorId: users.technician.id, + confirmedById: users.tenant_admin.id, + totalAmount: confirmedTotal, + status: 'confirmed', + submittedAt: new Date(Date.now() - 86400000 * 7), + confirmedAt: new Date(Date.now() - 86400000 * 5), + payments: { + create: confirmedPayments.map((p) => ({ paymentId: p.id })), + }, + }, + }); + console.log(`Remittance (confirmed): ₱${confirmedTotal} (${confirmedPayments.length} payments)`); + } + + // Create pending remittance + if (pendingPayments.length > 0) { + const pendingTotal = pendingPayments.reduce((s, p) => s + Number(p.amount), 0); + const pendingRemittance = await prisma.remittance.create({ + data: { + tenantId: tenant.id, + collectorId: users.technician.id, + totalAmount: pendingTotal, + status: 'pending', + submittedAt: new Date(Date.now() - 86400000 * 2), + payments: { + create: pendingPayments.map((p) => ({ paymentId: p.id })), + }, + }, + }); + console.log(`Remittance (pending): ₱${pendingTotal} (${pendingPayments.length} payments)`); + } + + const unremittedCount = allPayments.length - pendingEnd; + console.log(`Unremitted payments: ${unremittedCount} (available for new remittance)`); console.log('\n✅ Seed completed successfully!'); console.log(`\n📊 Summary:`);