Merge dev to main #1
@@ -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:`);
|
||||
|
||||
Reference in New Issue
Block a user