add controllers, servoices
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Finance\FinancialReportRequest;
|
||||
use App\Http\Requests\Finance\FinancialSummaryRequest;
|
||||
use App\Http\Requests\Finance\FinancialUnpaidParentsRequest;
|
||||
use App\Http\Resources\Finance\FinancialReportResource;
|
||||
use App\Http\Resources\Finance\FinancialSummaryResource;
|
||||
use App\Http\Resources\Finance\FinancialUnpaidParentResource;
|
||||
use App\Services\Finance\FinancialPdfReportService;
|
||||
use App\Services\Finance\FinancialReportService;
|
||||
use App\Services\Finance\FinancialSchoolYearService;
|
||||
use App\Services\Finance\FinancialSummaryService;
|
||||
use App\Services\Finance\FinancialUnpaidParentsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class FinancialController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private FinancialReportService $reportService,
|
||||
private FinancialSummaryService $summaryService,
|
||||
private FinancialUnpaidParentsService $unpaidParentsService,
|
||||
private FinancialSchoolYearService $schoolYears,
|
||||
private FinancialPdfReportService $pdfService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function report(FinancialReportRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$report = $this->reportService->getReport(
|
||||
$payload['date_from'] ?? null,
|
||||
$payload['date_to'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'report' => new FinancialReportResource($report),
|
||||
]);
|
||||
}
|
||||
|
||||
public function summary(FinancialSummaryRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$summary = $this->summaryService->getSummary(
|
||||
$payload['date_from'] ?? null,
|
||||
$payload['date_to'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
$schoolYears = $this->schoolYears->listYears($summary['schoolYear'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'summary' => new FinancialSummaryResource($summary),
|
||||
'schoolYears' => $schoolYears,
|
||||
]);
|
||||
}
|
||||
|
||||
public function unpaidParents(FinancialUnpaidParentsRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->unpaidParentsService->getUnpaidParents($payload['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'schoolYear' => $data['schoolYear'] ?? null,
|
||||
'schoolYears' => $data['schoolYears'] ?? [],
|
||||
'results' => FinancialUnpaidParentResource::collection($data['results'] ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadCsv(FinancialReportRequest $request): StreamedResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$report = $this->reportService->getReport(
|
||||
$payload['date_from'] ?? null,
|
||||
$payload['date_to'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
$paymentsMap = [];
|
||||
foreach ($report['payments'] as $payment) {
|
||||
$invoiceId = (int) ($payment['invoice_id'] ?? 0);
|
||||
$paymentsMap[$invoiceId] = (float) ($payment['paid_amount'] ?? 0);
|
||||
}
|
||||
|
||||
$refundsMap = [];
|
||||
foreach ($report['refunds'] as $refund) {
|
||||
$invoiceId = (int) ($refund['invoice_id'] ?? 0);
|
||||
$refundsMap[$invoiceId] = (float) ($refund['total_refunded'] ?? 0);
|
||||
}
|
||||
|
||||
$discountsMap = [];
|
||||
foreach ($report['discounts'] as $discount) {
|
||||
$invoiceId = (int) ($discount['invoice_id'] ?? 0);
|
||||
$discountsMap[$invoiceId] = (float) ($discount['discount_amount'] ?? 0);
|
||||
}
|
||||
|
||||
$breakdown = $report['paymentBreakdown'] ?? [];
|
||||
|
||||
$filename = 'financial_report_' . date('Ymd_His') . '.csv';
|
||||
|
||||
return response()->streamDownload(function () use ($report, $paymentsMap, $refundsMap, $discountsMap, $breakdown) {
|
||||
$out = fopen('php://output', 'w');
|
||||
|
||||
fputcsv($out, ['Invoice #', 'Parent', 'School Year', 'Total', 'Paid', 'Cash', 'Credit', 'Check', 'Balance', 'Refund', 'Discount', 'Status']);
|
||||
$sumTotal = 0.0;
|
||||
$sumPaid = 0.0;
|
||||
$sumCash = 0.0;
|
||||
$sumCredit = 0.0;
|
||||
$sumCheck = 0.0;
|
||||
$sumBalance = 0.0;
|
||||
$sumRefund = 0.0;
|
||||
$sumDiscount = 0.0;
|
||||
|
||||
foreach ($report['invoices'] as $inv) {
|
||||
$invoiceId = (int) ($inv['id'] ?? 0);
|
||||
$paid = (float) ($paymentsMap[$invoiceId] ?? 0);
|
||||
$bd = $breakdown[$invoiceId] ?? [];
|
||||
$cash = (float) ($bd['cash'] ?? 0);
|
||||
$credit = (float) ($bd['credit'] ?? 0);
|
||||
$check = (float) ($bd['check'] ?? 0);
|
||||
$refunded = (float) ($refundsMap[$invoiceId] ?? 0);
|
||||
$discount = (float) ($discountsMap[$invoiceId] ?? 0);
|
||||
$total = (float) ($inv['total_amount'] ?? 0);
|
||||
$balance = $total - $paid - $discount - $refunded;
|
||||
if ($balance < 0) {
|
||||
$balance = 0.0;
|
||||
}
|
||||
$status = $balance === 0.0 ? 'Paid' : 'Unpaid';
|
||||
|
||||
fputcsv($out, [
|
||||
$inv['invoice_number'] ?? '',
|
||||
$inv['parent_name'] ?? '',
|
||||
$inv['school_year'] ?? '',
|
||||
$total,
|
||||
$paid,
|
||||
$cash,
|
||||
$credit,
|
||||
$check,
|
||||
$balance,
|
||||
$refunded,
|
||||
$discount,
|
||||
$status,
|
||||
]);
|
||||
|
||||
$sumTotal += $total;
|
||||
$sumPaid += $paid;
|
||||
$sumCash += $cash;
|
||||
$sumCredit += $credit;
|
||||
$sumCheck += $check;
|
||||
$sumBalance += $balance;
|
||||
$sumRefund += $refunded;
|
||||
$sumDiscount += $discount;
|
||||
}
|
||||
|
||||
fputcsv($out, [
|
||||
'TOTALS',
|
||||
'',
|
||||
'',
|
||||
$sumTotal,
|
||||
$sumPaid,
|
||||
$sumCash,
|
||||
$sumCredit,
|
||||
$sumCheck,
|
||||
$sumBalance,
|
||||
$sumRefund,
|
||||
$sumDiscount,
|
||||
'',
|
||||
]);
|
||||
|
||||
fputcsv($out, []);
|
||||
fputcsv($out, ['Expenses Summary']);
|
||||
fputcsv($out, ['Category', 'Total Amount']);
|
||||
foreach ($report['expenses'] as $exp) {
|
||||
fputcsv($out, [
|
||||
$exp['category'] ?? '',
|
||||
$exp['total_amount'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
fputcsv($out, []);
|
||||
fputcsv($out, ['Reimbursements Summary']);
|
||||
fputcsv($out, ['Status', 'Total Amount']);
|
||||
foreach ($report['reimbursements'] as $row) {
|
||||
fputcsv($out, [
|
||||
$row['status'] ?? '',
|
||||
$row['total_amount'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
}, $filename, ['Content-Type' => 'text/csv']);
|
||||
}
|
||||
|
||||
public function downloadPdf(FinancialReportRequest $request): StreamedResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$summary = $this->summaryService->getSummary(
|
||||
$payload['date_from'] ?? null,
|
||||
$payload['date_to'] ?? null,
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
$pdfBytes = $this->pdfService->buildPdf($summary);
|
||||
$schoolYear = $summary['schoolYear'] ?? 'report';
|
||||
|
||||
return response()->streamDownload(function () use ($pdfBytes) {
|
||||
echo $pdfBytes;
|
||||
}, 'Financial_Report_' . $schoolYear . '.pdf', ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Invoices\InvoiceGenerateRequest;
|
||||
use App\Http\Requests\Invoices\InvoiceManagementRequest;
|
||||
use App\Http\Requests\Invoices\InvoiceParentRequest;
|
||||
use App\Http\Requests\Invoices\InvoiceStatusRequest;
|
||||
use App\Http\Resources\Invoices\InvoiceManagementParentResource;
|
||||
use App\Http\Resources\Invoices\InvoiceResource;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\Invoices\InvoiceGenerationService;
|
||||
use App\Services\Invoices\InvoiceManagementService;
|
||||
use App\Services\Invoices\InvoicePaymentService;
|
||||
use App\Services\Invoices\InvoicePdfService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class InvoiceController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private InvoiceManagementService $management,
|
||||
private InvoiceGenerationService $generation,
|
||||
private InvoicePaymentService $paymentService,
|
||||
private InvoicePdfService $pdfService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function management(InvoiceManagementRequest $request): JsonResponse
|
||||
{
|
||||
$schoolYear = $request->validated()['school_year'] ?? null;
|
||||
$schoolYear = $schoolYear ?: $request->input('schoolYear');
|
||||
$schoolYear = $schoolYear ?: $request->input('year');
|
||||
|
||||
$data = $this->management->getManagementData($schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'schoolYear' => $data['schoolYear'],
|
||||
'schoolYears' => $data['schoolYears'],
|
||||
'invoices' => InvoiceManagementParentResource::collection($data['invoices']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function generate(InvoiceGenerateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->generation->generateInvoice((int) $payload['parent_id'], $payload['school_year'] ?? null);
|
||||
|
||||
if (empty($result['ok'])) {
|
||||
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to generate invoice.'], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'updated' => $result['updated'] ?? false,
|
||||
'updated_ids' => $result['updated_ids'] ?? [],
|
||||
'insert_id' => $result['insert_id'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function byParent(InvoiceParentRequest $request, int $parentId): JsonResponse
|
||||
{
|
||||
$schoolYear = $request->validated()['school_year'] ?? null;
|
||||
$invoices = Invoice::getInvoicesByParentId($parentId, $schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'invoices' => InvoiceResource::collection($invoices),
|
||||
]);
|
||||
}
|
||||
|
||||
public function parentPayment(InvoiceParentRequest $request): JsonResponse
|
||||
{
|
||||
$parentId = (int) (auth()->id() ?? 0);
|
||||
if ($parentId <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$schoolYear = $request->validated()['school_year'] ?? null;
|
||||
$data = $this->paymentService->getParentInvoiceSummary($parentId, $schoolYear);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'invoices' => InvoiceResource::collection($data['invoices']),
|
||||
'schoolYears' => $data['schoolYears'],
|
||||
'selectedYear' => $data['selectedYear'],
|
||||
'currentSchoolYear' => $data['currentSchoolYear'],
|
||||
'dueDate' => $data['dueDate'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStatus(InvoiceStatusRequest $request, int $invoiceId): JsonResponse
|
||||
{
|
||||
$status = $request->validated()['status'];
|
||||
$updated = Invoice::updateInvoiceStatus($invoiceId, $status);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json(['ok' => false, 'message' => 'Invoice not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'status' => $status]);
|
||||
}
|
||||
|
||||
public function unpaid(): JsonResponse
|
||||
{
|
||||
$rows = Invoice::getUnpaidInvoices();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'invoices' => InvoiceResource::collection($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
public function pdf(int $invoiceId): StreamedResponse
|
||||
{
|
||||
$pdfBytes = $this->pdfService->buildPdf($invoiceId);
|
||||
|
||||
return response()->streamDownload(function () use ($pdfBytes) {
|
||||
echo $pdfBytes;
|
||||
}, 'invoice_' . $invoiceId . '.pdf', ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentByParentRequest;
|
||||
use App\Http\Requests\Payments\PaymentCreateRequest;
|
||||
use App\Http\Requests\Payments\PaymentUpdateBalanceRequest;
|
||||
use App\Http\Resources\Payments\PaymentResource;
|
||||
use App\Services\Payments\PaymentBalanceService;
|
||||
use App\Services\Payments\PaymentLookupService;
|
||||
use App\Services\Payments\PaymentPlanService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaymentController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private PaymentPlanService $planService,
|
||||
private PaymentLookupService $lookupService,
|
||||
private PaymentBalanceService $balanceService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function store(PaymentCreateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$payment = $this->planService->createPlan($payload);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'payment' => new PaymentResource($payment),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function byParent(PaymentByParentRequest $request, int $parentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$payments = $this->lookupService->getByParent($parentId, $payload['school_year'] ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'payments' => PaymentResource::collection($payments),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateBalance(PaymentUpdateBalanceRequest $request, int $paymentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$updated = $this->balanceService->updateBalance($paymentId, (float) $payload['paid_amount']);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json(['ok' => false, 'message' => 'Payment not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentEventChargesListRequest;
|
||||
use App\Http\Requests\Payments\PaymentEventChargesStoreRequest;
|
||||
use App\Services\Payments\PaymentEventChargesService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaymentEventChargesController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaymentEventChargesService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(PaymentEventChargesListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->listCharges($payload['school_year'] ?? null, $payload['semester'] ?? null);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function store(PaymentEventChargesStoreRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$count = $this->service->addCharges($payload, $userId);
|
||||
if ($count === 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'No student selected.'], 422);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'inserted' => $count]);
|
||||
}
|
||||
|
||||
public function enrolledStudents(PaymentEventChargesListRequest $request, int $parentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$students = $this->service->getEnrolledStudents($parentId, $payload['school_year'] ?? null);
|
||||
|
||||
return response()->json(['ok' => true, 'students' => $students]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentManualEditRequest;
|
||||
use App\Http\Requests\Payments\PaymentManualSearchRequest;
|
||||
use App\Http\Requests\Payments\PaymentManualSuggestRequest;
|
||||
use App\Http\Requests\Payments\PaymentManualUpdateRequest;
|
||||
use App\Services\Payments\PaymentManualService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PaymentManualController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaymentManualService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function search(PaymentManualSearchRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$data = $this->service->search(
|
||||
(string) ($payload['search_term'] ?? ''),
|
||||
$payload['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true] + $data);
|
||||
}
|
||||
|
||||
public function suggest(PaymentManualSuggestRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$items = $this->service->suggest((string) ($payload['q'] ?? ''));
|
||||
|
||||
return response()->json(['ok' => true, 'items' => $items]);
|
||||
}
|
||||
|
||||
public function record(PaymentManualUpdateRequest $request, int $invoiceId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
$result = $this->service->recordPayment(
|
||||
$invoiceId,
|
||||
(float) $payload['amount'],
|
||||
(string) $payload['payment_method'],
|
||||
$payload['payment_date'] ?? null,
|
||||
$payload['check_number'] ?? null,
|
||||
$payload['payment_type'] ?? null,
|
||||
$request->file('payment_file'),
|
||||
$payload['school_year'] ?? null,
|
||||
$payload['semester'] ?? null,
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true] + $result);
|
||||
}
|
||||
|
||||
public function edit(PaymentManualEditRequest $request, int $paymentId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
$this->service->editPayment(
|
||||
$paymentId,
|
||||
(float) $payload['paid_amount'],
|
||||
(string) $payload['payment_method'],
|
||||
$payload['check_number'] ?? null,
|
||||
$request->file('payment_file'),
|
||||
(int) (auth()->id() ?? 0)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function file(Request $request, string $filename)
|
||||
{
|
||||
$mode = (string) ($request->query('mode') ?? 'download');
|
||||
return $this->service->serveCheckFile($filename, $mode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentNotificationListRequest;
|
||||
use App\Http\Requests\Payments\PaymentNotificationSendRequest;
|
||||
use App\Http\Resources\Payments\PaymentNotificationLogResource;
|
||||
use App\Services\Payments\PaymentNotificationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaymentNotificationController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaymentNotificationService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(PaymentNotificationListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$logs = $this->service->listLogs(
|
||||
$payload['from'] ?? null,
|
||||
$payload['to'] ?? null,
|
||||
$payload['type'] ?? null
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'logs' => PaymentNotificationLogResource::collection($logs),
|
||||
]);
|
||||
}
|
||||
|
||||
public function send(PaymentNotificationSendRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$result = $this->service->send($payload);
|
||||
|
||||
return response()->json(['ok' => true] + $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaymentTransactionCreateRequest;
|
||||
use App\Http\Requests\Payments\PaymentTransactionStatusRequest;
|
||||
use App\Http\Resources\Payments\PaymentTransactionResource;
|
||||
use App\Services\Payments\PaymentTransactionService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaymentTransactionController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaymentTransactionService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function store(PaymentTransactionCreateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$transaction = $this->service->create($payload);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'transaction' => new PaymentTransactionResource($transaction->toArray()),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function byPayment(int $paymentId): JsonResponse
|
||||
{
|
||||
$transactions = $this->service->getByPayment($paymentId);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'transactions' => PaymentTransactionResource::collection($transactions),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStatus(PaymentTransactionStatusRequest $request, string $transactionId): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$updated = $this->service->updateStatus($transactionId, $payload['status']);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json(['ok' => false, 'message' => 'Transaction not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaypalExecuteRequest;
|
||||
use App\Services\Payments\PaypalPaymentService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PaypalPaymentController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaypalPaymentService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function redirect(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'redirect_url' => $this->service->getRedirectUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(int $paymentId): JsonResponse
|
||||
{
|
||||
$result = $this->service->createPayment($paymentId);
|
||||
|
||||
return response()->json(['ok' => true] + $result);
|
||||
}
|
||||
|
||||
public function execute(PaypalExecuteRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$this->service->executePayment(
|
||||
(string) $payload['payer_id'],
|
||||
$payload['paypal_payment_id'] ?? null
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function cancel(): JsonResponse
|
||||
{
|
||||
$this->service->cancelPayment();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Payment was canceled.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Payments\PaypalTransactionsListRequest;
|
||||
use App\Http\Resources\Payments\PaypalTransactionResource;
|
||||
use App\Services\Payments\PaypalTransactionsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class PaypalTransactionsController extends BaseApiController
|
||||
{
|
||||
public function __construct(private PaypalTransactionsService $service)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(PaypalTransactionsListRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$perPage = (int) ($payload['per_page'] ?? 10);
|
||||
$rows = $this->service->list($payload['q'] ?? null, $perPage);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'transactions' => PaypalTransactionResource::collection($rows->items()),
|
||||
'pagination' => [
|
||||
'current_page' => $rows->currentPage(),
|
||||
'per_page' => $rows->perPage(),
|
||||
'total' => $rows->total(),
|
||||
'last_page' => $rows->lastPage(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadCsv(PaypalTransactionsListRequest $request): StreamedResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$rows = $this->service->listAll($payload['q'] ?? null);
|
||||
|
||||
$filename = 'paypal_transactions_' . date('Ymd_His') . '.csv';
|
||||
|
||||
return response()->streamDownload(function () use ($rows) {
|
||||
$out = fopen('php://output', 'w');
|
||||
fputcsv($out, [
|
||||
'ID', 'Transaction ID', 'Order ID', 'Parent School ID',
|
||||
'Email', 'Amount', 'Net Amount', 'Currency',
|
||||
'Status', 'Event Type', 'Created At',
|
||||
]);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
fputcsv($out, [
|
||||
$row['id'] ?? null,
|
||||
$row['transaction_id'] ?? null,
|
||||
$row['order_id'] ?? null,
|
||||
$row['parent_school_id'] ?? null,
|
||||
$row['payer_email'] ?? null,
|
||||
$row['amount'] ?? null,
|
||||
$row['net_amount'] ?? null,
|
||||
$row['currency'] ?? null,
|
||||
$row['status'] ?? null,
|
||||
$row['event_type'] ?? null,
|
||||
$row['created_at'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
}, $filename, ['Content-Type' => 'text/csv']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Finance;
|
||||
|
||||
use App\Http\Controllers\Api\BaseApiController;
|
||||
use App\Http\Requests\Files\FileNameRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchAdminFileRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchAssignmentRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchCreateRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchEmailRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementBatchLockRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementExportBatchRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementExportRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementIndexRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementMarkDonationRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementProcessRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementStoreRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementUnderProcessingRequest;
|
||||
use App\Http\Requests\Reimbursements\ReimbursementUpdateRequest;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementBatchResource;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementExpenseResource;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementRecipientResource;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementResource;
|
||||
use App\Http\Resources\Reimbursements\ReimbursementUnderProcessingItemResource;
|
||||
use App\Models\Reimbursement;
|
||||
use App\Models\ReimbursementBatch;
|
||||
use App\Services\Files\FileServeService;
|
||||
use App\Services\Reimbursements\ReimbursementBatchAssignmentService;
|
||||
use App\Services\Reimbursements\ReimbursementBatchService;
|
||||
use App\Services\Reimbursements\ReimbursementContextService;
|
||||
use App\Services\Reimbursements\ReimbursementCrudService;
|
||||
use App\Services\Reimbursements\ReimbursementDonationService;
|
||||
use App\Services\Reimbursements\ReimbursementEmailService;
|
||||
use App\Services\Reimbursements\ReimbursementExportService;
|
||||
use App\Services\Reimbursements\ReimbursementFileService;
|
||||
use App\Services\Reimbursements\ReimbursementQueryService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use RuntimeException;
|
||||
|
||||
class ReimbursementController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ReimbursementContextService $context,
|
||||
private ReimbursementQueryService $queryService,
|
||||
private ReimbursementDonationService $donationService,
|
||||
private ReimbursementBatchService $batchService,
|
||||
private ReimbursementBatchAssignmentService $assignmentService,
|
||||
private ReimbursementFileService $fileService,
|
||||
private ReimbursementExportService $exportService,
|
||||
private ReimbursementEmailService $emailService,
|
||||
private ReimbursementCrudService $crudService,
|
||||
private FileServeService $fileServeService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function underProcessing(ReimbursementUnderProcessingRequest $request): JsonResponse
|
||||
{
|
||||
$data = $this->queryService->underProcessing();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'pendingItems' => ReimbursementUnderProcessingItemResource::collection($data['pendingItems'] ?? []),
|
||||
'existingBatches' => ReimbursementBatchResource::collection($data['existingBatches'] ?? []),
|
||||
'adminUsers' => ReimbursementRecipientResource::collection($data['adminUsers'] ?? []),
|
||||
'itemsPayload' => ReimbursementUnderProcessingItemResource::collection($data['itemsPayload'] ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
public function markDonation(ReimbursementMarkDonationRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
try {
|
||||
$this->donationService->markDonation((int) $payload['expense_id'], $userId);
|
||||
} catch (RuntimeException $e) {
|
||||
$message = $e->getMessage();
|
||||
$status = str_contains($message, 'not found') ? 404 : (str_contains($message, 'already') ? 409 : 422);
|
||||
return response()->json(['ok' => false, 'message' => $message], $status);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unable to mark donation right now.'], 500);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function createBatch(ReimbursementBatchCreateRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$schoolYear = $this->context->getSchoolYear();
|
||||
$semester = $this->context->getSemester();
|
||||
|
||||
$batch = $this->batchService->createBatch($payload['title'] ?? null, $userId, $schoolYear, $semester);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'batch' => $batch,
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function updateBatchAssignment(ReimbursementBatchAssignmentRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
|
||||
$batchId = (int) ($payload['batch_id'] ?? 0);
|
||||
$batchNumber = $payload['batch_number'] ?? null;
|
||||
if ($batchId <= 0 && $batchNumber !== null && trim((string) $batchNumber) !== '') {
|
||||
$batchId = (int) $batchNumber;
|
||||
}
|
||||
|
||||
$adminIdRaw = $payload['admin_id'] ?? null;
|
||||
$adminId = ($adminIdRaw === '' || $adminIdRaw === null) ? null : (int) $adminIdRaw;
|
||||
$reimbursementId = !empty($payload['reimbursement_id']) ? (int) $payload['reimbursement_id'] : null;
|
||||
|
||||
try {
|
||||
$result = $this->assignmentService->updateAssignment(
|
||||
(int) $payload['expense_id'],
|
||||
$batchId,
|
||||
$adminId,
|
||||
$reimbursementId,
|
||||
$this->context->getSchoolYear(),
|
||||
$this->context->getSemester()
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'assignment' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
public function lockBatch(ReimbursementBatchLockRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
try {
|
||||
$this->batchService->lockBatch(
|
||||
(int) $payload['batch_id'],
|
||||
$userId,
|
||||
$this->context->getSchoolYear(),
|
||||
$this->context->getSemester()
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
$message = $e->getMessage();
|
||||
$status = str_contains($message, 'check file') ? 409 : 404;
|
||||
return response()->json(['ok' => false, 'message' => $message], $status);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unable to lock batch right now.'], 500);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'status' => 'closed']);
|
||||
}
|
||||
|
||||
public function uploadBatchAdminFile(ReimbursementBatchAdminFileRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$batchId = (int) $payload['batch_id'];
|
||||
$adminId = (int) $payload['admin_id'];
|
||||
|
||||
$batch = ReimbursementBatch::query()->find($batchId);
|
||||
if (!$batch) {
|
||||
return response()->json(['ok' => false, 'message' => 'Batch not found.'], 404);
|
||||
}
|
||||
if (strtolower((string) ($batch->status ?? '')) !== ReimbursementBatch::STATUS_OPEN) {
|
||||
return response()->json(['ok' => false, 'message' => 'Cannot upload files for a locked batch.'], 409);
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $this->fileService->storeAdminFile($batchId, $adminId, $request->file('check_file'), (int) (auth()->id() ?? 0));
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unable to store the uploaded file.'], 500);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'file' => $file,
|
||||
]);
|
||||
}
|
||||
|
||||
public function serveAdminCheckFile(FileNameRequest $request, string $name, string $mode = 'inline'): Response|JsonResponse
|
||||
{
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'];
|
||||
$meta = $this->fileServeService->meta(storage_path('uploads/reimbursements'), $name, $allowedExtensions, null);
|
||||
$disposition = strtolower(trim($mode)) === 'download' ? 'attachment' : 'inline';
|
||||
|
||||
return response(file_get_contents($meta['path']), 200, [
|
||||
'Content-Type' => $meta['mime'],
|
||||
'Content-Disposition' => $disposition . '; filename="' . $meta['download_name'] . '"',
|
||||
'Content-Length' => (string) $meta['size'],
|
||||
'ETag' => $meta['etag'],
|
||||
'Last-Modified' => $meta['last_modified'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(ReimbursementIndexRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$filters = [
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
'status' => $payload['status'] ?? null,
|
||||
'user_id' => $payload['user_id'] ?? null,
|
||||
];
|
||||
|
||||
$data = $this->queryService->index($filters);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expenses' => ReimbursementExpenseResource::collection($data['expenses'] ?? []),
|
||||
'users' => ReimbursementRecipientResource::collection($data['users'] ?? []),
|
||||
'schoolYears' => $data['schoolYears'] ?? [],
|
||||
'batchSummaries' => $data['batchSummaries'] ?? [],
|
||||
'batchDetails' => $data['batchDetails'] ?? [],
|
||||
'batchAttachments' => $data['batchAttachments'] ?? [],
|
||||
'donationBatch' => $data['donationBatch'] ?? null,
|
||||
'donationDetails' => $data['donationDetails'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendBatchEmail(ReimbursementBatchEmailRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$receiptIds = $payload['receipts'] ?? [];
|
||||
$checkIds = $payload['checks'] ?? [];
|
||||
|
||||
try {
|
||||
$ok = $this->emailService->sendBatchEmail(
|
||||
(int) $payload['batch_id'],
|
||||
$payload['recipient_email'],
|
||||
(string) ($payload['message'] ?? ''),
|
||||
$receiptIds,
|
||||
$checkIds
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 404);
|
||||
}
|
||||
|
||||
if (!$ok) {
|
||||
return response()->json(['ok' => false, 'message' => 'Failed to send email. Please try again.'], 500);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true, 'message' => 'Email sent successfully.']);
|
||||
}
|
||||
|
||||
public function export(ReimbursementExportRequest $request): Response
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$type = $payload['type'] ?? 'processed';
|
||||
|
||||
if ($type === 'under_processing') {
|
||||
$csv = $this->exportService->buildUnderProcessingCsv();
|
||||
} else {
|
||||
$csv = $this->exportService->buildProcessedCsv($payload);
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($csv) {
|
||||
$out = fopen('php://output', 'w');
|
||||
fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
foreach ($csv['rows'] as $row) {
|
||||
fputcsv($out, $row);
|
||||
}
|
||||
fclose($out);
|
||||
}, $csv['filename'], [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportBatch(ReimbursementExportBatchRequest $request): Response
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$csv = $this->exportService->buildBatchCsv((int) $payload['batch_id']);
|
||||
|
||||
return response()->streamDownload(function () use ($csv) {
|
||||
$out = fopen('php://output', 'w');
|
||||
fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
foreach ($csv['rows'] as $row) {
|
||||
fputcsv($out, $row);
|
||||
}
|
||||
fclose($out);
|
||||
}, $csv['filename'], [
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(ReimbursementStoreRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$receiptName = null;
|
||||
|
||||
if ($request->hasFile('receipt')) {
|
||||
$receiptName = $this->fileService->storeReceipt($request->file('receipt'));
|
||||
}
|
||||
|
||||
try {
|
||||
$reimb = $this->crudService->store(
|
||||
$payload,
|
||||
$userId,
|
||||
$this->context->getSchoolYear(),
|
||||
$this->context->getSemester(),
|
||||
$receiptName
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
$data = $reimb->toArray();
|
||||
$data['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reimbursement' => new ReimbursementResource($data),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function process(ReimbursementProcessRequest $request): JsonResponse
|
||||
{
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
$receiptName = null;
|
||||
|
||||
if ($request->hasFile('receipt')) {
|
||||
$receiptName = $this->fileService->storeReceipt($request->file('receipt'));
|
||||
}
|
||||
|
||||
try {
|
||||
$reimb = $this->crudService->process(
|
||||
$payload,
|
||||
$userId,
|
||||
$this->context->getSchoolYear(),
|
||||
$this->context->getSemester(),
|
||||
$receiptName
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
$data = $reimb->toArray();
|
||||
$data['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reimbursement' => new ReimbursementResource($data),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function reimbursedExpenses(): JsonResponse
|
||||
{
|
||||
$expenses = $this->queryService->reimbursedExpenses();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'expenses' => $expenses,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ReimbursementUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$reimb = Reimbursement::query()->find($id);
|
||||
if (!$reimb) {
|
||||
return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$userId = (int) (auth()->id() ?? 0);
|
||||
|
||||
$receiptName = $reimb->receipt_path;
|
||||
if ($request->hasFile('receipt')) {
|
||||
$receiptName = $this->fileService->storeReceipt($request->file('receipt'));
|
||||
}
|
||||
if (!empty($payload['remove_receipt'])) {
|
||||
$receiptName = null;
|
||||
}
|
||||
|
||||
$updated = $this->crudService->update($reimb, $payload, $userId, $receiptName);
|
||||
$data = $updated->toArray();
|
||||
$data['receipt_url'] = $this->fileService->receiptUrl($updated->receipt_path ?? null);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reimbursement' => new ReimbursementResource($data),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user