603 lines
18 KiB
PHP
603 lines
18 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
require_once __DIR__ . '/db/config.php';
|
||
|
||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||
session_start();
|
||
}
|
||
|
||
function e(?string $value): string
|
||
{
|
||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||
}
|
||
|
||
function text_length(string $value): int
|
||
{
|
||
return function_exists('mb_strlen') ? mb_strlen($value) : strlen($value);
|
||
}
|
||
|
||
function project_name(): string
|
||
{
|
||
$name = $_SERVER['PROJECT_NAME'] ?? getenv('PROJECT_NAME') ?: '';
|
||
$name = trim((string) $name);
|
||
|
||
return $name !== '' ? $name : 'Sistem Peminjaman Barang';
|
||
}
|
||
|
||
function project_description(string $fallback = ''): string
|
||
{
|
||
$description = $_SERVER['PROJECT_DESCRIPTION'] ?? getenv('PROJECT_DESCRIPTION') ?: '';
|
||
$description = trim((string) $description);
|
||
|
||
return $description !== '' ? $description : $fallback;
|
||
}
|
||
|
||
function project_image_url(): string
|
||
{
|
||
$url = $_SERVER['PROJECT_IMAGE_URL'] ?? getenv('PROJECT_IMAGE_URL') ?: '';
|
||
|
||
return trim((string) $url);
|
||
}
|
||
|
||
function render_head_meta(string $pageTitle, string $fallbackDescription = ''): string
|
||
{
|
||
$title = trim($pageTitle);
|
||
$project = project_name();
|
||
|
||
if ($project !== '' && stripos($title, $project) === false) {
|
||
$title .= ' | ' . $project;
|
||
}
|
||
|
||
$projectDescription = project_description($fallbackDescription);
|
||
$projectImageUrl = project_image_url();
|
||
|
||
ob_start();
|
||
?>
|
||
<title><?= e($title) ?></title>
|
||
<?php if ($projectDescription !== ''): ?>
|
||
<meta name="description" content="<?= e($projectDescription) ?>" />
|
||
<meta property="og:description" content="<?= e($projectDescription) ?>" />
|
||
<meta property="twitter:description" content="<?= e($projectDescription) ?>" />
|
||
<?php endif; ?>
|
||
<?php if ($projectImageUrl !== ''): ?>
|
||
<meta property="og:image" content="<?= e($projectImageUrl) ?>" />
|
||
<meta property="twitter:image" content="<?= e($projectImageUrl) ?>" />
|
||
<?php endif; ?>
|
||
<?php
|
||
|
||
return (string) ob_get_clean();
|
||
}
|
||
|
||
function asset_version(string $relativePath): string
|
||
{
|
||
$fullPath = __DIR__ . '/' . ltrim($relativePath, '/');
|
||
|
||
if (is_file($fullPath)) {
|
||
$mtime = @filemtime($fullPath);
|
||
if ($mtime !== false) {
|
||
return (string) $mtime;
|
||
}
|
||
}
|
||
|
||
return (string) time();
|
||
}
|
||
|
||
function set_flash(string $type, string $message): void
|
||
{
|
||
$_SESSION['app_flash'][] = [
|
||
'type' => $type,
|
||
'message' => $message,
|
||
];
|
||
}
|
||
|
||
function pull_flashes(): array
|
||
{
|
||
$flashes = $_SESSION['app_flash'] ?? [];
|
||
unset($_SESSION['app_flash']);
|
||
|
||
return is_array($flashes) ? $flashes : [];
|
||
}
|
||
|
||
function lending_ensure_schema(): void
|
||
{
|
||
static $schemaReady = false;
|
||
|
||
if ($schemaReady) {
|
||
return;
|
||
}
|
||
|
||
db()->exec(<<<'SQL'
|
||
CREATE TABLE IF NOT EXISTS item_loans (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
borrower_name VARCHAR(120) NOT NULL,
|
||
borrower_contact VARCHAR(120) DEFAULT NULL,
|
||
department VARCHAR(120) DEFAULT NULL,
|
||
item_name VARCHAR(150) NOT NULL,
|
||
item_code VARCHAR(60) DEFAULT NULL,
|
||
quantity INT UNSIGNED NOT NULL DEFAULT 1,
|
||
loaned_at DATE NOT NULL,
|
||
due_at DATE NOT NULL,
|
||
returned_at DATETIME DEFAULT NULL,
|
||
return_condition VARCHAR(40) DEFAULT NULL,
|
||
notes TEXT DEFAULT NULL,
|
||
return_notes TEXT DEFAULT NULL,
|
||
last_reminder_at DATETIME DEFAULT NULL,
|
||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (id),
|
||
INDEX idx_item_loans_due_returned (due_at, returned_at),
|
||
INDEX idx_item_loans_created (created_at),
|
||
INDEX idx_item_loans_item (item_name),
|
||
INDEX idx_item_loans_borrower (borrower_name)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
SQL);
|
||
|
||
$schemaReady = true;
|
||
}
|
||
|
||
function default_loan_form(): array
|
||
{
|
||
return [
|
||
'borrower_name' => '',
|
||
'borrower_contact' => '',
|
||
'department' => '',
|
||
'item_name' => '',
|
||
'item_code' => '',
|
||
'quantity' => '1',
|
||
'loaned_at' => date('Y-m-d'),
|
||
'due_at' => date('Y-m-d', strtotime('+7 days')),
|
||
'notes' => '',
|
||
];
|
||
}
|
||
|
||
function default_return_form(): array
|
||
{
|
||
return [
|
||
'returned_at' => date('Y-m-d\TH:i'),
|
||
'return_condition' => 'baik',
|
||
'return_notes' => '',
|
||
];
|
||
}
|
||
|
||
function is_valid_date(string $value): bool
|
||
{
|
||
$date = DateTimeImmutable::createFromFormat('Y-m-d', $value);
|
||
|
||
return $date instanceof DateTimeImmutable && $date->format('Y-m-d') === $value;
|
||
}
|
||
|
||
function is_valid_datetime_local(string $value): bool
|
||
{
|
||
$dateTime = DateTimeImmutable::createFromFormat('Y-m-d\TH:i', $value);
|
||
|
||
return $dateTime instanceof DateTimeImmutable && $dateTime->format('Y-m-d\TH:i') === $value;
|
||
}
|
||
|
||
function validate_loan_input(array $input): array
|
||
{
|
||
$values = default_loan_form();
|
||
$allowedKeys = array_keys($values);
|
||
|
||
foreach ($allowedKeys as $key) {
|
||
if (isset($input[$key])) {
|
||
$values[$key] = trim((string) $input[$key]);
|
||
}
|
||
}
|
||
|
||
$errors = [];
|
||
|
||
if ($values['borrower_name'] === '') {
|
||
$errors['borrower_name'] = 'Nama peminjam wajib diisi.';
|
||
} elseif (text_length($values['borrower_name']) > 120) {
|
||
$errors['borrower_name'] = 'Nama peminjam maksimal 120 karakter.';
|
||
}
|
||
|
||
if ($values['borrower_contact'] !== '' && text_length($values['borrower_contact']) > 120) {
|
||
$errors['borrower_contact'] = 'Kontak maksimal 120 karakter.';
|
||
}
|
||
|
||
if ($values['department'] !== '' && text_length($values['department']) > 120) {
|
||
$errors['department'] = 'Divisi maksimal 120 karakter.';
|
||
}
|
||
|
||
if ($values['item_name'] === '') {
|
||
$errors['item_name'] = 'Nama barang wajib diisi.';
|
||
} elseif (text_length($values['item_name']) > 150) {
|
||
$errors['item_name'] = 'Nama barang maksimal 150 karakter.';
|
||
}
|
||
|
||
if ($values['item_code'] !== '' && text_length($values['item_code']) > 60) {
|
||
$errors['item_code'] = 'Kode barang maksimal 60 karakter.';
|
||
}
|
||
|
||
if ($values['quantity'] === '' || filter_var($values['quantity'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 999]]) === false) {
|
||
$errors['quantity'] = 'Jumlah harus berupa angka 1–999.';
|
||
}
|
||
|
||
if (!is_valid_date($values['loaned_at'])) {
|
||
$errors['loaned_at'] = 'Tanggal pinjam tidak valid.';
|
||
}
|
||
|
||
if (!is_valid_date($values['due_at'])) {
|
||
$errors['due_at'] = 'Tanggal jatuh tempo tidak valid.';
|
||
}
|
||
|
||
if (!isset($errors['loaned_at']) && !isset($errors['due_at'])) {
|
||
$loanedAt = new DateTimeImmutable($values['loaned_at']);
|
||
$dueAt = new DateTimeImmutable($values['due_at']);
|
||
|
||
if ($dueAt < $loanedAt) {
|
||
$errors['due_at'] = 'Jatuh tempo harus sama atau setelah tanggal pinjam.';
|
||
}
|
||
}
|
||
|
||
if ($values['notes'] !== '' && text_length($values['notes']) > 1000) {
|
||
$errors['notes'] = 'Catatan maksimal 1000 karakter.';
|
||
}
|
||
|
||
return [$values, $errors];
|
||
}
|
||
|
||
function validate_return_input(array $input, array $loan): array
|
||
{
|
||
$values = default_return_form();
|
||
|
||
foreach (array_keys($values) as $key) {
|
||
if (isset($input[$key])) {
|
||
$values[$key] = trim((string) $input[$key]);
|
||
}
|
||
}
|
||
|
||
$errors = [];
|
||
$allowedConditions = ['baik', 'catatan', 'rusak'];
|
||
|
||
if (!is_valid_datetime_local($values['returned_at'])) {
|
||
$errors['returned_at'] = 'Waktu pengembalian tidak valid.';
|
||
}
|
||
|
||
if (!in_array($values['return_condition'], $allowedConditions, true)) {
|
||
$errors['return_condition'] = 'Pilih kondisi barang yang tersedia.';
|
||
}
|
||
|
||
if ($values['return_notes'] !== '' && text_length($values['return_notes']) > 1000) {
|
||
$errors['return_notes'] = 'Catatan pengembalian maksimal 1000 karakter.';
|
||
}
|
||
|
||
if (!isset($errors['returned_at'])) {
|
||
$returnedAt = DateTimeImmutable::createFromFormat('Y-m-d\TH:i', $values['returned_at']);
|
||
$loanedAt = new DateTimeImmutable($loan['loaned_at']);
|
||
if ($returnedAt instanceof DateTimeImmutable && $returnedAt->format('Y-m-d') < $loanedAt->format('Y-m-d')) {
|
||
$errors['returned_at'] = 'Tanggal kembali tidak boleh lebih awal dari tanggal pinjam.';
|
||
}
|
||
}
|
||
|
||
return [$values, $errors];
|
||
}
|
||
|
||
function create_loan(array $values): int
|
||
{
|
||
lending_ensure_schema();
|
||
|
||
$statement = db()->prepare(
|
||
'INSERT INTO item_loans (
|
||
borrower_name,
|
||
borrower_contact,
|
||
department,
|
||
item_name,
|
||
item_code,
|
||
quantity,
|
||
loaned_at,
|
||
due_at,
|
||
notes
|
||
) VALUES (
|
||
:borrower_name,
|
||
:borrower_contact,
|
||
:department,
|
||
:item_name,
|
||
:item_code,
|
||
:quantity,
|
||
:loaned_at,
|
||
:due_at,
|
||
:notes
|
||
)'
|
||
);
|
||
|
||
$statement->bindValue(':borrower_name', $values['borrower_name']);
|
||
$statement->bindValue(':borrower_contact', $values['borrower_contact'] !== '' ? $values['borrower_contact'] : null, PDO::PARAM_STR);
|
||
$statement->bindValue(':department', $values['department'] !== '' ? $values['department'] : null, PDO::PARAM_STR);
|
||
$statement->bindValue(':item_name', $values['item_name']);
|
||
$statement->bindValue(':item_code', $values['item_code'] !== '' ? $values['item_code'] : null, PDO::PARAM_STR);
|
||
$statement->bindValue(':quantity', (int) $values['quantity'], PDO::PARAM_INT);
|
||
$statement->bindValue(':loaned_at', $values['loaned_at']);
|
||
$statement->bindValue(':due_at', $values['due_at']);
|
||
$statement->bindValue(':notes', $values['notes'] !== '' ? $values['notes'] : null, PDO::PARAM_STR);
|
||
$statement->execute();
|
||
|
||
return (int) db()->lastInsertId();
|
||
}
|
||
|
||
function record_return(int $loanId, array $values): void
|
||
{
|
||
lending_ensure_schema();
|
||
|
||
$returnedAt = DateTimeImmutable::createFromFormat('Y-m-d\TH:i', $values['returned_at']);
|
||
|
||
$statement = db()->prepare(
|
||
'UPDATE item_loans
|
||
SET returned_at = :returned_at,
|
||
return_condition = :return_condition,
|
||
return_notes = :return_notes
|
||
WHERE id = :id AND returned_at IS NULL'
|
||
);
|
||
|
||
$statement->bindValue(':returned_at', $returnedAt instanceof DateTimeImmutable ? $returnedAt->format('Y-m-d H:i:s') : null, PDO::PARAM_STR);
|
||
$statement->bindValue(':return_condition', $values['return_condition']);
|
||
$statement->bindValue(':return_notes', $values['return_notes'] !== '' ? $values['return_notes'] : null, PDO::PARAM_STR);
|
||
$statement->bindValue(':id', $loanId, PDO::PARAM_INT);
|
||
$statement->execute();
|
||
}
|
||
|
||
function mark_reminder_sent(int $loanId): void
|
||
{
|
||
lending_ensure_schema();
|
||
|
||
$statement = db()->prepare(
|
||
'UPDATE item_loans
|
||
SET last_reminder_at = NOW()
|
||
WHERE id = :id AND returned_at IS NULL'
|
||
);
|
||
$statement->bindValue(':id', $loanId, PDO::PARAM_INT);
|
||
$statement->execute();
|
||
}
|
||
|
||
function dashboard_counts(): array
|
||
{
|
||
lending_ensure_schema();
|
||
|
||
$statement = db()->query(
|
||
'SELECT
|
||
COUNT(*) AS total_loans,
|
||
SUM(CASE WHEN returned_at IS NULL THEN 1 ELSE 0 END) AS active_loans,
|
||
SUM(CASE WHEN returned_at IS NULL AND due_at < CURDATE() THEN 1 ELSE 0 END) AS overdue_loans,
|
||
SUM(CASE WHEN returned_at IS NULL AND due_at = CURDATE() THEN 1 ELSE 0 END) AS due_today_loans,
|
||
SUM(CASE WHEN returned_at IS NOT NULL THEN 1 ELSE 0 END) AS returned_loans
|
||
FROM item_loans'
|
||
);
|
||
|
||
$row = $statement->fetch() ?: [];
|
||
|
||
return [
|
||
'total_loans' => (int) ($row['total_loans'] ?? 0),
|
||
'active_loans' => (int) ($row['active_loans'] ?? 0),
|
||
'overdue_loans' => (int) ($row['overdue_loans'] ?? 0),
|
||
'due_today_loans' => (int) ($row['due_today_loans'] ?? 0),
|
||
'returned_loans' => (int) ($row['returned_loans'] ?? 0),
|
||
];
|
||
}
|
||
|
||
function fetch_priority_loans(int $limit = 4): array
|
||
{
|
||
lending_ensure_schema();
|
||
|
||
$limit = max(1, min($limit, 12));
|
||
|
||
$statement = db()->prepare(
|
||
'SELECT *
|
||
FROM item_loans
|
||
WHERE returned_at IS NULL AND due_at <= DATE_ADD(CURDATE(), INTERVAL 2 DAY)
|
||
ORDER BY CASE WHEN due_at < CURDATE() THEN 0 ELSE 1 END ASC, due_at ASC, created_at DESC
|
||
LIMIT ' . $limit
|
||
);
|
||
$statement->execute();
|
||
|
||
return array_map('decorate_loan', $statement->fetchAll() ?: []);
|
||
}
|
||
|
||
function fetch_loans(string $filter = 'all', string $search = '', int $limit = 50): array
|
||
{
|
||
lending_ensure_schema();
|
||
|
||
$allowedFilters = ['all', 'active', 'overdue', 'returned'];
|
||
if (!in_array($filter, $allowedFilters, true)) {
|
||
$filter = 'all';
|
||
}
|
||
|
||
$limit = max(1, min($limit, 200));
|
||
$params = [];
|
||
$conditions = [];
|
||
|
||
if ($filter === 'active') {
|
||
$conditions[] = 'returned_at IS NULL AND due_at >= CURDATE()';
|
||
} elseif ($filter === 'overdue') {
|
||
$conditions[] = 'returned_at IS NULL AND due_at < CURDATE()';
|
||
} elseif ($filter === 'returned') {
|
||
$conditions[] = 'returned_at IS NOT NULL';
|
||
}
|
||
|
||
$search = trim($search);
|
||
if ($search !== '') {
|
||
$conditions[] = '(borrower_name LIKE :term OR item_name LIKE :term OR COALESCE(item_code, \'\') LIKE :term OR COALESCE(department, \'\') LIKE :term)';
|
||
$params[':term'] = '%' . $search . '%';
|
||
}
|
||
|
||
$sql = 'SELECT * FROM item_loans';
|
||
if ($conditions !== []) {
|
||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||
}
|
||
$sql .= ' ORDER BY CASE WHEN returned_at IS NULL AND due_at < CURDATE() THEN 0 ELSE 1 END ASC, CASE WHEN returned_at IS NULL THEN 0 ELSE 1 END ASC, due_at ASC, created_at DESC LIMIT ' . $limit;
|
||
|
||
$statement = db()->prepare($sql);
|
||
foreach ($params as $param => $value) {
|
||
$statement->bindValue($param, $value, PDO::PARAM_STR);
|
||
}
|
||
$statement->execute();
|
||
|
||
return array_map('decorate_loan', $statement->fetchAll() ?: []);
|
||
}
|
||
|
||
function fetch_loan(int $loanId): ?array
|
||
{
|
||
lending_ensure_schema();
|
||
|
||
$statement = db()->prepare('SELECT * FROM item_loans WHERE id = :id LIMIT 1');
|
||
$statement->bindValue(':id', $loanId, PDO::PARAM_INT);
|
||
$statement->execute();
|
||
|
||
$loan = $statement->fetch();
|
||
|
||
return $loan ? decorate_loan($loan) : null;
|
||
}
|
||
|
||
function loan_reference(int $loanId): string
|
||
{
|
||
return 'PJM-' . str_pad((string) $loanId, 5, '0', STR_PAD_LEFT);
|
||
}
|
||
|
||
function loan_status_key(array $loan): string
|
||
{
|
||
if (!empty($loan['returned_at'])) {
|
||
return 'returned';
|
||
}
|
||
|
||
$today = new DateTimeImmutable('today');
|
||
$dueAt = new DateTimeImmutable($loan['due_at']);
|
||
|
||
if ($dueAt < $today) {
|
||
return 'overdue';
|
||
}
|
||
|
||
return 'active';
|
||
}
|
||
|
||
function loan_status_label(array $loan): string
|
||
{
|
||
return match (loan_status_key($loan)) {
|
||
'overdue' => 'Terlambat',
|
||
'returned' => 'Dikembalikan',
|
||
default => 'Aktif',
|
||
};
|
||
}
|
||
|
||
function loan_status_badge_class(array $loan): string
|
||
{
|
||
return match (loan_status_key($loan)) {
|
||
'overdue' => 'badge-status-overdue',
|
||
'returned' => 'badge-status-returned',
|
||
default => 'badge-status-active',
|
||
};
|
||
}
|
||
|
||
function format_date_id(?string $value, bool $withTime = false): string
|
||
{
|
||
if (!$value) {
|
||
return '—';
|
||
}
|
||
|
||
try {
|
||
$date = new DateTimeImmutable($value);
|
||
} catch (Throwable $exception) {
|
||
return '—';
|
||
}
|
||
|
||
$months = [
|
||
1 => 'Jan',
|
||
2 => 'Feb',
|
||
3 => 'Mar',
|
||
4 => 'Apr',
|
||
5 => 'Mei',
|
||
6 => 'Jun',
|
||
7 => 'Jul',
|
||
8 => 'Agu',
|
||
9 => 'Sep',
|
||
10 => 'Okt',
|
||
11 => 'Nov',
|
||
12 => 'Des',
|
||
];
|
||
|
||
$formatted = $date->format('d') . ' ' . $months[(int) $date->format('n')] . ' ' . $date->format('Y');
|
||
|
||
if ($withTime) {
|
||
$formatted .= ' · ' . $date->format('H:i');
|
||
}
|
||
|
||
return $formatted;
|
||
}
|
||
|
||
function loan_delay_days(array $loan): int
|
||
{
|
||
$dueAt = new DateTimeImmutable($loan['due_at']);
|
||
|
||
if (!empty($loan['returned_at'])) {
|
||
$endDate = (new DateTimeImmutable($loan['returned_at']))->setTime(0, 0);
|
||
} else {
|
||
$endDate = new DateTimeImmutable('today');
|
||
}
|
||
|
||
return (int) $dueAt->diff($endDate)->format('%r%a');
|
||
}
|
||
|
||
function loan_due_note(array $loan): string
|
||
{
|
||
$delayDays = loan_delay_days($loan);
|
||
|
||
if (loan_status_key($loan) === 'returned') {
|
||
if ($delayDays > 0) {
|
||
return 'Dikembalikan ' . $delayDays . ' hari lewat tempo';
|
||
}
|
||
|
||
return 'Dikembalikan tepat waktu';
|
||
}
|
||
|
||
if ($delayDays > 0) {
|
||
return 'Terlambat ' . $delayDays . ' hari';
|
||
}
|
||
|
||
if ($delayDays === 0) {
|
||
return 'Jatuh tempo hari ini';
|
||
}
|
||
|
||
return 'Jatuh tempo ' . abs($delayDays) . ' hari lagi';
|
||
}
|
||
|
||
function loan_condition_label(?string $value): string
|
||
{
|
||
return match ($value) {
|
||
'catatan' => 'Ada catatan',
|
||
'rusak' => 'Rusak',
|
||
'baik' => 'Baik',
|
||
default => '—',
|
||
};
|
||
}
|
||
|
||
function build_reminder_message(array $loan): string
|
||
{
|
||
$name = trim((string) ($loan['borrower_name'] ?? ''));
|
||
$item = trim((string) ($loan['item_name'] ?? ''));
|
||
$qty = (int) ($loan['quantity'] ?? 1);
|
||
$dueAt = format_date_id((string) ($loan['due_at'] ?? ''));
|
||
$reference = loan_reference((int) ($loan['id'] ?? 0));
|
||
|
||
return "Halo {$name}, ini pengingat pengembalian barang {$item} (qty {$qty}) dengan jatuh tempo {$dueAt}. Mohon dikembalikan ke tim operasional. Ref {$reference}.";
|
||
}
|
||
|
||
function decorate_loan(array $loan): array
|
||
{
|
||
$loan['status_key'] = loan_status_key($loan);
|
||
$loan['status_label'] = loan_status_label($loan);
|
||
$loan['status_badge_class'] = loan_status_badge_class($loan);
|
||
$loan['reference'] = loan_reference((int) $loan['id']);
|
||
$loan['due_note'] = loan_due_note($loan);
|
||
$loan['loaned_at_label'] = format_date_id($loan['loaned_at'] ?? null);
|
||
$loan['due_at_label'] = format_date_id($loan['due_at'] ?? null);
|
||
$loan['returned_at_label'] = format_date_id($loan['returned_at'] ?? null, true);
|
||
$loan['last_reminder_at_label'] = format_date_id($loan['last_reminder_at'] ?? null, true);
|
||
$loan['return_condition_label'] = loan_condition_label($loan['return_condition'] ?? null);
|
||
$loan['reminder_message'] = build_reminder_message($loan);
|
||
$loan['is_active'] = $loan['status_key'] === 'active';
|
||
$loan['is_overdue'] = $loan['status_key'] === 'overdue';
|
||
$loan['is_returned'] = $loan['status_key'] === 'returned';
|
||
|
||
return $loan;
|
||
}
|