'required|integer', 'invoice_id' => 'required|integer', 'total_amount' => 'required|decimal', 'paid_amount' => 'required|decimal', 'balance' => 'required|decimal', 'number_of_installments' => 'required|integer', 'payment_method' => 'required|max_length[50]', // ✅ Removed 'string' 'payment_date' => 'required|valid_date[Y-m-d H:i:s]', 'status' => 'required|max_length[50]', // ✅ Removed 'string' 'check_number' => 'permit_empty|max_length[100]', ]; protected $validationMessages = [ 'parent_id' => [ 'required' => 'Parent ID is required.', 'integer' => 'Parent ID must be an integer.', ], 'invoice_id' => [ 'required' => 'Invoice ID is required.', 'integer' => 'Invoice ID must be an integer.', ], 'total_amount' => [ 'required' => 'Total amount is required.', 'decimal' => 'Total amount must be a valid decimal.', ], 'paid_amount' => [ 'required' => 'Paid amount is required.', 'decimal' => 'Paid amount must be a valid decimal.', ], 'balance' => [ 'required' => 'Balance is required.', 'decimal' => 'Balance must be a valid decimal.', ], 'payment_date' => [ 'required' => 'Payment date is required.', 'valid_date' => 'Payment date must be a valid date (Y-m-d H:i:s).', ], 'payment_method' => [ 'required' => 'Payment method is required.', 'max_length' => 'Payment method must not exceed 50 characters.', ], 'status' => [ 'required' => 'Status is required.', 'max_length' => 'Status must not exceed 50 characters.', ], 'number_of_installments' => [ 'required' => 'Number of installments is required.', 'integer' => 'Number of installments must be an integer.', ], 'check_number' => [ 'max_length' => 'Check number must not exceed 100 characters.', ] ]; /** * Get payments by parent ID. * * @param int $parentId * @return array */ public function getPaymentsByParentId(int $parentId): array { return $this->where('parent_id', $parentId) ->orderBy('payment_date', 'DESC') ->findAll(); } /** * Calculate the total paid amount for a specific parent. * * @param int $parentId * @return float */ public function getTotalPaidByParentId(int $parentId, string $schoolYear): float { $db = \Config\Database::connect(); $result = $db->table('payments') ->selectSum('paid_amount', 'total_paid') ->where('parent_id', $parentId) ->where('school_year', $schoolYear) ->get() ->getRowArray(); return $result && isset($result['total_paid']) ? (float) $result['total_paid'] : 0.00; } /** * Update balance for a specific payment. * * @param int $paymentId * @param float $amountPaid * @return bool */ public function updateBalance(int $paymentId, float $amountPaid): bool { $payment = $this->find($paymentId); if (!$payment) { return false; } $newBalance = $payment['balance'] - $amountPaid; return $this->update($paymentId, [ 'paid_amount' => $payment['paid_amount'] + $amountPaid, 'balance' => $newBalance ]); } /** * Get payments by school year. * * @param string $schoolYear * @return array */ public function getPaymentsByYear(string $schoolYear): array { return $this->where('school_year', $schoolYear) ->orderBy('payment_date', 'DESC') ->findAll(); } /** * Generate a new transaction ID. * * @return string */ public function generateNewTransactionId(): string { $currentYear = date('Y'); $prefix = $currentYear . '-'; $latest = $this->like('transaction_id', $prefix, 'after') ->orderBy('transaction_id', 'DESC') ->first(); if ($latest && isset($latest['transaction_id'])) { $lastNumber = (int) str_replace($prefix, '', $latest['transaction_id']); $newNumber = $lastNumber + 1; } else { $newNumber = 1; } return $prefix . str_pad($newNumber, 6, '0', STR_PAD_LEFT); } public function getPaymentsByInvoice($invoiceIds): array { // Normalize input if (!is_array($invoiceIds)) { $invoiceIds = [$invoiceIds]; } if (empty($invoiceIds)) { return []; } // Subquery: latest payment_date per invoice (Full/Partial only) $latestSub = $this->db->table('payments') ->select('invoice_id, MAX(payment_date) AS max_date') ->whereIn('status', ['Full', 'Partially Paid']) ->groupBy('invoice_id') ->getCompiledSelect(); // Main query: join payments to the latest per invoice $rows = $this->db->table('payments p') ->select('p.invoice_id, p.paid_amount AS last_paid_amount, p.payment_date AS last_payment_date, p.status AS last_payment_status') ->join("($latestSub) latest", 'latest.invoice_id = p.invoice_id AND latest.max_date = p.payment_date', 'inner', false) // <-- escape=false ->whereIn('p.invoice_id', $invoiceIds) ->whereIn('p.status', ['Full', 'Partially Paid']) ->get() ->getResultArray(); return $rows; } }