import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { TenantService } from '../../database/tenant.service'; @Injectable() export class PaymentsService { constructor(private tenant: TenantService) {} async findAll() { return this.tenant.query(` SELECT p.*, u.unit_number, i.invoice_number FROM payments p JOIN units u ON u.id = p.unit_id LEFT JOIN invoices i ON i.id = p.invoice_id ORDER BY p.payment_date DESC, p.created_at DESC `); } async findOne(id: string) { const rows = await this.tenant.query(` SELECT p.*, u.unit_number, i.invoice_number FROM payments p JOIN units u ON u.id = p.unit_id LEFT JOIN invoices i ON i.id = p.invoice_id WHERE p.id = $1`, [id]); if (!rows.length) throw new NotFoundException('Payment not found'); return rows[0]; } async create(dto: any, userId: string) { // Validate invoice exists and get details let invoice: any = null; if (dto.invoice_id) { const rows = await this.tenant.query('SELECT * FROM invoices WHERE id = $1', [dto.invoice_id]); if (!rows.length) throw new NotFoundException('Invoice not found'); invoice = rows[0]; if (invoice.status === 'paid') throw new BadRequestException('Invoice is already paid'); if (invoice.status === 'void') throw new BadRequestException('Cannot pay void invoice'); } // Get fiscal period const payDate = new Date(dto.payment_date); let fp = await this.tenant.query( 'SELECT id FROM fiscal_periods WHERE year = $1 AND month = $2', [payDate.getFullYear(), payDate.getMonth() + 1], ); if (!fp.length) { fp = await this.tenant.query( `INSERT INTO fiscal_periods (year, month, status) VALUES ($1, $2, 'open') RETURNING id`, [payDate.getFullYear(), payDate.getMonth() + 1], ); } // Create payment record const payment = await this.tenant.query( `INSERT INTO payments (unit_id, invoice_id, payment_date, amount, payment_method, reference_number, notes, received_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`, [dto.unit_id, dto.invoice_id || null, dto.payment_date, dto.amount, dto.payment_method || 'check', dto.reference_number || null, dto.notes || null, userId], ); // Create journal entry: DR Cash, CR Accounts Receivable const cashAccount = await this.tenant.query(`SELECT id FROM accounts WHERE account_number = '1000'`); const arAccount = await this.tenant.query(`SELECT id FROM accounts WHERE account_number = '1200'`); if (cashAccount.length && arAccount.length) { const je = await this.tenant.query( `INSERT INTO journal_entries (entry_date, description, entry_type, fiscal_period_id, source_type, source_id, is_posted, posted_at, created_by) VALUES ($1, $2, 'payment', $3, 'payment', $4, true, NOW(), $5) RETURNING id`, [dto.payment_date, `Payment received - ${dto.reference_number || 'N/A'}`, fp[0].id, payment[0].id, userId], ); await this.tenant.query( `INSERT INTO journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, $3, 0), ($1, $4, 0, $3)`, [je[0].id, cashAccount[0].id, dto.amount, arAccount[0].id], ); await this.tenant.query(`UPDATE payments SET journal_entry_id = $1 WHERE id = $2`, [je[0].id, payment[0].id]); } // Update invoice if linked if (invoice) { const newPaid = parseFloat(invoice.amount_paid) + parseFloat(dto.amount); const invoiceAmt = parseFloat(invoice.amount); const newStatus = newPaid >= invoiceAmt ? 'paid' : 'partial'; await this.tenant.query( `UPDATE invoices SET amount_paid = $1, status = $2, paid_at = CASE WHEN $2 = 'paid' THEN NOW() ELSE paid_at END, updated_at = NOW() WHERE id = $3`, [newPaid, newStatus, invoice.id], ); } return payment[0]; } }