648 lines
24 KiB
PHP
Executable File
648 lines
24 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Models\Configuration;
|
|
use App\Models\DiscountUsage;
|
|
use App\Models\DiscountVoucher;
|
|
use App\Models\Invoice;
|
|
use App\Models\Payment;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\URL;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class DiscountController extends BaseApiController
|
|
{
|
|
protected DiscountVoucher $discount;
|
|
protected DiscountUsage $discountUsage;
|
|
protected Invoice $invoice;
|
|
protected User $user;
|
|
protected Payment $payment;
|
|
protected Configuration $config;
|
|
|
|
protected string $schoolYear;
|
|
protected ?string $semester;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->discount = model(DiscountVoucher::class);
|
|
$this->discountUsage = model(DiscountUsage::class);
|
|
$this->invoice = model(Invoice::class);
|
|
$this->user = model(User::class);
|
|
$this->payment = model(Payment::class);
|
|
$this->config = model(Configuration::class);
|
|
|
|
$this->schoolYear = $this->config->getConfig('school_year') ?? '';
|
|
$sem = $this->config->getConfig('semester');
|
|
$this->semester = is_string($sem) && $sem !== '' ? $sem : null;
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
$activeOnly = $this->request->getGet('active') === 'true';
|
|
$query = $this->discount->newQuery();
|
|
|
|
if ($activeOnly) {
|
|
$today = now()->toDateString();
|
|
$query->where('is_active', 1)
|
|
->where(function ($q) use ($today) {
|
|
$q->whereNull('valid_from')->orWhere('valid_from', '<=', $today);
|
|
})
|
|
->where(function ($q) use ($today) {
|
|
$q->whereNull('valid_until')->orWhere('valid_until', '>=', $today);
|
|
});
|
|
}
|
|
|
|
$discounts = $query->orderBy('created_at', 'DESC')->get()->toArray();
|
|
return $this->success($discounts, 'Discount vouchers retrieved successfully');
|
|
}
|
|
|
|
public function getByCode($code = null)
|
|
{
|
|
$discount = $this->findActiveDiscount($code);
|
|
|
|
if (!$discount) {
|
|
return $this->respondError('Discount voucher not found or expired', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
if ($this->isUsageLimitReached($discount)) {
|
|
return $this->respondError('Discount voucher has reached maximum usage limit', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
return $this->success($discount->toArray(), 'Discount voucher retrieved successfully');
|
|
}
|
|
|
|
public function apply()
|
|
{
|
|
$user = $this->getCurrentUser();
|
|
if (!$user) {
|
|
return $this->respondError('Unauthorized', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
$data = $this->payloadData();
|
|
if (empty($data)) {
|
|
return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$rules = [
|
|
'code' => 'required',
|
|
'invoice_id' => 'required|integer',
|
|
];
|
|
|
|
$errors = $this->validateRequest($data, $rules);
|
|
if (!empty($errors)) {
|
|
return $this->respondValidationError($errors);
|
|
}
|
|
|
|
$code = strtoupper(trim((string) ($data['code'] ?? '')));
|
|
$discount = $this->findActiveDiscount($code);
|
|
if (!$discount) {
|
|
return $this->respondError('Invalid or expired discount voucher', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
if ($this->isUsageLimitReached($discount)) {
|
|
return $this->respondError('Discount voucher has reached maximum usage limit', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$exists = $this->discountUsage->newQuery()
|
|
->where('voucher_id', $discount->id)
|
|
->where('invoice_id', $data['invoice_id'])
|
|
->exists();
|
|
|
|
if ($exists) {
|
|
return $this->respondError('Discount voucher already applied to this invoice', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
try {
|
|
$usage = $this->discountUsage->create([
|
|
'voucher_id' => $discount->id,
|
|
'invoice_id' => $data['invoice_id'],
|
|
'used_by' => $user->id,
|
|
'used_at' => now()->toDateTimeString(),
|
|
]);
|
|
|
|
$discount->increment('times_used');
|
|
|
|
return $this->success([
|
|
'discount' => $discount->fresh()->toArray(),
|
|
'applied' => true,
|
|
], 'Discount voucher applied successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Discount apply error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to apply discount voucher', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
public function parents()
|
|
{
|
|
$parents = $this->user->getParents();
|
|
$result = [];
|
|
foreach ($parents as $parent) {
|
|
$discount = DB::table('discount_usages as du')
|
|
->selectRaw('COALESCE(SUM(du.discount_amount),0) AS total_discount')
|
|
->join('invoices as i', 'i.id', '=', 'du.invoice_id')
|
|
->where('i.parent_id', $parent['id'])
|
|
->where('i.school_year', $this->schoolYear)
|
|
->first();
|
|
|
|
$totalDiscount = (float) ($discount->total_discount ?? 0);
|
|
$result[] = [
|
|
'id' => $parent['id'],
|
|
'firstname' => $parent['firstname'],
|
|
'lastname' => $parent['lastname'],
|
|
'email' => $parent['email'],
|
|
'school_id' => $parent['school_id'] ?? null,
|
|
'total_discount' => $totalDiscount,
|
|
'has_discount' => $totalDiscount > 0 ? 1 : 0,
|
|
];
|
|
}
|
|
|
|
return $this->success($result, 'Parent discounts retrieved');
|
|
}
|
|
|
|
public function applyVoucher()
|
|
{
|
|
$data = $this->payloadData();
|
|
$rules = [
|
|
'voucher_id' => 'required|integer',
|
|
'parent_ids' => 'required',
|
|
];
|
|
|
|
$errors = $this->validateRequest($data, $rules);
|
|
if (!empty($errors)) {
|
|
return $this->respondValidationError($errors);
|
|
}
|
|
|
|
$voucherId = (int) $data['voucher_id'];
|
|
$parentIds = $this->normalizeParentIds($data['parent_ids']);
|
|
if (empty($parentIds)) {
|
|
return $this->respondError('At least one parent is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$voucher = $this->discount->find($voucherId);
|
|
if (!$voucher) {
|
|
return $this->respondError('Voucher not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
$remainingUses = max(0, (int) ($voucher['max_uses'] ?? 1) - (int) ($voucher['times_used'] ?? 0));
|
|
if ($remainingUses <= 0) {
|
|
return $this->respondError('Voucher has reached its maximum allowed uses', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$description = $this->normalizeDescription($voucher['description'] ?? '');
|
|
$now = utc_now();
|
|
$fullyCoveredInvoiceIds = [];
|
|
$appliedInvoices = [];
|
|
$errors = [];
|
|
$usedCount = 0;
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
foreach ($parentIds as $parentId) {
|
|
$invoices = $this->invoice->getInvoicesByUserId($parentId, $this->schoolYear);
|
|
if (empty($invoices)) {
|
|
$errors[] = "Parent {$parentId} has no invoices for {$this->schoolYear}.";
|
|
continue;
|
|
}
|
|
|
|
foreach ($invoices as $invoice) {
|
|
if ($invoice['balance'] <= 0) {
|
|
continue;
|
|
}
|
|
if ($remainingUses <= 0) {
|
|
break 2;
|
|
}
|
|
|
|
$initialPreBalance = $this->getCurrentInvoiceBalance($invoice['id'], $this->schoolYear);
|
|
|
|
$already = $this->discountUsage->newQuery()
|
|
->where('voucher_id', $voucherId)
|
|
->where('invoice_id', $invoice['id'])
|
|
->exists();
|
|
if ($already) {
|
|
continue;
|
|
}
|
|
|
|
$rawDiscount = ($voucher['discount_type'] === 'percent')
|
|
? round(((float) $invoice['total_amount'] * (float) $voucher['discount_value']) / 100, 2)
|
|
: (float) $voucher['discount_value'];
|
|
|
|
$discountAmount = min($rawDiscount, $initialPreBalance);
|
|
if ($discountAmount <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$this->db->table('discount_usages')->insert([
|
|
'voucher_id' => $voucherId,
|
|
'invoice_id' => $invoice['id'],
|
|
'parent_id' => $parentId,
|
|
'discount_amount' => $discountAmount,
|
|
'description' => $description,
|
|
'school_year' => $this->schoolYear,
|
|
'semester' => $this->semester,
|
|
'updated_by' => $this->getCurrentUserId() ?? 0,
|
|
'used_at' => $now,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
]);
|
|
|
|
$newBalance = max(0.0, round(((float) $invoice['balance']) - $discountAmount, 2));
|
|
$this->invoice->update($invoice['id'], [
|
|
'balance' => $newBalance,
|
|
'has_discount' => 1,
|
|
'updated_at' => $now,
|
|
]);
|
|
|
|
$postBalance = max(0.0, round($initialPreBalance - $discountAmount, 2));
|
|
$currentBalance = $this->getCurrentInvoiceBalance($invoice['id'], $this->schoolYear);
|
|
|
|
$this->discount->newQuery()
|
|
->where('id', $voucherId)
|
|
->set('times_used', 'times_used + 1', false)
|
|
->update();
|
|
|
|
[$eventData, $studentData] = $this->buildPaymentEventData(
|
|
$invoice['id'],
|
|
(string) $voucherId,
|
|
$discountAmount,
|
|
'discount',
|
|
$now,
|
|
'',
|
|
0,
|
|
$initialPreBalance,
|
|
$postBalance
|
|
);
|
|
event('paymentReceived', [$eventData, $studentData]);
|
|
|
|
$epsilon = 0.01;
|
|
$fullyCovered = (abs($discountAmount - $initialPreBalance) <= $epsilon)
|
|
|| ($postBalance <= $epsilon)
|
|
|| ($currentBalance <= $epsilon);
|
|
|
|
if ($fullyCovered) {
|
|
$fullyCoveredInvoiceIds[] = (int) $invoice['id'];
|
|
}
|
|
|
|
$appliedInvoices[] = (int) $invoice['id'];
|
|
$remainingUses--;
|
|
$usedCount++;
|
|
|
|
if ($remainingUses <= 0) {
|
|
$this->discount->update($voucherId, [
|
|
'is_active' => 0,
|
|
'updated_at' => $now,
|
|
]);
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
DB::commit();
|
|
} catch (\Throwable $e) {
|
|
DB::rollBack();
|
|
log_message('error', 'Voucher application failed: ' . $e->getMessage());
|
|
return $this->respondError('Voucher application failed. Transaction rolled back.', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
$enrollmentUpdates = 0;
|
|
foreach (array_unique($fullyCoveredInvoiceIds) as $invoiceId) {
|
|
try {
|
|
$enrollmentUpdates += $this->updateEnrollmentStatusIfPaid($invoiceId);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'updateEnrollmentStatusIfPaid failed for invoice {iid}: {err}', [
|
|
'iid' => $invoiceId,
|
|
'err' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $this->success([
|
|
'applied_invoices' => array_values(array_unique($appliedInvoices)),
|
|
'errors' => $errors,
|
|
'enrollments' => $enrollmentUpdates,
|
|
'used_count' => $usedCount,
|
|
], 'Voucher applied successfully');
|
|
}
|
|
|
|
public function createVoucher()
|
|
{
|
|
$data = $this->payloadData();
|
|
$rules = [
|
|
'code' => 'required|string',
|
|
'discount_type' => 'required|in:percent,fixed',
|
|
'discount_value' => 'required|numeric',
|
|
];
|
|
|
|
$errors = $this->validateRequest($data, $rules);
|
|
if (!empty($errors)) {
|
|
return $this->respondValidationError($errors);
|
|
}
|
|
|
|
$code = strtoupper(preg_replace('/[^A-Z0-9\-]/i', '', trim((string) $data['code'])));
|
|
if ($code === '') {
|
|
return $this->respondError('Voucher code is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$payload = [
|
|
'code' => $code,
|
|
'discount_type' => strtolower((string) $data['discount_type']),
|
|
'discount_value' => (float) $data['discount_value'],
|
|
'max_uses' => isset($data['max_uses']) && $data['max_uses'] !== '' ? (int) $data['max_uses'] : null,
|
|
'valid_from' => $this->normalizeDate($data['valid_from'] ?? null),
|
|
'valid_until' => $this->normalizeDate($data['valid_until'] ?? null),
|
|
'is_active' => isset($data['is_active']) ? (bool) $data['is_active'] : true,
|
|
'description' => trim((string) ($data['description'] ?? '')),
|
|
];
|
|
|
|
if ($payload['valid_from'] && $payload['valid_until'] && $payload['valid_from'] > $payload['valid_until']) {
|
|
return $this->respondError('Valid until must be the same as or after valid from', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
try {
|
|
$voucher = $this->discount->create($payload);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Voucher creation failed: ' . $e->getMessage());
|
|
return $this->respondError('Failed to save voucher: ' . $e->getMessage());
|
|
}
|
|
|
|
return $this->respondCreated($voucher->toArray(), 'Voucher created successfully');
|
|
}
|
|
|
|
public function editVoucher($id)
|
|
{
|
|
$voucher = $this->discount->find($id);
|
|
if (!$voucher) {
|
|
return $this->respondError('Voucher not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
$data = $this->payloadData();
|
|
if (empty($data)) {
|
|
return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$update = [];
|
|
foreach (['code', 'discount_type', 'discount_value', 'max_uses', 'valid_from', 'valid_until', 'is_active', 'description'] as $field) {
|
|
if (array_key_exists($field, $data)) {
|
|
$value = $data[$field];
|
|
if (in_array($field, ['valid_from', 'valid_until'], true)) {
|
|
$value = $this->normalizeDate($value);
|
|
}
|
|
$update[$field] = $value;
|
|
}
|
|
}
|
|
|
|
if (!empty($update)) {
|
|
$this->discount->update($id, $update);
|
|
}
|
|
|
|
return $this->success($this->discount->find($id), 'Voucher updated successfully');
|
|
}
|
|
|
|
protected function findActiveDiscount(?string $code)
|
|
{
|
|
if (empty($code)) {
|
|
return null;
|
|
}
|
|
|
|
$today = now()->toDateString();
|
|
return $this->discount->newQuery()
|
|
->where('code', $code)
|
|
->where('is_active', 1)
|
|
->where(function ($q) use ($today) {
|
|
$q->whereNull('valid_from')->orWhere('valid_from', '<=', $today);
|
|
})
|
|
->where(function ($q) use ($today) {
|
|
$q->whereNull('valid_until')->orWhere('valid_until', '>=', $today);
|
|
})
|
|
->first();
|
|
}
|
|
|
|
protected function isUsageLimitReached($discount): bool
|
|
{
|
|
if (!$discount->max_uses) {
|
|
return false;
|
|
}
|
|
return isset($discount->times_used) && $discount->times_used >= $discount->max_uses;
|
|
}
|
|
|
|
private function normalizeParentIds($input): array
|
|
{
|
|
$values = [];
|
|
if (is_string($input)) {
|
|
$decoded = json_decode($input, true);
|
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
|
$values = $decoded;
|
|
} else {
|
|
$values = [$input];
|
|
}
|
|
} elseif (is_array($input)) {
|
|
$values = $input;
|
|
}
|
|
|
|
return array_values(array_filter(array_map('intval', $values), fn($id) => $id > 0));
|
|
}
|
|
|
|
private function normalizeDescription(?string $description): string
|
|
{
|
|
if ($description === null) {
|
|
return '';
|
|
}
|
|
$description = trim($description);
|
|
return preg_replace(
|
|
'/^Auto-generated on \d{4}-\d{2}-\d{2}\s*[—-]\s*reason:\s*/i',
|
|
'',
|
|
$description
|
|
);
|
|
}
|
|
|
|
private function normalizeDate($value): ?string
|
|
{
|
|
if ($value === null || $value === '') {
|
|
return null;
|
|
}
|
|
|
|
$date = preg_replace('/[^0-9\-]/', '', (string) $value);
|
|
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) ? $date : null;
|
|
}
|
|
|
|
private function getCurrentInvoiceBalance($invoiceId, $schoolYear): float
|
|
{
|
|
$invoice = $this->invoice->find($invoiceId);
|
|
if (!$invoice) {
|
|
return 0.0;
|
|
}
|
|
|
|
$qb = $this->payment
|
|
->select('COALESCE(SUM(paid_amount),0) AS total_paid')
|
|
->where('invoice_id', $invoiceId)
|
|
->where('school_year', $schoolYear);
|
|
|
|
$table = $this->payment->table;
|
|
if ($this->db->getSchemaBuilder()->hasColumn($table, 'status')) {
|
|
$qb->where(function ($q) {
|
|
$q->whereNotIn('status', ['void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled'])
|
|
->orWhereNull('status');
|
|
});
|
|
}
|
|
if ($this->db->getSchemaBuilder()->hasColumn($table, 'is_void')) {
|
|
$qb->where(function ($q) {
|
|
$q->where('is_void', 0)
|
|
->orWhereNull('is_void');
|
|
});
|
|
}
|
|
|
|
$rowPaid = $qb->first();
|
|
$totalPaid = (float) ($rowPaid['total_paid'] ?? 0);
|
|
|
|
$rowDisc = DB::table('discount_usages as du')
|
|
->selectRaw('COALESCE(SUM(du.discount_amount),0) AS total_disc')
|
|
->join('invoices as i', 'i.id', '=', 'du.invoice_id')
|
|
->where('du.invoice_id', $invoiceId)
|
|
->where('i.school_year', $schoolYear)
|
|
->first();
|
|
$totalDisc = (float) ($rowDisc->total_disc ?? 0);
|
|
|
|
$rowRefund = DB::table('refunds')
|
|
->selectRaw('COALESCE(SUM(refund_paid_amount),0) AS total_refund_paid')
|
|
->where('invoice_id', $invoiceId)
|
|
->where('school_year', $schoolYear)
|
|
->whereIn('status', ['Partial', 'Paid'])
|
|
->first();
|
|
$totalRefundPaid = (float) ($rowRefund->total_refund_paid ?? 0);
|
|
|
|
$total = (float) ($invoice['total_amount'] ?? 0);
|
|
return max(0.0, round($total - $totalPaid - $totalDisc - $totalRefundPaid, 2));
|
|
}
|
|
|
|
private function updateEnrollmentStatusIfPaid(int $invoiceId): int
|
|
{
|
|
$invoice = $this->invoice->find($invoiceId);
|
|
if (!$invoice) {
|
|
log_message('warning', 'Invoice not found: {id}', ['id' => $invoiceId]);
|
|
return 0;
|
|
}
|
|
|
|
$total = (float) ($invoice['total_amount'] ?? 0);
|
|
$balance = (float) ($invoice['balance'] ?? 0);
|
|
if (!($total > 0 && $balance < $total)) {
|
|
log_message('info', 'No payment yet. Skipping enrollment update. Invoice #{id}', ['id' => $invoiceId]);
|
|
return 0;
|
|
}
|
|
|
|
$parentId = (int) ($invoice['parent_id'] ?? 0);
|
|
if ($parentId <= 0 || $this->schoolYear === '') {
|
|
log_message('warning', 'Missing parent_id/school_year for invoice #{id}', ['id' => $invoiceId]);
|
|
return 0;
|
|
}
|
|
|
|
$paidEnrollmentIds = [];
|
|
try {
|
|
$rows = DB::table('invoice_items')
|
|
->select('enrollment_id')
|
|
->where('invoice_id', $invoiceId)
|
|
->whereNotNull('enrollment_id')
|
|
->get()
|
|
->pluck('enrollment_id')
|
|
->map('intval')
|
|
->filter(fn($eid) => $eid > 0)
|
|
->unique()
|
|
->values()
|
|
->toArray();
|
|
$paidEnrollmentIds = $rows;
|
|
} catch (\Throwable $e) {
|
|
// ignore
|
|
}
|
|
|
|
$builder = DB::table('enrollments')
|
|
->where('parent_id', $parentId)
|
|
->where('school_year', $this->schoolYear)
|
|
->where(function ($query) {
|
|
$query->where('enrollment_status', 'payment pending')
|
|
->orWhere('enrollment_status', 'Payment pending')
|
|
->orWhere('enrollment_status', 'Payment Pending')
|
|
->orWhere('enrollment_status', 'PAYMENT PENDING')
|
|
->orWhereRaw("LOWER(TRIM(REPLACE(enrollment_status, CHAR(160), ' '))) = 'payment pending'");
|
|
});
|
|
|
|
if ($this->semester !== null) {
|
|
$builder->where('semester', $this->semester);
|
|
}
|
|
if (!empty($paidEnrollmentIds)) {
|
|
$builder->whereIn('id', $paidEnrollmentIds);
|
|
}
|
|
|
|
$toUpdateIds = $builder->pluck('id')->map('intval')->toArray();
|
|
if (empty($toUpdateIds)) {
|
|
return 0;
|
|
}
|
|
|
|
$updated = DB::table('enrollments')
|
|
->whereIn('id', $toUpdateIds)
|
|
->update([
|
|
'enrollment_status' => 'enrolled',
|
|
'enrollment_date' => local_date(utc_now(), 'Y-m-d'),
|
|
]);
|
|
|
|
return $updated;
|
|
}
|
|
|
|
private function buildPaymentEventData(
|
|
int $invoiceId,
|
|
string $transactionId,
|
|
float $amount,
|
|
string $paymentMethod,
|
|
string $paymentDate,
|
|
?string $checkNumber,
|
|
int $installmentSeq,
|
|
float $preBalance,
|
|
float $postBalance
|
|
): array {
|
|
$invoice = $this->invoice->find($invoiceId);
|
|
if (!$invoice) {
|
|
throw new \RuntimeException("Invoice {$invoiceId} not found");
|
|
}
|
|
|
|
$parentRow = DB::table('users')
|
|
->select('id', 'firstname', 'lastname', 'email')
|
|
->where('id', $invoice['parent_id'])
|
|
->first();
|
|
|
|
if (!$parentRow) {
|
|
throw new \RuntimeException("Parent not found for invoice {$invoiceId}");
|
|
}
|
|
|
|
$studentRows = DB::table('students as s')
|
|
->select('s.id', 's.firstname', 's.lastname', 'sc.class_section_id')
|
|
->join('student_class as sc', 'sc.student_id', '=', 's.id')
|
|
->where('s.parent_id', $invoice['parent_id'])
|
|
->where('sc.school_year', $this->schoolYear)
|
|
->when($this->semester !== null, fn($q) => $q->where('sc.semester', $this->semester))
|
|
->get()
|
|
->toArray();
|
|
|
|
$data = [
|
|
'user_id' => (int) $parentRow->id,
|
|
'email' => $parentRow->email,
|
|
'firstname' => $parentRow->firstname,
|
|
'lastname' => $parentRow->lastname,
|
|
'school_year' => $this->schoolYear,
|
|
'semester' => $this->semester,
|
|
'invoice_id' => $invoiceId,
|
|
'transaction_id' => $transactionId,
|
|
'payment_date' => $paymentDate,
|
|
'amount' => $amount,
|
|
'method' => $paymentMethod,
|
|
'check_number' => $checkNumber,
|
|
'installment_seq' => $installmentSeq,
|
|
'invoice_total' => (float) $invoice['total_amount'],
|
|
'pre_balance' => $preBalance,
|
|
'post_balance' => $postBalance,
|
|
'portalLink' => URL::to('/parent/invoices/' . $invoiceId),
|
|
];
|
|
|
|
return [$data, $studentRows];
|
|
}
|
|
}
|