Merge dev to main #1

Open
kibin wants to merge 33 commits from dev into main
Showing only changes of commit 5f4a4c6874 - Show all commits

View File

@@ -162,6 +162,7 @@ async function main() {
tenant_admin: 'tenant_admin', tenant_admin: 'tenant_admin',
manager: 'manager', manager: 'manager',
technician: 'technician', technician: 'technician',
collector: 'collector', // collector user gets collector tenant role
viewer: 'collector', // viewer user gets collector role for demo viewer: 'collector', // viewer user gets collector role for demo
}; };
@@ -224,7 +225,7 @@ async function main() {
]); ]);
console.log(`Plans: ${plans.length}`); console.log(`Plans: ${plans.length}`);
// ─── Clients (20 clients across various areas/plans) ── // ─── Clients (45 clients across various areas/plans) ──
// Area center coordinates (Lipa City, Batangas area) // Area center coordinates (Lipa City, Batangas area)
const areaCoords: [number, number][] = [ const areaCoords: [number, number][] = [
[14.0785, 121.1760], // Barangay 1 - Centro [14.0785, 121.1760], // Barangay 1 - Centro
@@ -256,7 +257,7 @@ async function main() {
{ 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: '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: '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 }, { 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 },
// Additional clients for richer testing // Additional clients (indices 20-29)
{ first: 'Angelo', last: 'Manalo', phone: '09371234567', email: 'angelo@email.com', address: '15 Rizal Ave, Centro', area: 0, plan: 3, type: 'postpaid', latOff: 0.0025, lngOff: -0.0015 }, { first: 'Angelo', last: 'Manalo', phone: '09371234567', email: 'angelo@email.com', address: '15 Rizal Ave, Centro', area: 0, plan: 3, type: 'postpaid', latOff: 0.0025, lngOff: -0.0015 },
{ first: 'Bella', last: 'Cruz', phone: '09381234567', email: 'bella@email.com', address: '26 Mabini Ext, Poblacion', area: 1, plan: 1, type: 'postpaid', latOff: -0.0015, lngOff: 0.002 }, { first: 'Bella', last: 'Cruz', phone: '09381234567', email: 'bella@email.com', address: '26 Mabini Ext, Poblacion', area: 1, plan: 1, type: 'postpaid', latOff: -0.0015, lngOff: 0.002 },
{ first: 'Claudio', last: 'Diaz', phone: '09391234567', email: null, address: '37 Bonifacio Rd, San Isidro', area: 2, plan: 4, type: 'postpaid', latOff: 0.001, lngOff: 0.003 }, { first: 'Claudio', last: 'Diaz', phone: '09391234567', email: null, address: '37 Bonifacio Rd, San Isidro', area: 2, plan: 4, type: 'postpaid', latOff: 0.001, lngOff: 0.003 },
@@ -271,7 +272,7 @@ async function main() {
{ first: 'Linda', last: 'Madrid', phone: '09481234567', email: 'linda@email.com', address: '126 Andres St, Poblacion', area: 1, plan: 0, type: 'postpaid', latOff: -0.0015, lngOff: 0.002 }, { first: 'Linda', last: 'Madrid', phone: '09481234567', email: 'linda@email.com', address: '126 Andres St, Poblacion', area: 1, plan: 0, type: 'postpaid', latOff: -0.0015, lngOff: 0.002 },
{ first: 'Mario', last: 'Ng', phone: '09491234567', email: null, address: '137 Katipunan Rd, San Isidro', area: 2, plan: 3, type: 'postpaid', latOff: 0.002, lngOff: -0.002 }, { first: 'Mario', last: 'Ng', phone: '09491234567', email: null, address: '137 Katipunan Rd, San Isidro', area: 2, plan: 3, type: 'postpaid', latOff: 0.002, lngOff: -0.002 },
{ first: 'Nancy', last: 'Ong', phone: '09501234567', email: 'nancy@email.com', address: '148 Makabayan Blvd, Riverside', area: 3, plan: 2, type: 'postpaid', latOff: -0.001, lngOff: 0.003 }, { first: 'Nancy', last: 'Ong', phone: '09501234567', email: 'nancy@email.com', address: '148 Makabayan Blvd, Riverside', area: 3, plan: 2, type: 'postpaid', latOff: -0.001, lngOff: 0.003 },
// Additional new signups — pending installation // New signups — pending installation (indices 30-44)
{ first: 'Oscar', last: 'Pineda', phone: '09511234567', email: 'oscar@email.com', address: '159 Rizal Ext, Centro', area: 0, plan: 2, type: 'postpaid', latOff: 0.001, lngOff: -0.002 }, { first: 'Oscar', last: 'Pineda', phone: '09511234567', email: 'oscar@email.com', address: '159 Rizal Ext, Centro', area: 0, plan: 2, type: 'postpaid', latOff: 0.001, lngOff: -0.002 },
{ first: 'Patricia', last: 'Quintos', phone: '09521234567', email: 'patricia@email.com', address: '170 Mabini Rd, Poblacion', area: 1, plan: 1, type: 'postpaid', latOff: -0.002, lngOff: 0.001 }, { first: 'Patricia', last: 'Quintos', phone: '09521234567', email: 'patricia@email.com', address: '170 Mabini Rd, Poblacion', area: 1, plan: 1, type: 'postpaid', latOff: -0.002, lngOff: 0.001 },
{ first: 'Quentin', last: 'Reyes Jr', phone: '09531234567', email: null, address: '181 Bonifacio Ext, San Isidro', area: 2, plan: 3, type: 'postpaid', latOff: 0.002, lngOff: 0.002 }, { first: 'Quentin', last: 'Reyes Jr', phone: '09531234567', email: null, address: '181 Bonifacio Ext, San Isidro', area: 2, plan: 3, type: 'postpaid', latOff: 0.002, lngOff: 0.002 },
@@ -464,11 +465,11 @@ async function main() {
}); });
// Create payment(s) for paid/partial invoices // Create payment(s) for paid/partial invoices
const collector = i % 2 === 0 ? usersByEmail['collector@demo-isp.com'] : usersByEmail['tech@demo-isp.com'];
if (status === 'paid') { if (status === 'paid') {
const methods = ['gcash', 'maya', 'cash', 'bank_transfer']; const methods = ['gcash', 'maya', 'cash', 'bank_transfer'];
const method = methods[Math.floor(Math.random() * methods.length)]; const method = methods[Math.floor(Math.random() * methods.length)];
const paidDate = new Date(dueDate.getTime() - 86400000 * Math.floor(Math.random() * 5)); const paidDate = new Date(dueDate.getTime() - 86400000 * Math.floor(Math.random() * 5));
const collector = i % 2 === 0 ? usersByEmail['collector@demo-isp.com'] : usersByEmail['tech@demo-isp.com'];
await prisma.payment.create({ await prisma.payment.create({
data: { data: {
@@ -486,7 +487,6 @@ async function main() {
const methods = ['gcash', 'cash']; const methods = ['gcash', 'cash'];
const method = methods[Math.floor(Math.random() * methods.length)]; const method = methods[Math.floor(Math.random() * methods.length)];
const paidDate = new Date(dueDate.getTime() - 86400000 * 2); const paidDate = new Date(dueDate.getTime() - 86400000 * 2);
const collector = i % 2 === 0 ? usersByEmail['collector@demo-isp.com'] : usersByEmail['tech@demo-isp.com'];
await prisma.payment.create({ await prisma.payment.create({
data: { data: {
@@ -506,7 +506,7 @@ async function main() {
} }
console.log(`Clients: ${clients.length} (with subscriptions, tickets, invoices, payments)`); console.log(`Clients: ${clients.length} (with subscriptions, tickets, invoices, payments)`);
// ─── Open support tickets ────────────────────────────── // ─── Support tickets ───────────────────────────────────
const supportTickets = [ const supportTickets = [
{ clientIdx: 2, title: 'Intermittent connection drops', desc: 'Internet keeps disconnecting every 30 minutes', priority: 'high', status: 'open', type: 'support', assignee: null }, { clientIdx: 2, title: 'Intermittent connection drops', desc: 'Internet keeps disconnecting every 30 minutes', priority: 'high', status: 'open', type: 'support', assignee: null },
{ clientIdx: 5, title: 'Slow speed during peak hours', desc: 'Speed drops to 5 Mbps from 8-10 PM', priority: 'normal', status: 'open', type: 'support', assignee: null }, { clientIdx: 5, title: 'Slow speed during peak hours', desc: 'Speed drops to 5 Mbps from 8-10 PM', priority: 'normal', status: 'open', type: 'support', assignee: null },
@@ -772,7 +772,95 @@ async function main() {
} }
const unremittedCount = allPayments.length - pendingEnd; const unremittedCount = allPayments.length - pendingEnd;
console.log(`Unremitted payments: ${unremittedCount} (available for new remittance)`); console.log(`Unremitted payments: ${unremittedCount} (from invoice loop)`);
// ─── Additional unremitted payments (recent, for testing) ──
const recentPaymentDefs = [
{ clientIdx: 0, amount: 699, method: 'gcash' as const, ref: 'GCASH-44221', daysAgo: 0 },
{ clientIdx: 2, amount: 999, method: 'cash' as const, ref: null, daysAgo: 0 },
{ clientIdx: 5, amount: 1499, method: 'maya' as const, ref: 'MAYA-88312', daysAgo: 1 },
{ clientIdx: 7, amount: 999, method: 'bank_transfer' as const, ref: 'BDO-10293', daysAgo: 1 },
{ clientIdx: 9, amount: 2499, method: 'gcash' as const, ref: 'GCASH-44228', daysAgo: 2 },
{ clientIdx: 10, amount: 699, method: 'cash' as const, ref: null, daysAgo: 2 },
{ clientIdx: 14, amount: 1499, method: 'gcash' as const, ref: 'GCASH-44235', daysAgo: 3 },
{ clientIdx: 3, amount: 999, method: 'maya' as const, ref: 'MAYA-88319', daysAgo: 4 },
];
// Find or create overdue invoices for these clients to attach payments to
for (const rp of recentPaymentDefs) {
const client = clients[rp.clientIdx];
const paidDate = new Date();
paidDate.setDate(paidDate.getDate() - rp.daysAgo);
// Find an existing overdue or partial invoice for this client
let invoice = await prisma.invoice.findFirst({
where: { tenantId: tenant.id, clientId: client.id, status: { in: ['overdue', 'partial'] } },
});
// If no overdue invoice, create one
if (!invoice) {
invoiceCount++;
const dueDate = new Date();
dueDate.setDate(dueDate.getDate() - 5);
invoice = await prisma.invoice.create({
data: {
tenantId: tenant.id,
clientId: client.id,
number: `INV-${String(invoiceCount).padStart(6, '0')}`,
amount: rp.amount,
balance: rp.amount,
status: 'overdue',
dueDate,
periodStart: new Date(dueDate.getTime() - 30 * 86400000),
periodEnd: dueDate,
},
});
}
await prisma.payment.create({
data: {
tenantId: tenant.id,
clientId: client.id,
invoiceId: invoice.id,
collectedById: users.collector.id,
amount: rp.amount,
method: rp.method,
referenceNo: rp.ref,
createdAt: paidDate,
},
});
}
console.log(`Recent unremitted payments: ${recentPaymentDefs.length}`);
// ─── Notifications (unread, for testing) ──────────────────
const notifDefs = [
{ userId: users.collector.id, type: 'in_app', channel: 'payment_confirmation', title: 'Payment Recorded', message: 'Payment of ₱699 for Juan Dela Cruz has been recorded.', daysAgo: 0 },
{ userId: users.collector.id, type: 'in_app', channel: 'billing_reminder', title: 'Overdue Reminder', message: '3 invoices are overdue in Barangay 1 - Centro.', daysAgo: 1 },
{ userId: users.collector.id, type: 'in_app', channel: 'ticket_update', title: 'Ticket Assigned', message: 'DNS resolution issues ticket has been assigned to you.', daysAgo: 1 },
{ userId: users.technician.id, type: 'in_app', channel: 'ticket_update', title: 'New Ticket', message: 'Fiber cable repair - Poblacion ticket needs attention.', daysAgo: 0 },
{ userId: users.technician.id, type: 'in_app', channel: 'ticket_update', title: 'Ticket Resolved', message: 'Cannot connect after reboot ticket has been resolved.', daysAgo: 0 },
{ userId: users.manager.id, type: 'in_app', channel: 'billing_reminder', title: 'Weekly Summary', message: '12 payments collected this week totaling ₱14,988.', daysAgo: 2 },
{ userId: users.manager.id, type: 'in_app', channel: 'ticket_update', title: 'Urgent Maintenance', message: 'Node outage - Riverside sector affecting 8 subscribers.', daysAgo: 0 },
{ userId: users.tenant_admin.id, type: 'in_app', channel: 'payment_confirmation', title: 'Remittance Confirmed', message: 'Remittance of ₱5,000 has been confirmed by Admin.', daysAgo: 3 },
];
for (const n of notifDefs) {
const sentAt = new Date();
sentAt.setDate(sentAt.getDate() - n.daysAgo);
await prisma.notification.create({
data: {
tenantId: tenant.id,
userId: n.userId,
type: n.type,
channel: n.channel,
title: n.title,
message: n.message,
isRead: false,
sentAt,
},
});
}
console.log(`Notifications: ${notifDefs.length} (unread)`);
console.log('\n✅ Seed completed successfully!'); console.log('\n✅ Seed completed successfully!');
console.log(`\n📊 Summary:`); console.log(`\n📊 Summary:`);
@@ -786,8 +874,12 @@ async function main() {
console.log(` Expenses: ${expDefs.length} (approved + pending)`); console.log(` Expenses: ${expDefs.length} (approved + pending)`);
console.log(` Assets: ${assetDefs.length}`); console.log(` Assets: ${assetDefs.length}`);
console.log(` Company Accounts: ${accts.length}`); console.log(` Company Accounts: ${accts.length}`);
console.log(`\n🔑 Login: admin@demo-isp.com / admin123!`); console.log(`\n🔑 Logins (all passwords: admin123!):`);
console.log(`🌐 Portal: C-000001 / 09171234567`); console.log(` admin@demo-isp.com (tenant_admin) - full access`);
console.log(` manager@demo-isp.com (manager) - operational management`);
console.log(` collector@demo-isp.com (collector) - payment collection`);
console.log(` tech@demo-isp.com (technician) - field operations`);
console.log(` tech2@demo-isp.com (technician) - field operations`);
} }
main() main()