37241-vm/api/invoices_post.php
2026-01-03 08:40:36 +00:00

116 lines
4.6 KiB
PHP

<?php
// api/invoices_post.php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/uuid.php';
require_once __DIR__ . '/../includes/journal_helpers.php';
$response = [
'status' => 'error',
'message' => 'Invalid request.'
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (json_last_error() !== JSON_ERROR_NONE || empty($input['student_id']) || empty($input['fee_structure_id'])) {
$response['message'] = 'Invalid JSON payload or missing student_id/fee_structure_id.';
http_response_code(400);
echo json_encode($response);
exit;
}
$pdo = db();
try {
$pdo->beginTransaction();
// 1. Fetch Fee Structure details
$stmt_fsl = $pdo->prepare("SELECT fsl.amount, a.account_code FROM fee_structure_lines fsl JOIN accounts a ON fsl.revenue_account_id = a.id WHERE fsl.fee_structure_id = ?");
$stmt_fsl->execute([$input['fee_structure_id']]);
$fee_structure_lines = $stmt_fsl->fetchAll(PDO::FETCH_ASSOC);
if (empty($fee_structure_lines)) throw new Exception('Fee structure has no lines or revenue accounts not found.');
// 2. Create Invoice
$total_amount = array_sum(array_column($fee_structure_lines, 'amount'));
$invoice_id = uuid_v4();
$invoice_number = 'INV-' . time();
$invoice_date = $input['invoice_date'] ?? date('Y-m-d');
$due_date = $input['due_date'] ?? date('Y-m-d', strtotime('+30 days'));
$stmt_inv = $pdo->prepare(
"INSERT INTO invoices (id, student_id, invoice_number, invoice_date, due_date, total_amount, status) VALUES (?, ?, ?, ?, ?, ?, 'Posted')"
);
$stmt_inv->execute([$invoice_id, $input['student_id'], $invoice_number, $invoice_date, $due_date, $total_amount]);
// Don't need to insert invoice lines manually if they are based on fee structure, can be joined.
// For simplicity, let's assume the previous logic of creating them was desired.
// Re-creating invoice lines for atomicity.
foreach ($fee_structure_lines as $line) {
$stmt_inv_line = $pdo->prepare(
"INSERT INTO invoice_lines (id, invoice_id, description, amount, revenue_account_id) VALUES (?, ?, ?, ?, (SELECT id from accounts where account_code = ?))"
);
$stmt_inv_line->execute([uuid_v4(), $invoice_id, 'Fee', $line['amount'], $line['account_code']]);
}
// 3. Prepare and Create Journal Entry
$journal_lines = [];
// Debit AR
$journal_lines[] = [
'account_code' => '1200-AR-STUDENTS',
'type' => 'DEBIT',
'amount' => $total_amount
];
// Credit Revenue accounts
foreach ($fee_structure_lines as $line) {
$journal_lines[] = [
'account_code' => $line['account_code'],
'type' => 'CREDIT',
'amount' => $line['amount']
];
}
$journal_payload = [
'entry_date' => $invoice_date,
'description' => "Invoice {$invoice_number} for student {$input['student_id']}",
'lines' => $journal_lines
];
$journal_result = post_journal_entry($pdo, $journal_payload);
if (empty($journal_result['journal_entry_id'])) {
throw new Exception("Failed to post journal entry: " . ($journal_result['error'] ?? 'Unknown error'));
}
$journal_entry_id = $journal_result['journal_entry_id'];
// Link Invoice to Journal Entry
$stmt_inv_update = $pdo->prepare("UPDATE invoices SET journal_entry_id = ? WHERE id = ?");
$stmt_inv_update->execute([$journal_entry_id, $invoice_id]);
// Log audit trail
log_audit_trail($pdo, 'create_invoice', ['invoice_id' => $invoice_id, 'student_id' => $input['student_id'], 'total_amount' => $total_amount]);
$pdo->commit();
$response = [
'status' => 'success',
'message' => 'Invoice created and journal entry posted successfully.',
'data' => [
'invoice_id' => $invoice_id,
'invoice_number' => $invoice_number,
'journal_entry_id' => $journal_entry_id
]
];
http_response_code(201);
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
$response['message'] = 'Error creating invoice: ' . $e->getMessage();
http_response_code(500);
}
}
echo json_encode($response);