fix: mobile app bugs - payment screen, remittance isolation, invoice sorting, ticket creation
- Fix record payment screen fetching invoice directly instead of relying on collection provider - Fix getUnremittedPayments tenant isolation (remittance tenantId filter) - Add invoice status filter and dueDate sort support in controller/service - Allow technicians to create tickets (role decorator fix) - Enhance seed data with 10 more pending-installation clients and installation tickets
This commit is contained in:
@@ -271,6 +271,17 @@ 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
|
||||||
|
{ 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: '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: 'Rita', last: 'Santillan', phone: '09541234567', email: 'rita@email.com', address: '192 Luna St, Riverside', area: 3, plan: 0, type: 'prepaid', latOff: -0.001, lngOff: -0.001 },
|
||||||
|
{ first: 'Samuel', last: 'Torres', phone: '09551234567', email: null, address: '203 Del Pilar Blvd, Hilltop', area: 4, plan: 2, type: 'postpaid', latOff: 0.0015, lngOff: 0.001 },
|
||||||
|
{ first: 'Tina', last: 'Uy', phone: '09561234567', email: 'tina@email.com', address: '214 Quezon Rd, Centro', area: 0, plan: 4, type: 'postpaid', latOff: -0.002, lngOff: -0.0015 },
|
||||||
|
{ first: 'Ulysses', last: 'Velasco', phone: '09571234567', email: null, address: '225 Magsaysay Ext, Poblacion', area: 1, plan: 1, type: 'prepaid', latOff: 0.003, lngOff: -0.002 },
|
||||||
|
{ first: 'Vivian', last: 'Walsh', phone: '09581234567', email: 'vivian@email.com', address: '236 Roxas Ave, San Isidro', area: 2, plan: 2, type: 'postpaid', latOff: -0.001, lngOff: 0.003 },
|
||||||
|
{ first: 'Walter', last: 'Xavier', phone: '09591234567', email: null, address: '247 Laurel Blvd, Riverside', area: 3, plan: 3, type: 'postpaid', latOff: 0.002, lngOff: -0.001 },
|
||||||
|
{ first: 'Yolanda', last: 'Yap', phone: '09601234567', email: 'yolanda@email.com', address: '258 Osmena Rd, Hilltop', area: 4, plan: 1, type: 'postpaid', latOff: -0.0015, lngOff: 0.002 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const clients: any[] = [];
|
const clients: any[] = [];
|
||||||
@@ -281,7 +292,7 @@ async function main() {
|
|||||||
const c = clientDefs[i];
|
const c = clientDefs[i];
|
||||||
const accountNumber = `C-${String(i + 1).padStart(6, '0')}`;
|
const accountNumber = `C-${String(i + 1).padStart(6, '0')}`;
|
||||||
const plan = plans[c.plan];
|
const plan = plans[c.plan];
|
||||||
const isNewSignup = i >= 30; // last 5 are new signups pending installation
|
const isNewSignup = i >= 30; // 15 new signups pending installation
|
||||||
|
|
||||||
const client = await prisma.client.create({
|
const client = await prisma.client.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -511,6 +522,12 @@ async function main() {
|
|||||||
{ clientIdx: 26, title: 'High latency for gaming', desc: 'Ping above 100ms during evenings', priority: 'normal', status: 'in_progress', type: 'support', assignee: 'tech' },
|
{ clientIdx: 26, title: 'High latency for gaming', desc: 'Ping above 100ms during evenings', priority: 'normal', status: 'in_progress', type: 'support', assignee: 'tech' },
|
||||||
{ clientIdx: 19, title: 'ONT replacement needed', desc: 'ONT showing red fault light intermittently', priority: 'high', status: 'open', type: 'maintenance', assignee: null },
|
{ clientIdx: 19, title: 'ONT replacement needed', desc: 'ONT showing red fault light intermittently', priority: 'high', status: 'open', type: 'maintenance', assignee: null },
|
||||||
{ clientIdx: 21, title: 'Monthly service credit request', desc: 'Requesting credit for 2-day outage last month', priority: 'low', status: 'open', type: 'support', assignee: null },
|
{ clientIdx: 21, title: 'Monthly service credit request', desc: 'Requesting credit for 2-day outage last month', priority: 'low', status: 'open', type: 'support', assignee: null },
|
||||||
|
{ clientIdx: 3, title: 'Second floor extension installation', desc: 'Client wants additional fiber drop to 2nd floor office', priority: 'normal', status: 'open', type: 'installation', assignee: null },
|
||||||
|
{ clientIdx: 9, title: 'Fiber relocation due to renovation', desc: 'House renovation requires moving fiber entry point', priority: 'normal', status: 'in_progress', type: 'installation', assignee: 'tech2' },
|
||||||
|
{ clientIdx: 14, title: 'ONT upgrade to GPON', desc: 'Current ONT outdated, needs GPON-compatible replacement', priority: 'low', status: 'open', type: 'installation', assignee: null },
|
||||||
|
{ clientIdx: 7, title: 'New branch office fiber install', desc: 'Client opened sari-sari store next door, wants 2nd connection', priority: 'high', status: 'open', type: 'installation', assignee: null },
|
||||||
|
{ clientIdx: 24, title: 'Intermittent packet loss', desc: 'Ping shows 5-10% packet loss during daytime', priority: 'high', status: 'in_progress', type: 'maintenance', assignee: 'tech' },
|
||||||
|
{ clientIdx: 12, title: 'Cable exposed across driveway', desc: 'Fiber cable hanging low across client driveway, safety hazard', priority: 'urgent', status: 'open', type: 'maintenance', assignee: null },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const t of supportTickets) {
|
for (const t of supportTickets) {
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ export class InvoiceController {
|
|||||||
@CurrentUser() user: CurrentUserPayload,
|
@CurrentUser() user: CurrentUserPayload,
|
||||||
@Query('clientId') clientId?: string,
|
@Query('clientId') clientId?: string,
|
||||||
@Query('status') status?: string,
|
@Query('status') status?: string,
|
||||||
|
@Query('sort') sort?: string,
|
||||||
) {
|
) {
|
||||||
return this.invoiceService.findAll(user.tenantId, { clientId, status });
|
return this.invoiceService.findAll(user.tenantId, { clientId, status, sort });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
|||||||
@@ -10,13 +10,23 @@ import { paginationArgs, paginatedResult } from '../common/dto/pagination.dto';
|
|||||||
export class InvoiceService {
|
export class InvoiceService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
async findAll(tenantId: string, filters?: { clientId?: string; status?: string; page?: number; limit?: number }) {
|
async findAll(tenantId: string, filters?: { clientId?: string; status?: string; sort?: string; page?: number; limit?: number }) {
|
||||||
const { skip, take, page, limit } = paginationArgs({ page: filters?.page, limit: filters?.limit });
|
const { skip, take, page, limit } = paginationArgs({ page: filters?.page, limit: filters?.limit });
|
||||||
const db = this.prisma.forTenant(tenantId);
|
const db = this.prisma.forTenant(tenantId);
|
||||||
|
|
||||||
|
const statusFilter = filters?.status
|
||||||
|
? (filters.status.includes(',') ? { in: filters.status.split(',') } : filters.status)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
...(filters?.clientId && { clientId: filters.clientId }),
|
...(filters?.clientId && { clientId: filters.clientId }),
|
||||||
...(filters?.status && { status: filters.status }),
|
...(statusFilter && { status: statusFilter }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const orderBy = filters?.sort === 'dueDate:asc'
|
||||||
|
? { dueDate: 'asc' as const }
|
||||||
|
: { createdAt: 'desc' as const };
|
||||||
|
|
||||||
const [items, total] = await Promise.all([
|
const [items, total] = await Promise.all([
|
||||||
db.invoice.findMany({
|
db.invoice.findMany({
|
||||||
where,
|
where,
|
||||||
@@ -26,7 +36,7 @@ export class InvoiceService {
|
|||||||
client: { select: { id: true, firstName: true, lastName: true, accountNumber: true, phone: true, latitude: true, longitude: true } },
|
client: { select: { id: true, firstName: true, lastName: true, accountNumber: true, phone: true, latitude: true, longitude: true } },
|
||||||
_count: { select: { payments: true } },
|
_count: { select: { payments: true } },
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy,
|
||||||
}),
|
}),
|
||||||
db.invoice.count({ where }),
|
db.invoice.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -159,7 +159,10 @@ export class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getUnremittedPayments(tenantId: string, collectorId: string) {
|
async getUnremittedPayments(tenantId: string, collectorId: string) {
|
||||||
const remittedIds = (await this.prisma.remittancePayment.findMany({ select: { paymentId: true } }))
|
const remittedIds = (await this.prisma.remittancePayment.findMany({
|
||||||
|
where: { remittance: { tenantId } },
|
||||||
|
select: { paymentId: true },
|
||||||
|
}))
|
||||||
.map((r) => r.paymentId);
|
.map((r) => r.paymentId);
|
||||||
|
|
||||||
return this.prisma.payment.findMany({
|
return this.prisma.payment.findMany({
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class TicketController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Roles('manager')
|
@Roles('technician')
|
||||||
async create(
|
async create(
|
||||||
@CurrentUser() user: CurrentUserPayload,
|
@CurrentUser() user: CurrentUserPayload,
|
||||||
@Body() dto: CreateTicketDto,
|
@Body() dto: CreateTicketDto,
|
||||||
|
|||||||
Reference in New Issue
Block a user