prepare('SELECT * FROM users WHERE username = :u'); $stmt->execute([':u' => $username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password_hash'])) { $_SESSION['uid'] = (int)$user['id']; header('Location: index.php'); exit; } flash('error', '用户名或密码错误'); header('Location: index.php?action=login'); exit; } $flash = getFlash(); ?> 金牛集团贷款档案管理系统 - 登录

金牛集团贷款档案管理系统

请输入账号密码登录系统

金牛集团贷款档案管理系统
金牛集团贷款档案管理系统
() 返回上页 返回列表 个人设置 用户管理 删除审核 退出
HTML; } function fetchDirectories(PDO $pdo): array { return $pdo->query('SELECT * FROM directories ORDER BY level ASC, id ASC')->fetchAll(); } function safePathPart(string $name): string { $name = trim($name); $name = preg_replace('/[\/\\\\:\*\?"<>\|]+/u', '_', $name); $name = preg_replace('/\s+/u', '_', (string)$name); $name = trim((string)$name, '._'); return $name !== '' ? $name : 'unnamed'; } function buildArchiveUploadSubdir(PDO $pdo, int $archiveId): string { $stmt = $pdo->prepare('SELECT id,name,directory_id FROM archives WHERE id=:id'); $stmt->execute([':id' => $archiveId]); $archive = $stmt->fetch(); if (!$archive) { return 'unknown/archive_' . $archiveId; } $dirs = fetchDirectories($pdo); $map = []; foreach ($dirs as $d) { $map[(int)$d['id']] = $d; } $parts = []; $cur = (int)$archive['directory_id']; while (isset($map[$cur])) { $parts[] = safePathPart((string)$map[$cur]['name']); $pid = $map[$cur]['parent_id']; if ($pid === null) { break; } $cur = (int)$pid; } $parts = array_reverse($parts); $parts[] = 'archive_' . (int)$archive['id'] . '_' . safePathPart((string)$archive['name']); return implode('/', $parts); } function maskUsername(string $username): string { $len = mb_strlen($username); if ($len <= 2) { return $username; } return mb_substr($username, 0, 1) . '***' . mb_substr($username, -1); } function roleLabel(string $role): string { return $role === 'admin' ? '管理员' : '普通用户'; } function repaymentPlanStorePath(): string { return __DIR__ . '/../data/repayment_plans.json'; } function loadRepaymentPlans(): array { $path = repaymentPlanStorePath(); if (!is_file($path)) { return []; } $raw = file_get_contents($path); if ($raw === false || $raw === '') { return []; } $data = json_decode($raw, true); return is_array($data) ? $data : []; } function saveRepaymentPlans(array $data): void { $path = repaymentPlanStorePath(); $dir = dirname($path); if (!is_dir($dir)) { mkdir($dir, 0775, true); } file_put_contents($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX); } function getArchiveRepaymentPlan(array $all, int $archiveId): array { return $all[(string)$archiveId] ?? ['enabled' => false, 'plans' => []]; } function financeStorePath(): string { return __DIR__ . '/../data/archive_finance.json'; } function loadFinanceConfigs(): array { $path = financeStorePath(); if (!is_file($path)) { return []; } $raw = file_get_contents($path); if ($raw === false || $raw === '') { return []; } $data = json_decode($raw, true); return is_array($data) ? $data : []; } function saveFinanceConfigs(array $data): void { $path = financeStorePath(); $dir = dirname($path); if (!is_dir($dir)) { mkdir($dir, 0775, true); } file_put_contents($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX); } function getArchiveFinanceConfig(array $all, int $archiveId): array { $cfg = $all[(string)$archiveId] ?? []; return [ 'interest_mode' => $cfg['interest_mode'] ?? 'monthly', 'settlement_rule' => $cfg['settlement_rule'] ?? 'monthly_day', 'settlement_day' => (int)($cfg['settlement_day'] ?? 21), 'overdue_float_pct' => (float)($cfg['overdue_float_pct'] ?? 50), 'manual_overdue' => !empty($cfg['manual_overdue']), 'manual_early_settled' => !empty($cfg['manual_early_settled']), 'remark' => (string)($cfg['remark'] ?? ''), 'manual_principal_repaid' => (float)($cfg['manual_principal_repaid'] ?? 0), 'manual_interest_paid' => (float)($cfg['manual_interest_paid'] ?? 0), ]; } /** 计息用本金:优先剩余额度,否则授信额度 */ function effectivePrincipalForInterest(array $archive): float { if (isset($archive['remaining_limit']) && $archive['remaining_limit'] !== null && $archive['remaining_limit'] !== '') { return max(0.0, (float)$archive['remaining_limit']); } if ($archive['credit_limit'] === null || $archive['credit_limit'] === '') { return 0.0; } return max(0.0, (float)$archive['credit_limit']); } function archiveStatusWithConfig(?string $endDate, array $financeCfg): string { if (!empty($financeCfg['manual_early_settled'])) { return '已完成'; } if (!empty($financeCfg['manual_overdue'])) { return '逾期中'; } // 与 helpers::archiveStatus 一致;内联避免因 helpers 未同步部署仍按旧逻辑显示「到期自动已完成」 if (!$endDate) { return '待更新'; } $today = date('Y-m-d'); if ($endDate >= $today) { return '履行中'; } return '待更新'; } /** 便捷查询汇总:履行中与逾期中均视为未结清存续业务 */ function archiveStatusIsOngoingAggregate(?string $endDate, array $financeCfg): bool { $s = archiveStatusWithConfig($endDate, $financeCfg); return $s === '履行中' || $s === '逾期中'; } /** 与当前结息周期对齐的区间起点(按 refDate 所在周期),用于「当前应付利息」 */ function interestPeriodStartForRefDate(array $archive, array $financeCfg, string $refDate): string { $startDate = $archive['start_date'] ?: $refDate; $mode = (string)($financeCfg['interest_mode'] ?? 'monthly'); $settlementRule = (string)($financeCfg['settlement_rule'] ?? 'monthly_day'); $day = max(1, min(28, (int)($financeCfg['settlement_day'] ?? 20))); $periodStart = $startDate; if ($settlementRule === 'monthly_day' && $mode !== 'bullet') { $curYm = date('Y-m', strtotime($refDate)); $thisSettle = $curYm . '-' . str_pad((string)$day, 2, '0', STR_PAD_LEFT); if ($refDate >= $thisSettle) { $periodStart = $thisSettle; } else { $periodStart = date('Y-m-d', strtotime('-1 month', strtotime($thisSettle))); } } elseif ($settlementRule === 'quarterly_day' && $mode !== 'bullet') { $m = (int)date('n', strtotime($refDate)); $qStartMonth = ((int)(($m - 1) / 3)) * 3 + 1; $curQuarterSettle = date('Y', strtotime($refDate)) . '-' . str_pad((string)$qStartMonth, 2, '0', STR_PAD_LEFT) . '-' . str_pad((string)$day, 2, '0', STR_PAD_LEFT); if ($refDate >= $curQuarterSettle) { $periodStart = $curQuarterSettle; } else { $periodStart = date('Y-m-d', strtotime('-3 month', strtotime($curQuarterSettle))); } } if ($periodStart < $startDate) { $periodStart = $startDate; } return $periodStart; } /** * 下一结息日所在「整段结息周期」的起始日:上一结息日与起息日孰晚。 * 用于结息日应付利息(自周期起点至结息日,而非自今日至结息日)。 */ function settlementPeriodStartForNextSettle(string $nextSettle, array $archive, array $financeCfg): string { $startDate = $archive['start_date'] ?: $nextSettle; $mode = (string)($financeCfg['interest_mode'] ?? 'monthly'); $settlementRule = (string)($financeCfg['settlement_rule'] ?? 'monthly_day'); $day = max(1, min(28, (int)($financeCfg['settlement_day'] ?? 20))); if ($settlementRule === 'maturity_once' || $mode === 'bullet') { return $startDate; } $prevAnchor = $startDate; if ($settlementRule === 'monthly_day') { $prevAnchor = date('Y-m-d', strtotime('-1 month', strtotime($nextSettle))); } elseif ($settlementRule === 'quarterly_day') { $prevAnchor = date('Y-m-d', strtotime('-3 months', strtotime($nextSettle))); } return max($startDate, $prevAnchor); } /** 自 periodStart 起至 throughDate(含区间逻辑与 calcCurrentInterest 一致)的应计利息,不含「已归还利息」扣减 */ function calcAccruedInterestForRange(array $archive, array $financeCfg, array $repaymentPlans, string $periodStart, string $throughDate): float { $principal = effectivePrincipalForInterest($archive); $annualRate = $archive['annual_rate'] === null ? 0.0 : (float)$archive['annual_rate']; if ($principal <= 0 || $annualRate <= 0 || $throughDate < $periodStart) { return 0.0; } $status = archiveStatusWithConfig($archive['end_date'], $financeCfg); $overdueRate = $annualRate * (1 + ((float)($financeCfg['overdue_float_pct'] ?? 0) / 100)); $events = []; $overduePrincipal = 0.0; foreach ($repaymentPlans as $p) { $amount = (float)($p['amount'] ?? 0); if ($amount <= 0) { continue; } $pStatus = (string)($p['status'] ?? 'pending'); if ($pStatus === 'paid' && !empty($p['paid_at'])) { $paidAt = (string)$p['paid_at']; if ($paidAt >= $periodStart && $paidAt <= $throughDate) { $events[] = ['date' => $paidAt, 'type' => 'paid', 'amount' => $amount]; } continue; } if ($pStatus === 'overdue') { $dueDate = (string)($p['due_date'] ?? ''); if ($dueDate === '' || $dueDate > $throughDate) { continue; } if ($dueDate < $periodStart) { $overduePrincipal += $amount; } else { $events[] = ['date' => $dueDate, 'type' => 'overdue_start', 'amount' => $amount]; } } } usort($events, function ($a, $b) { $cmp = strcmp((string)$a['date'], (string)$b['date']); if ($cmp !== 0) { return $cmp; } $orderA = ($a['type'] ?? '') === 'overdue_start' ? 0 : 1; $orderB = ($b['type'] ?? '') === 'overdue_start' ? 0 : 1; return $orderA <=> $orderB; }); $interest = 0.0; $cursor = $periodStart; $currentPrincipal = $principal; foreach ($events as $ev) { $evDate = (string)($ev['date'] ?? ''); if ($evDate > $cursor) { $days = (strtotime($evDate) - strtotime($cursor)) / 86400; if ($days > 0) { $normalPrincipal = max(0, $currentPrincipal - $overduePrincipal); $interest += $normalPrincipal * ($annualRate / 100) * ($days / 360); $interest += $overduePrincipal * ($overdueRate / 100) * ($days / 360); } } $amount = (float)($ev['amount'] ?? 0); if (($ev['type'] ?? '') === 'overdue_start') { $overduePrincipal = min($currentPrincipal, $overduePrincipal + $amount); } elseif (($ev['type'] ?? '') === 'paid') { $currentPrincipal = max(0, $currentPrincipal - $amount); $overduePrincipal = min($overduePrincipal, $currentPrincipal); } $cursor = $evDate; } if ($throughDate > $cursor) { $days = (strtotime($throughDate) - strtotime($cursor)) / 86400; if ($days > 0) { $normalPrincipal = max(0, $currentPrincipal - $overduePrincipal); if ($overduePrincipal <= 0 && $status === '逾期中') { $interest += $currentPrincipal * ($overdueRate / 100) * ($days / 360); } else { $interest += $normalPrincipal * ($annualRate / 100) * ($days / 360); $interest += $overduePrincipal * ($overdueRate / 100) * ($days / 360); } } } return round($interest, 2); } function applyInterestPaidCredit(float $accrued, array $financeCfg): float { $paid = (float)($financeCfg['manual_interest_paid'] ?? 0); return round(max(0.0, $accrued - $paid), 2); } function calcCurrentInterest(array $archive, array $financeCfg, array $repaymentPlans): float { $today = date('Y-m-d'); $periodStart = interestPeriodStartForRefDate($archive, $financeCfg, $today); $raw = calcAccruedInterestForRange($archive, $financeCfg, $repaymentPlans, $periodStart, $today); return applyInterestPaidCredit($raw, $financeCfg); } /** 最近一个结息日应付的整周期利息(与利息查询一致) */ function calcSettlementPeriodInterest(array $archive, array $financeCfg, array $repaymentPlans, string $todayRef): float { $mode = (string)($financeCfg['interest_mode'] ?? 'monthly'); $annualRate = $archive['annual_rate'] === null ? 0.0 : (float)$archive['annual_rate']; if (effectivePrincipalForInterest($archive) <= 0 || $annualRate <= 0) { return 0.0; } $nextSettle = $mode === 'bullet' ? (((string)($archive['end_date'] ?? '') !== '' && (string)$archive['end_date'] >= $todayRef) ? (string)$archive['end_date'] : null) : nextSettlementDate($todayRef, $mode, (int)($financeCfg['settlement_day'] ?? 21)); if ($nextSettle === null || $nextSettle === '') { return 0.0; } $periodStart = settlementPeriodStartForNextSettle($nextSettle, $archive, $financeCfg); $raw = calcAccruedInterestForRange($archive, $financeCfg, $repaymentPlans, $periodStart, $nextSettle); return applyInterestPaidCredit($raw, $financeCfg); } function recalcRemainingLimitByPlans(PDO $pdo, int $archiveId, array $plans): void { if ($archiveId <= 0) { return; } $st = $pdo->prepare('SELECT credit_limit FROM archives WHERE id=:id'); $st->execute([':id' => $archiveId]); $credit = $st->fetchColumn(); if ($credit === false || $credit === null) { return; } $paidSum = 0.0; foreach ($plans as $p) { if ((string)($p['status'] ?? '') === 'paid') { $paidSum += (float)($p['amount'] ?? 0); } } $financeAll = loadFinanceConfigs(); $manualPrincipal = (float)(getArchiveFinanceConfig($financeAll, $archiveId)['manual_principal_repaid'] ?? 0); $remaining = max(0, (float)$credit - $paidSum - $manualPrincipal); $pdo->prepare('UPDATE archives SET remaining_limit=:r,updated_at=:u WHERE id=:id') ->execute([':r' => $remaining, ':u' => now(), ':id' => $archiveId]); } function uploadArchiveFiles(PDO $pdo, int $archiveId, array $files): void { if ($archiveId <= 0 || empty($files) || empty($files['name']) || !is_array($files['name'])) { return; } $names = $files['name']; $tmpNames = $files['tmp_name'] ?? []; $sizes = $files['size'] ?? []; $errors = $files['error'] ?? []; $subDir = buildArchiveUploadSubdir($pdo, $archiveId); $targetDir = rtrim(UPLOAD_DIR . '/' . $subDir, '/'); if (!is_dir($targetDir)) { mkdir($targetDir, 0775, true); } for ($i = 0; $i < count($names); $i++) { if ((int)($errors[$i] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { continue; } if ((int)($sizes[$i] ?? 0) > MAX_FILE_SIZE) { continue; } $original = (string)$names[$i]; $ext = strtolower(pathinfo($original, PATHINFO_EXTENSION)); if (!in_array($ext, ALLOWED_EXTENSIONS, true)) { continue; } $normalized = normalizeFileName($original); $stored = $subDir . '/' . uniqid('f_', true) . '_' . $normalized; if (move_uploaded_file((string)$tmpNames[$i], UPLOAD_DIR . '/' . $stored)) { $pdo->prepare('INSERT INTO files(archive_id,original_name,stored_name,file_ext,file_size,created_at) VALUES(:a,:o,:s,:e,:z,:c)') ->execute([':a' => $archiveId, ':o' => $original, ':s' => $stored, ':e' => $ext, ':z' => (int)$sizes[$i], ':c' => now()]); } } } function deleteApprovalStorePath(): string { return __DIR__ . '/../data/delete_approvals.json'; } function loadDeleteApprovals(): array { $path = deleteApprovalStorePath(); if (!is_file($path)) { return []; } $raw = file_get_contents($path); if ($raw === false || $raw === '') { return []; } $data = json_decode($raw, true); return is_array($data) ? $data : []; } function saveDeleteApprovals(array $data): void { $path = deleteApprovalStorePath(); $dir = dirname($path); if (!is_dir($dir)) { mkdir($dir, 0775, true); } file_put_contents($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX); } function currentUrlFromServer(): string { $uri = (string)($_SERVER['REQUEST_URI'] ?? ''); if ($uri === '' || strpos($uri, 'index.php') === false) { return 'index.php'; } return $uri; } function redirectBackOr(string $fallback): void { $posted = trim((string)($_POST['redirect_to'] ?? '')); $query = trim((string)($_GET['redirect_to'] ?? '')); $ref = trim((string)($_SERVER['HTTP_REFERER'] ?? '')); $target = $posted !== '' ? $posted : ($query !== '' ? $query : $ref); if ($target === '' || strpos($target, 'index.php') === false) { $target = $fallback; } header('Location: ' . $target); exit; } function fetchArchiveSimpleMap(PDO $pdo): array { $rows = $pdo->query('SELECT id,name,loan_institution,borrower,archive_kind FROM archives')->fetchAll(); $map = []; foreach ($rows as $r) { $id = (int)($r['id'] ?? 0); if ($id <= 0) { continue; } $map[$id] = [ 'name' => (string)($r['name'] ?? ''), 'loan_institution' => (string)($r['loan_institution'] ?? ''), 'borrower' => (string)($r['borrower'] ?? ''), 'archive_kind' => normalizeArchiveKind((string)($r['archive_kind'] ?? '')), ]; } return $map; } function approvalTargetLabel(array $ap, array $archiveMap): string { $type = (string)($ap['target_type'] ?? ''); if ($type === 'archive_batch') { $ids = array_values(array_unique(array_filter(array_map('intval', (array)($ap['target_ids'] ?? []))))); $labels = []; foreach (array_slice($ids, 0, 5) as $aid) { if (!isset($archiveMap[$aid])) { $labels[] = 'ID:' . $aid . '(已删除)'; continue; } $arc = $archiveMap[$aid]; $labels[] = '[' . archiveKindLabel((string)$arc['archive_kind']) . '] ' . (string)$arc['name']; } $more = count($ids) > 5 ? ' 等' . count($ids) . '条' : ''; return '批量' . (int)($ap['target_count'] ?? count($ids)) . '条:' . implode(';', $labels) . $more; } $aid = (int)($ap['target_id'] ?? 0); if ($aid <= 0) { return 'ID:0'; } if (!isset($archiveMap[$aid])) { return 'ID:' . $aid . '(已删除/不存在)'; } $arc = $archiveMap[$aid]; $name = (string)$arc['name']; $inst = (string)$arc['loan_institution']; $borrower = (string)$arc['borrower']; return 'ID:' . $aid . ' [' . archiveKindLabel((string)$arc['archive_kind']) . '] ' . $name . ' / ' . $inst . ' / ' . $borrower; } function nextSettlementDate(string $today, string $mode, int $day): ?string { if ($mode === 'bullet') { return null; } $day = max(1, min(28, $day)); if ($mode === 'quarterly') { $m = (int)date('n', strtotime($today)); $qStartMonth = ((int)(($m - 1) / 3)) * 3 + 1; $cand = date('Y', strtotime($today)) . '-' . str_pad((string)$qStartMonth, 2, '0', STR_PAD_LEFT) . '-' . str_pad((string)$day, 2, '0', STR_PAD_LEFT); if ($cand <= $today) { $cand = date('Y-m-d', strtotime('+3 month', strtotime($cand))); } return $cand; } $curYm = date('Y-m', strtotime($today)); $cand = $curYm . '-' . str_pad((string)$day, 2, '0', STR_PAD_LEFT); if ($cand <= $today) { $cand = date('Y-m-d', strtotime('+1 month', strtotime($cand))); } return $cand; } function ensureDirectoryPath(PDO $pdo, string $lv1, string $lv2, string $lv3): int { $clean = function (string $v): string { $v = str_replace("\xC2\xA0", ' ', $v); $v = preg_replace('/\s+/u', ' ', trim($v)); return (string)$v; }; $lv1 = $clean($lv1); $lv2 = $clean($lv2); $lv3 = $clean($lv3); $findOrCreate = function (?int $parentId, int $level, string $name) use ($pdo): int { if ($parentId === null) { $q = $pdo->prepare('SELECT id FROM directories WHERE name=:n AND level=:l AND parent_id IS NULL'); $q->execute([':n' => $name, ':l' => $level]); } else { $q = $pdo->prepare('SELECT id FROM directories WHERE name=:n AND level=:l AND parent_id=:p'); $q->execute([':n' => $name, ':l' => $level, ':p' => $parentId]); } $id = (int)$q->fetchColumn(); if ($id > 0) { return $id; } $ins = $pdo->prepare('INSERT INTO directories(name,parent_id,level,created_at) VALUES(:n,:p,:l,:c)'); $ins->bindValue(':n', $name); $ins->bindValue(':p', $parentId, $parentId === null ? PDO::PARAM_NULL : PDO::PARAM_INT); $ins->bindValue(':l', $level, PDO::PARAM_INT); $ins->bindValue(':c', now()); $ins->execute(); return (int)$pdo->lastInsertId(); }; $id1 = $findOrCreate(null, 1, $lv1); $id2 = $findOrCreate($id1, 2, $lv2); return $findOrCreate($id2, 3, $lv3); } function hardDeleteArchive(PDO $pdo, int $archiveId): void { $stmt = $pdo->prepare('SELECT stored_name FROM files WHERE archive_id=:a'); $stmt->execute([':a' => $archiveId]); foreach ($stmt->fetchAll() as $f) { $filePath = UPLOAD_DIR . '/' . $f['stored_name']; if (is_file($filePath)) { unlink($filePath); } } $pdo->prepare('DELETE FROM archives WHERE id=:id')->execute([':id' => $archiveId]); } function loanInstitutionStorePath(): string { return __DIR__ . '/../data/loan_institutions.json'; } function defaultLoanInstitutions(): array { return [ '河南农商银行登封支行', '郑州银行登封支行', '浦发银行郑州金水支行', '中国银行登封支行', '建设银行登封支行', '邮储银行登封支行', '河南农商银行郑州关虎屯支行', '中信银行登封支行', ]; } function loadLoanInstitutions(): array { $path = loanInstitutionStorePath(); if (!is_file($path)) { return defaultLoanInstitutions(); } $raw = file_get_contents($path); if ($raw === false || $raw === '') { return defaultLoanInstitutions(); } $arr = json_decode($raw, true); if (!is_array($arr)) { return defaultLoanInstitutions(); } $out = []; foreach ($arr as $x) { $v = trim((string)$x); if ($v !== '') { $out[] = $v; } } return $out ?: defaultLoanInstitutions(); } function saveLoanInstitutions(array $items): void { $out = []; foreach ($items as $x) { $v = trim((string)$x); if ($v !== '') { $out[] = $v; } } $out = array_values(array_unique($out)); if (empty($out)) { $out = defaultLoanInstitutions(); } $path = loanInstitutionStorePath(); $dir = dirname($path); if (!is_dir($dir)) { mkdir($dir, 0775, true); } file_put_contents($path, json_encode($out, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX); } function autoArchiveName(string $borrower, ?float $creditLimit): string { $b = trim($borrower); if ($b === '') { return ''; } $wan = $creditLimit === null ? '' : rtrim(rtrim(number_format($creditLimit / 10000, 2, '.', ''), '0'), '.'); return $wan === '' ? $b : ($b . $wan . '万元'); } /** 对外担保:担保人 +「对外担保」+ 授信/10000 +「万元」 */ function autoExternalGuaranteeArchiveName(string $guarantor, ?float $creditLimit): string { $g = trim($guarantor); if ($g === '') { return ''; } $wan = $creditLimit === null ? '' : rtrim(rtrim(number_format($creditLimit / 10000, 2, '.', ''), '0'), '.'); return $wan === '' ? '' : ($g . '对外担保' . $wan . '万元'); } function ensureAutoBankLoanDirectory(PDO $pdo, ?string $startDate, string $loanInstitution): int { $year = $startDate ? date('Y', strtotime((string) $startDate)) : ''; $bank = trim($loanInstitution); if ($year === '' || $bank === '') { return 0; } return ensureDirectoryPath($pdo, '银行贷款', $year, $bank); } function ensureAutoExternalGuaranteeDirectory(PDO $pdo, ?string $startDate, string $guarantor): int { $year = $startDate ? date('Y', strtotime((string) $startDate)) : ''; $g = trim($guarantor); if ($year === '' || $g === '') { return 0; } return ensureDirectoryPath($pdo, '对外担保', $year, $g); } function collectAncestorIds(array $dirs, ?int $selectedId): array { if (!$selectedId) { return []; } $parentMap = []; foreach ($dirs as $d) { $parentMap[(int)$d['id']] = $d['parent_id'] === null ? null : (int)$d['parent_id']; } $ids = [$selectedId]; $cur = $selectedId; while (isset($parentMap[$cur]) && $parentMap[$cur] !== null) { $cur = (int)$parentMap[$cur]; $ids[] = $cur; } return array_values(array_unique($ids)); } /** 二级目录排序:从名称中提取年份(如 2025、2025年)升序;无年份则按名称排在后段。 */ function directoryLevel2TimeSortKey(string $name): int { $name = trim($name); if ($name === '') { return 9999; } if (preg_match('/^(\d{4})(?:\b|年)/u', $name, $m)) { return (int) $m[1]; } if (preg_match('/(\d{4})/u', $name, $m)) { return (int) $m[1]; } return 9999; } function buildTreeHtml(array $dirs, ?int $selectedId, bool $admin, array $pathIds): string { $byParent = []; $dirMap = []; foreach ($dirs as $d) { $pid = $d['parent_id'] === null ? 0 : (int) $d['parent_id']; $byParent[$pid][] = $d; $dirMap[(int)$d['id']] = $d; } foreach ($byParent as &$children) { if ($children === []) { continue; } if ((int) ($children[0]['level'] ?? 0) === 2) { usort($children, static function (array $a, array $b): int { $ka = directoryLevel2TimeSortKey((string) $a['name']); $kb = directoryLevel2TimeSortKey((string) $b['name']); if ($ka !== $kb) { return $ka <=> $kb; } return strcmp((string) $a['name'], (string) $b['name']); }); } } unset($children); $pathSet = array_flip($pathIds); $rootNameById = []; foreach ($dirMap as $id => $d) { $cur = $id; $rootName = (string)$d['name']; while (isset($dirMap[$cur]) && $dirMap[$cur]['parent_id'] !== null) { $cur = (int)$dirMap[$cur]['parent_id']; if (!isset($dirMap[$cur])) { break; } $rootName = (string)$dirMap[$cur]['name']; } $rootNameById[$id] = $rootName; } $render = function ($parent) use (&$render, $byParent, $selectedId, $admin, $pathSet, $rootNameById): string { if (empty($byParent[$parent])) { return ''; } $html = ''; return $html; }; return $render(0); } /** 便捷查询侧栏 / 便捷查询页:折叠菜单内链接 */ function quickQueryMenuHtml(): string { $links = [ ['query_payable_interest', '应付本息查询'], ['query_external_guarantee', '对外担保查询'], ['query_totals', '查询总金额'], ['interest_calculator', '利息计算器'], ['acceptance_discount_calculator', '承兑贴现计算器'], ['repayment_history', '历史还款记录查询'], ]; $inner = ''; foreach ($links as $pair) { $inner .= '' . e($pair[1]) . ''; } return '
' . '便捷查询点击展开' . '' . '
'; } if ($action === 'new_dir') { requireAdmin(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = trim((string)($_POST['name'] ?? '')); $parentId = (int)($_POST['parent_id'] ?? 0); if ($name === '') { flash('error', '目录名称不能为空'); header('Location: index.php'); exit; } $level = 1; $parentValue = null; if ($parentId > 0) { $stmt = $pdo->prepare('SELECT * FROM directories WHERE id=:id'); $stmt->execute([':id' => $parentId]); $p = $stmt->fetch(); if (!$p) { flash('error', '父目录不存在'); header('Location: index.php'); exit; } $level = (int)$p['level'] + 1; $parentValue = $parentId; if ($level > 3) { flash('error', '最多只支持三级目录'); header('Location: index.php?dir_id=' . $parentId); exit; } } $stmt = $pdo->prepare('INSERT INTO directories(name,parent_id,level,created_at) VALUES(:n,:p,:l,:c)'); $stmt->bindValue(':n', $name); $stmt->bindValue(':p', $parentValue, $parentValue === null ? PDO::PARAM_NULL : PDO::PARAM_INT); $stmt->bindValue(':l', $level, PDO::PARAM_INT); $stmt->bindValue(':c', now()); $stmt->execute(); flash('ok', '目录已创建'); header('Location: index.php' . ($parentId ? '?dir_id=' . $parentId : '')); exit; } $parentId = (int)($_GET['parent_id'] ?? 0); pageHeader($me); ?>

新建目录

返回
prepare('SELECT * FROM directories WHERE id=:id'); $stmt->execute([':id' => $id]); $d = $stmt->fetch(); if (!$d) { flash('error', '目录不存在'); header('Location: index.php'); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = trim((string)($_POST['name'] ?? '')); if ($name === '') { flash('error', '目录名称不能为空'); } else { $up = $pdo->prepare('UPDATE directories SET name=:n WHERE id=:id'); $up->execute([':n' => $name, ':id' => $id]); flash('ok', '目录已更新'); header('Location: index.php?dir_id=' . $id); exit; } } pageHeader($me); ?>

编辑目录

返回
prepare('SELECT COUNT(*) FROM directories WHERE parent_id=:id'); $c1->execute([':id' => $id]); $c2 = $pdo->prepare('SELECT COUNT(*) FROM archives WHERE directory_id=:id'); $c2->execute([':id' => $id]); if ((int)$c1->fetchColumn() > 0 || (int)$c2->fetchColumn() > 0) { flash('error', '目录非空,不能删除'); header('Location: index.php?dir_id=' . $id); exit; } $del = $pdo->prepare('DELETE FROM directories WHERE id=:id'); $del->execute([':id' => $id]); flash('ok', '目录已删除'); header('Location: index.php'); exit; } if ($action === 'save_archive') { requireAdmin(); $id = (int)($_POST['id'] ?? 0); $directoryId = (int)($_POST['directory_id'] ?? 0); $archiveKind = normalizeArchiveKind((string)($_POST['archive_kind'] ?? '')); $loanInstitution = trim((string)($_POST['loan_institution'] ?? '')); $borrower = trim((string)($_POST['borrower'] ?? '')); $guarantorEarly = trim((string)($_POST['guarantor'] ?? '')); $creditLimitVal = ($_POST['credit_limit'] ?? '') === '' ? null : (float)$_POST['credit_limit']; $startDateVal = $_POST['start_date'] ?: null; if ($archiveKind === ARCHIVE_KIND_EXTERNAL_GUARANTEE) { $name = autoExternalGuaranteeArchiveName($guarantorEarly, $creditLimitVal); if ($name === '') { flash('error', '对外担保需填写担保人、授信额度(用于自动生成档案名称)'); header('Location: index.php'); exit; } } else { $name = autoArchiveName($borrower, $creditLimitVal); if ($name === '') { flash('error', '借款主体与授信额度必填(用于自动生成档案名称)'); header('Location: index.php'); exit; } } if (isset($_POST['loan_institutions_json'])) { $json = json_decode((string)$_POST['loan_institutions_json'], true); if (is_array($json)) { saveLoanInstitutions($json); } } if ($directoryId > 0) { $check = $pdo->prepare('SELECT level FROM directories WHERE id=:id'); $check->execute([':id' => $directoryId]); $lv = (int)$check->fetchColumn(); if ($lv !== 3) { flash('error', '手动选择目录时必须是三级目录'); header('Location: index.php?dir_id=' . $directoryId); exit; } } else { if ($archiveKind === ARCHIVE_KIND_EXTERNAL_GUARANTEE) { $directoryId = ensureAutoExternalGuaranteeDirectory($pdo, $startDateVal, $guarantorEarly); if ($directoryId <= 0) { flash('error', '未选择三级目录时,对外担保需填写起始日与担保人,系统将自动创建目录:对外担保 / 年份 / 担保人'); header('Location: index.php'); exit; } } else { $directoryId = ensureAutoBankLoanDirectory($pdo, $startDateVal, $loanInstitution); if ($directoryId <= 0) { flash('error', '未选择三级目录时,银行贷款需填写贷款机构与起始日,系统将自动创建目录:银行贷款 / 年份 / 贷款机构'); header('Location: index.php'); exit; } } } $planEnabled = ($_POST['plan_enabled'] ?? '0') === '1'; $allPlans = loadRepaymentPlans(); $existing = $id > 0 ? getArchiveRepaymentPlan($allPlans, $id) : ['enabled' => false, 'plans' => []]; $existingMap = []; foreach (($existing['plans'] ?? []) as $ep) { if (!empty($ep['id'])) { $existingMap[(string)$ep['id']] = $ep; } } $dueDates = (array)($_POST['plan_due_date'] ?? []); $amounts = (array)($_POST['plan_amount'] ?? []); $planIds = (array)($_POST['plan_id'] ?? []); $planStatuses = (array)($_POST['plan_status'] ?? []); $delayDates = (array)($_POST['plan_delay_date'] ?? []); $plans = []; $paidSum = 0.0; if ($planEnabled) { $n = max(count($dueDates), count($amounts), count($planStatuses)); for ($i = 0; $i < $n; $i++) { $due = trim((string)($dueDates[$i] ?? '')); $amtRaw = trim((string)($amounts[$i] ?? '')); if ($due === '' || $amtRaw === '') { continue; } $amt = (float)$amtRaw; if ($amt <= 0) { continue; } $pid = trim((string)($planIds[$i] ?? '')); $status = (string)($planStatuses[$i] ?? 'pending'); if (!in_array($status, ['pending', 'paid', 'delayed', 'overdue'], true)) { $status = 'pending'; } $today = date('Y-m-d'); if (($status === 'delayed' || $status === 'overdue') && $due > $today) { $status = 'pending'; } $delayDate = trim((string)($delayDates[$i] ?? '')); if ($status === 'delayed' && $delayDate !== '') { $due = $delayDate; } $old = $existingMap[$pid] ?? null; $paidAt = null; if ($status === 'paid') { $paidAt = ($old && ($old['status'] ?? '') === 'paid' && !empty($old['paid_at'])) ? $old['paid_at'] : date('Y-m-d'); $paidSum += $amt; } $plans[] = [ 'id' => $pid !== '' ? $pid : uniqid('rp_', true), 'due_date' => $due, 'amount' => $amt, 'status' => $status, 'paid_at' => $paidAt, 'updated_at' => now(), ]; } } $financeAllWork = loadFinanceConfigs(); $rawPrevFinance = $id > 0 ? ($financeAllWork[(string)$id] ?? []) : []; $prevManualPrincipal = (float)($rawPrevFinance['manual_principal_repaid'] ?? 0); $prevManualInterest = (float)($rawPrevFinance['manual_interest_paid'] ?? 0); $addPrincipal = max(0, (float)($_POST['principal_repayment_now'] ?? 0)); $addInterest = max(0, (float)($_POST['interest_repayment_now'] ?? 0)); if ($creditLimitVal !== null) { $repayCap = max(0, $creditLimitVal - $paidSum - $prevManualPrincipal); if ($addPrincipal > $repayCap + 1e-6) { flash('error', '本次归还本金超过可还金额(授信 − 分期已还 − 已累计归还本金)'); header('Location: index.php?action=archive_form' . ($id > 0 ? ('&id=' . $id) : '')); exit; } } $oldEndDate = ''; if ($id > 0) { $stEnd = $pdo->prepare('SELECT end_date FROM archives WHERE id=:id'); $stEnd->execute([':id' => $id]); $oldEndRow = $stEnd->fetchColumn(); if ($oldEndRow !== false && $oldEndRow !== null) { $oldEndDate = (string)$oldEndRow; } } $loanExtendTo = trim((string)($_POST['loan_extend_to'] ?? '')); $endDatePosted = ($_POST['end_date'] ?? '') !== '' ? (string)$_POST['end_date'] : null; $endDateFinal = $endDatePosted; if ($loanExtendTo !== '') { if ($startDateVal && $loanExtendTo < $startDateVal) { flash('error', '延期后的到期日不能早于起始日'); header('Location: index.php?action=archive_form' . ($id > 0 ? ('&id=' . $id) : '')); exit; } if ($oldEndDate !== '' && $loanExtendTo < $oldEndDate) { flash('error', '延期后的到期日不能早于当前到期日'); header('Location: index.php?action=archive_form' . ($id > 0 ? ('&id=' . $id) : '')); exit; } $endDateFinal = $loanExtendTo; } $manualPrincipalTotal = $prevManualPrincipal + $addPrincipal; $manualInterestTotal = $prevManualInterest + $addInterest; $remainingAuto = $creditLimitVal === null ? null : max(0, $creditLimitVal - $paidSum - $manualPrincipalTotal); $payload = [ ':name' => $name, ':directory_id' => $directoryId, ':loan_institution' => $loanInstitution, ':borrower' => $borrower, ':credit_limit' => $creditLimitVal, ':remaining_limit' => $remainingAuto, ':start_date' => $startDateVal, ':end_date' => $endDateFinal, ':annual_rate' => ($_POST['annual_rate'] ?? '') === '' ? null : (float)$_POST['annual_rate'], ':guarantee_type' => trim((string)($_POST['guarantee_type'] ?? '')), ':guarantor' => $guarantorEarly, ':archive_kind' => $archiveKind, ':updated_at' => now(), ]; if ($id > 0) { $sql = 'UPDATE archives SET name=:name,directory_id=:directory_id,archive_kind=:archive_kind,loan_institution=:loan_institution,borrower=:borrower,credit_limit=:credit_limit,remaining_limit=:remaining_limit,start_date=:start_date,end_date=:end_date,annual_rate=:annual_rate,guarantee_type=:guarantee_type,guarantor=:guarantor,updated_at=:updated_at WHERE id=:id'; $payload[':id'] = $id; $pdo->prepare($sql)->execute($payload); $archiveId = $id; flash('ok', '档案已更新(' . date('H:i:s') . ')'); } else { $sql = 'INSERT INTO archives(name,directory_id,archive_kind,loan_institution,borrower,credit_limit,remaining_limit,start_date,end_date,annual_rate,guarantee_type,guarantor,created_at,updated_at) VALUES(:name,:directory_id,:archive_kind,:loan_institution,:borrower,:credit_limit,:remaining_limit,:start_date,:end_date,:annual_rate,:guarantee_type,:guarantor,:created_at,:updated_at)'; $payload[':created_at'] = now(); $pdo->prepare($sql)->execute($payload); $archiveId = (int)$pdo->lastInsertId(); flash('ok', '档案已创建(' . date('H:i:s') . ')'); } if ($planEnabled) { $allPlans[(string)$archiveId] = ['enabled' => true, 'plans' => $plans]; } else { $allPlans[(string)$archiveId] = ['enabled' => false, 'plans' => []]; } saveRepaymentPlans($allPlans); $financeAll = loadFinanceConfigs(); $interestMode = (string)($_POST['interest_mode'] ?? 'monthly'); if (!in_array($interestMode, ['monthly', 'quarterly', 'bullet'], true)) { $interestMode = 'monthly'; } $settlementRule = $interestMode === 'quarterly' ? 'quarterly_day' : ($interestMode === 'bullet' ? 'maturity_once' : 'monthly_day'); $settlementDay = $interestMode === 'bullet' ? 0 : max(1, min(28, (int)($_POST['settlement_day'] ?? 21))); $todaySave = date('Y-m-d'); $endDatePosted = $endDateFinal !== null && $endDateFinal !== '' ? (string)$endDateFinal : ''; $postedEarlyRaw = $_POST['is_early_settled'] ?? null; $prevFinance = getArchiveFinanceConfig($financeAll, $archiveId); if ($endDatePosted !== '' && $endDatePosted < $todaySave) { // 已过到期日:禁止在编辑页首次标「已完成」(须走「待更新」);已是已完成的保留,除非表单显式提交为否 if (!empty($prevFinance['manual_early_settled'])) { $manualEarlySettled = ($postedEarlyRaw === null) ? true : ($postedEarlyRaw === '1'); } else { $manualEarlySettled = false; } } else { $manualEarlySettled = $endDatePosted !== '' && ($postedEarlyRaw === '1'); } $manualOverdue = (($_POST['is_overdue'] ?? '0') === '1'); if ($manualEarlySettled) { $manualOverdue = false; } $remarkRaw = trim((string)($_POST['archive_remark'] ?? '')); if (function_exists('mb_substr')) { $remarkRaw = mb_substr($remarkRaw, 0, 4000, 'UTF-8'); } elseif (strlen($remarkRaw) > 4000) { $remarkRaw = substr($remarkRaw, 0, 4000); } $financeMergeBase = $financeAll[(string)$archiveId] ?? []; $financeAll[(string)$archiveId] = array_merge($financeMergeBase, [ 'interest_mode' => $interestMode, 'settlement_rule' => $settlementRule, 'settlement_day' => $settlementDay, 'overdue_float_pct' => max(0, (float)($_POST['overdue_float_pct'] ?? 50)), 'manual_overdue' => $manualOverdue, 'manual_early_settled' => $manualEarlySettled, 'remark' => $remarkRaw, 'manual_principal_repaid' => $manualPrincipalTotal, 'manual_interest_paid' => $manualInterestTotal, 'updated_at' => now(), ]); saveFinanceConfigs($financeAll); uploadArchiveFiles($pdo, $archiveId, $_FILES['new_files'] ?? []); header('Location: index.php?action=archive_form&id=' . $archiveId); exit; } if ($action === 'reminder_action') { requireAdmin(); $op = (string)($_POST['op'] ?? $_GET['op'] ?? ''); $archiveId = (int)($_POST['archive_id'] ?? $_GET['archive_id'] ?? 0); $planId = trim((string)($_POST['plan_id'] ?? $_GET['plan_id'] ?? '')); $redirectType = (string)($_POST['redirect_type'] ?? $_GET['redirect_type'] ?? 'all'); if (!in_array($redirectType, ['all', 'due', 'plan', 'overdue'], true)) { $redirectType = 'all'; } $back = 'index.php?action=reminders&type=' . $redirectType; if ($archiveId <= 0) { flash('error', '参数错误'); redirectBackOr($back); } if ($op === 'early_settle') { $financeAll = loadFinanceConfigs(); $cfg = getArchiveFinanceConfig($financeAll, $archiveId); $cfg['manual_early_settled'] = true; $cfg['manual_overdue'] = false; $cfg['updated_at'] = now(); $financeAll[(string)$archiveId] = $cfg; saveFinanceConfigs($financeAll); flash('ok', '已设为提前还款,状态改为已完成'); redirectBackOr($back); } if ($op === 'plan_delay') { $newDue = trim((string)($_POST['new_due_date'] ?? $_GET['new_due_date'] ?? '')); if ($planId === '' || $newDue === '') { flash('error', '延期参数不完整'); redirectBackOr($back); } $allPlans = loadRepaymentPlans(); $cfg = getArchiveRepaymentPlan($allPlans, $archiveId); if (empty($cfg['enabled']) || empty($cfg['plans']) || !is_array($cfg['plans'])) { flash('error', '分期计划不存在'); redirectBackOr($back); } $ok = false; foreach ($cfg['plans'] as &$p) { if ((string)($p['id'] ?? '') !== $planId) { continue; } $p['due_date'] = $newDue; $p['status'] = 'delayed'; $p['paid_at'] = null; $p['updated_at'] = now(); $ok = true; break; } unset($p); if ($ok) { $allPlans[(string)$archiveId] = $cfg; saveRepaymentPlans($allPlans); flash('ok', '还款计划已延期'); } else { flash('error', '未找到对应还款计划'); } redirectBackOr($back); } if ($op === 'overdue_paid') { $allPlans = loadRepaymentPlans(); $cfg = getArchiveRepaymentPlan($allPlans, $archiveId); $handled = false; if ($planId !== '' && !empty($cfg['enabled']) && !empty($cfg['plans']) && is_array($cfg['plans'])) { foreach ($cfg['plans'] as &$p) { if ((string)($p['id'] ?? '') !== $planId) { continue; } $p['status'] = 'paid'; $p['paid_at'] = date('Y-m-d'); $p['updated_at'] = now(); $handled = true; break; } unset($p); if ($handled) { $allPlans[(string)$archiveId] = $cfg; saveRepaymentPlans($allPlans); recalcRemainingLimitByPlans($pdo, $archiveId, (array)$cfg['plans']); flash('ok', '逾期分期已标记为已归还'); } } else { $financeAll = loadFinanceConfigs(); $f = getArchiveFinanceConfig($financeAll, $archiveId); $f['manual_early_settled'] = true; $f['manual_overdue'] = false; $f['updated_at'] = now(); $financeAll[(string)$archiveId] = $f; saveFinanceConfigs($financeAll); flash('ok', '整笔逾期已归还,状态改为已完成'); $handled = true; } if (!$handled) { flash('error', '未找到可处理的逾期项'); } redirectBackOr($back); } flash('error', '不支持的操作'); redirectBackOr($back); } if ($action === 'pending_update_action') { requireAdmin(); $op = (string)($_POST['op'] ?? ''); $archiveId = (int)($_POST['archive_id'] ?? 0); $planId = trim((string)($_POST['plan_id'] ?? '')); $todayPu = date('Y-m-d'); $dirIdPost = (int)($_POST['dir_id'] ?? 0); $back = 'index.php?action=pending_updates' . ($dirIdPost > 0 ? ('&dir_id=' . $dirIdPost) : ''); if ($archiveId <= 0) { flash('error', '参数错误'); redirectBackOr($back); } if ($op === 'pending_loan_repaid' || $op === 'pending_loan_overdue') { $st = $pdo->prepare('SELECT end_date FROM archives WHERE id=:id'); $st->execute([':id' => $archiveId]); $endRow = $st->fetchColumn(); $endStr = $endRow !== false && $endRow !== null ? (string)$endRow : ''; if ($endStr === '' || $endStr >= $todayPu) { flash('error', '该贷款尚未到期待更新处理'); redirectBackOr($back); } $financeAll = loadFinanceConfigs(); $f = getArchiveFinanceConfig($financeAll, $archiveId); if (archiveStatusWithConfig($endStr, $f) !== '待更新') { flash('error', '该项已处理或状态已变更'); redirectBackOr($back); } if ($op === 'pending_loan_repaid') { $f['manual_early_settled'] = true; $f['manual_overdue'] = false; $f['updated_at'] = now(); $financeAll[(string)$archiveId] = $f; saveFinanceConfigs($financeAll); flash('ok', '已标记为已还款,状态改为已完成'); } else { $f['manual_overdue'] = true; $f['manual_early_settled'] = false; $f['updated_at'] = now(); $financeAll[(string)$archiveId] = $f; saveFinanceConfigs($financeAll); flash('ok', '已标记为已逾期,状态改为逾期中'); } redirectBackOr($back); } if ($op === 'pending_plan_repaid' || $op === 'pending_plan_overdue') { if ($planId === '') { flash('error', '参数错误'); redirectBackOr($back); } $allPlans = loadRepaymentPlans(); $cfg = getArchiveRepaymentPlan($allPlans, $archiveId); $handled = false; if (!empty($cfg['enabled']) && !empty($cfg['plans']) && is_array($cfg['plans'])) { foreach ($cfg['plans'] as &$p) { if ((string)($p['id'] ?? '') !== $planId) { continue; } $pst = (string)($p['status'] ?? 'pending'); $d = (string)($p['due_date'] ?? ''); if ($d === '' || $d >= $todayPu || !in_array($pst, ['pending', 'delayed'], true)) { break; } if ($op === 'pending_plan_repaid') { $p['status'] = 'paid'; $p['paid_at'] = date('Y-m-d'); } else { $p['status'] = 'overdue'; $p['paid_at'] = null; } $p['updated_at'] = now(); $handled = true; break; } unset($p); } if ($handled) { $allPlans[(string)$archiveId] = $cfg; saveRepaymentPlans($allPlans); if ($op === 'pending_plan_repaid') { recalcRemainingLimitByPlans($pdo, $archiveId, (array)$cfg['plans']); } flash('ok', $op === 'pending_plan_repaid' ? '已标记为已还款(按期还款)' : '已标记为已逾期(逾期中)'); } else { flash('error', '未找到可处理的还款计划或状态已变更'); } redirectBackOr($back); } flash('error', '不支持的操作'); redirectBackOr($back); } if ($action === 'download_import_template') { requireAdmin(); $manualTemplatePath = __DIR__ . '/../data/import_template.xlsx'; if (is_file($manualTemplatePath)) { header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename="' . rawurlencode('档案导入模板.xlsx') . '"'); readfile($manualTemplatePath); exit; } $headers = ['一级目录', '二级目录', '三级目录', '档案名称', '贷款机构', '借款主体', '授信额度', '起止日', '到期日', '年利率', '担保类型', '担保人', '计息方式', '结息日', '是否有分期还款计划']; $sample = ['银行贷款', '2026', 'XX银行', 'A公司流动资金贷款合同', 'XX银行', 'A公司', '5000000', '2026-01-15', '2027-01-14', '5.5', '抵押', 'B公司', '按月计息', '21', '否']; if (class_exists('\PhpOffice\PhpSpreadsheet\Spreadsheet') && class_exists('\PhpOffice\PhpSpreadsheet\Writer\Xlsx')) { $filename = '档案导入模板.xlsx'; $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setCellValue( 'A1', '说明:第1行可写模板使用提示(导入时忽略);第2行为列标题勿改;从第3行起填数据。' . '一级目录填「银行贷款」或「对外担保」;系统据此识别档案类型。对外担保导入时「贷款机构」「年利率」可留空;「档案名称」可留空,将按担保人+对外担保+授信万元自动生成。' . '银行贷款必填至担保人(含档案名称列);选填:计息方式、结息日、是否有分期还款计划。' ); $sheet->fromArray($headers, null, 'A2'); $sheet->fromArray([$sample], null, 'A3'); foreach (range('A', 'O') as $col) { $sheet->getColumnDimension($col)->setWidth(20); } header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"'); $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); $writer->save('php://output'); exit; } flash('error', '未找到可用xlsx模板。请安装PhpSpreadsheet或将模板放到 data/import_template.xlsx'); header('Location: index.php'); exit; } if ($action === 'import_archives') { requireAdmin(); if (empty($_FILES['import_file']) || (int)$_FILES['import_file']['error'] !== UPLOAD_ERR_OK) { flash('error', '请选择有效导入文件'); header('Location: index.php'); exit; } $name = (string)$_FILES['import_file']['name']; $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); $rows = []; if ($ext === 'csv') { $fp = fopen((string)$_FILES['import_file']['tmp_name'], 'rb'); if ($fp) { fgetcsv($fp); $header = fgetcsv($fp); while (($line = fgetcsv($fp)) !== false) { $rows[] = $line; } fclose($fp); } } elseif (in_array($ext, ['xlsx', 'xls'], true) && class_exists('\PhpOffice\PhpSpreadsheet\IOFactory')) { $sheet = \PhpOffice\PhpSpreadsheet\IOFactory::load((string)$_FILES['import_file']['tmp_name'])->getActiveSheet(); $arr = $sheet->toArray(); array_shift($arr); $header = array_shift($arr); $rows = $arr; } else { flash('error', '仅支持CSV,或安装PhpSpreadsheet后支持Excel'); header('Location: index.php'); exit; } $created = 0; $skipped = 0; $financeAll = loadFinanceConfigs(); $planAll = loadRepaymentPlans(); $seen = []; foreach ($rows as $r) { $lv1 = (string)($r[0] ?? ''); $lv2 = (string)($r[1] ?? ''); $lv3 = (string)($r[2] ?? ''); $archiveName = trim((string)($r[3] ?? '')); $loanInstitution = trim((string)($r[4] ?? '')); $borrower = trim((string)($r[5] ?? '')); $creditRaw = trim((string)($r[6] ?? '')); $startDate = trim((string)($r[7] ?? '')); $endDate = trim((string)($r[8] ?? '')); $annualRateRaw = trim((string)($r[9] ?? '')); $guaranteeType = trim((string)($r[10] ?? '')); $guarantor = trim((string)($r[11] ?? '')); $importKind = trim($lv1) === '对外担保' ? ARCHIVE_KIND_EXTERNAL_GUARANTEE : ARCHIVE_KIND_BANK_LOAN; if ($importKind === ARCHIVE_KIND_EXTERNAL_GUARANTEE) { if ($lv1 === '' || $lv2 === '' || $lv3 === '' || $borrower === '' || $creditRaw === '' || $startDate === '' || $endDate === '' || $guaranteeType === '' || $guarantor === '') { continue; } $annualRate = $annualRateRaw === '' ? null : (float) $annualRateRaw; } else { if ($lv1 === '' || $lv2 === '' || $lv3 === '' || $archiveName === '' || $loanInstitution === '' || $borrower === '' || $creditRaw === '' || $startDate === '' || $endDate === '' || $annualRateRaw === '' || $guaranteeType === '' || $guarantor === '') { continue; } $annualRate = (float) $annualRateRaw; } // 无论目录是否预先存在,都会自动按三级路径创建并返回三级目录ID。 $dirId = ensureDirectoryPath($pdo, $lv1, $lv2, $lv3); $credit = (float) $creditRaw; $remain = $credit; if ($importKind === ARCHIVE_KIND_EXTERNAL_GUARANTEE) { $archiveName = autoExternalGuaranteeArchiveName($guarantor, $credit); if ($archiveName === '') { continue; } } $fingerprint = implode('|', [ $importKind, (string) $dirId, mb_strtolower($archiveName), mb_strtolower($loanInstitution), mb_strtolower($borrower), (string) $credit, $startDate, $endDate, (string) ($annualRate ?? ''), mb_strtolower($guaranteeType), mb_strtolower($guarantor), ]); if (isset($seen[$fingerprint])) { $skipped++; continue; } $seen[$fingerprint] = true; $dupStmt = $pdo->prepare('SELECT COUNT(*) FROM archives WHERE directory_id=:d AND archive_kind=:ak AND name=:n AND COALESCE(loan_institution,\'\')=:li AND borrower=:b AND credit_limit=:cl AND start_date=:sd AND end_date=:ed AND COALESCE(annual_rate,-999999.99)=COALESCE(:ar,-999999.99) AND guarantee_type=:gt AND guarantor=:g'); $dupStmt->execute([ ':d' => $dirId, ':ak' => $importKind, ':n' => $archiveName, ':li' => $loanInstitution, ':b' => $borrower, ':cl' => $credit, ':sd' => $startDate, ':ed' => $endDate, ':ar' => $annualRate, ':gt' => $guaranteeType, ':g' => $guarantor, ]); if ((int)$dupStmt->fetchColumn() > 0) { $skipped++; continue; } $pdo->prepare('INSERT INTO archives(name,directory_id,archive_kind,loan_institution,borrower,credit_limit,remaining_limit,start_date,end_date,annual_rate,guarantee_type,guarantor,created_at,updated_at) VALUES(:name,:directory_id,:archive_kind,:loan_institution,:borrower,:credit_limit,:remaining_limit,:start_date,:end_date,:annual_rate,:guarantee_type,:guarantor,:created_at,:updated_at)') ->execute([ ':name' => $archiveName, ':directory_id' => $dirId, ':archive_kind' => $importKind, ':loan_institution' => $loanInstitution, ':borrower' => $borrower, ':credit_limit' => $credit, ':remaining_limit' => $remain, ':start_date' => $startDate ?: null, ':end_date' => $endDate ?: null, ':annual_rate' => $annualRate, ':guarantee_type' => $guaranteeType, ':guarantor' => $guarantor, ':created_at' => now(), ':updated_at' => now(), ]); $archiveId = (int)$pdo->lastInsertId(); $modeText = trim((string)($r[12] ?? '')); $mode = $modeText === '按季付息' ? 'quarterly' : ($modeText === '到期一次性付清' ? 'bullet' : 'monthly'); $settlementRule = $mode === 'quarterly' ? 'quarterly_day' : ($mode === 'bullet' ? 'maturity_once' : 'monthly_day'); $settlementDay = $mode === 'bullet' ? 0 : max(1, min(28, (int)trim((string)($r[13] ?? '21')))); $financeAll[(string)$archiveId] = [ 'interest_mode' => $mode, 'settlement_rule' => $settlementRule, 'settlement_day' => $settlementDay, 'overdue_float_pct' => 50, 'manual_overdue' => false, 'manual_early_settled' => false, 'remark' => '', 'updated_at' => now(), ]; $planEnabledText = trim((string)($r[14] ?? '否')); $planAll[(string)$archiveId] = ['enabled' => in_array($planEnabledText, ['是', 'yes', 'YES', '1'], true), 'plans' => []]; $created++; } saveFinanceConfigs($financeAll); saveRepaymentPlans($planAll); flash('ok', '导入完成:新增 ' . $created . ' 条,重复忽略 ' . $skipped . ' 条'); header('Location: index.php'); exit; } if ($action === 'request_delete_archive') { requireAdmin(); $archiveId = (int)($_GET['id'] ?? 0); if ($archiveId <= 0) { flash('error', '参数错误'); header('Location: index.php'); exit; } $approvals = loadDeleteApprovals(); foreach ($approvals as $ap) { if (($ap['target_type'] ?? '') === 'archive' && (int)($ap['target_id'] ?? 0) === $archiveId && ($ap['status'] ?? '') === 'pending') { flash('error', '该档案已有待审核删除申请'); header('Location: index.php'); exit; } } $approvals[] = [ 'id' => uniqid('del_', true), 'target_type' => 'archive', 'target_id' => $archiveId, 'status' => 'pending', 'requested_by' => (int)$me['id'], 'requested_name' => (string)$me['username'], 'requested_at' => now(), 'approved_by' => null, 'approved_at' => null, ]; saveDeleteApprovals($approvals); flash('ok', '已提交删除申请,需另一位管理员审核'); header('Location: index.php'); exit; } if ($action === 'request_delete_selected') { requireAdmin(); $idsRaw = $_GET['ids'] ?? ''; if (is_array($idsRaw)) { $ids = array_values(array_unique(array_filter(array_map('intval', $idsRaw)))); } else { $ids = array_values(array_unique(array_filter(array_map('intval', explode(',', (string)$idsRaw))))); } if (empty($ids)) { flash('error', '请先选择要删除的档案'); header('Location: index.php'); exit; } $approvals = loadDeleteApprovals(); $pendingSet = []; foreach ($approvals as $ap) { if (($ap['status'] ?? '') !== 'pending') { continue; } if (($ap['target_type'] ?? '') === 'archive' && !empty($ap['target_id'])) { $pendingSet[] = (string)$ap['target_id']; } if (($ap['target_type'] ?? '') === 'archive_batch' && !empty($ap['target_ids']) && is_array($ap['target_ids'])) { foreach ($ap['target_ids'] as $x) { $pendingSet[] = (string)(int)$x; } } } foreach ($ids as $id) { if (in_array((string)$id, $pendingSet, true)) { flash('error', '所选档案中存在待审核删除项,请先完成审核'); header('Location: index.php'); exit; } } $approvals[] = [ 'id' => uniqid('del_', true), 'target_type' => 'archive_batch', 'target_ids' => $ids, 'target_count' => count($ids), 'status' => 'pending', 'requested_by' => (int)$me['id'], 'requested_name' => (string)$me['username'], 'requested_at' => now(), 'approved_by' => null, 'approved_at' => null, ]; saveDeleteApprovals($approvals); flash('ok', '已提交选中档案删除申请(' . count($ids) . ' 条),需另一位管理员审核'); header('Location: index.php'); exit; } if ($action === 'delete_approvals') { requireAdmin(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $approvalId = trim((string)($_POST['approval_id'] ?? '')); $do = (string)($_POST['do'] ?? ''); $approvals = loadDeleteApprovals(); foreach ($approvals as &$ap) { if (($ap['id'] ?? '') !== $approvalId || ($ap['status'] ?? '') !== 'pending') { continue; } if ((int)$ap['requested_by'] === (int)$me['id']) { flash('error', '不能由申请人本人审核'); break; } if ($do === 'approve' && ($ap['target_type'] ?? '') === 'archive') { hardDeleteArchive($pdo, (int)$ap['target_id']); $ap['status'] = 'approved'; $ap['approved_by'] = (int)$me['id']; $ap['approved_at'] = now(); flash('ok', '删除已由第二管理员审核并执行'); break; } if ($do === 'approve' && ($ap['target_type'] ?? '') === 'archive_batch') { $ids = array_values(array_unique(array_filter(array_map('intval', (array)($ap['target_ids'] ?? []))))); foreach ($ids as $aid) { hardDeleteArchive($pdo, $aid); } $ap['status'] = 'approved'; $ap['approved_by'] = (int)$me['id']; $ap['approved_at'] = now(); flash('ok', '批量删除已审核并执行(' . count($ids) . ' 条)'); break; } if ($do === 'reject') { $ap['status'] = 'rejected'; $ap['approved_by'] = (int)$me['id']; $ap['approved_at'] = now(); flash('ok', '已驳回删除申请'); break; } } unset($ap); saveDeleteApprovals($approvals); redirectBackOr('index.php?action=delete_approvals'); } $approvals = array_reverse(loadDeleteApprovals()); $filterStatus = (string)($_GET['status'] ?? 'pending'); $filterTarget = trim((string)($_GET['target_id'] ?? '')); $filterRequester = trim((string)($_GET['requester'] ?? '')); $approvals = array_values(array_filter($approvals, function ($ap) use ($filterStatus, $filterTarget, $filterRequester) { if ($filterStatus !== 'all' && ($ap['status'] ?? '') !== $filterStatus) { return false; } if ($filterTarget !== '' && (string)($ap['target_id'] ?? '') !== $filterTarget) { return false; } if ($filterRequester !== '' && mb_stripos((string)($ap['requested_name'] ?? ''), $filterRequester) === false) { return false; } return true; })); $usersMap = []; $archiveMap = fetchArchiveSimpleMap($pdo); foreach ($pdo->query('SELECT id,username FROM users')->fetchAll() as $u) { $usersMap[(int)$u['id']] = (string)$u['username']; } pageHeader($me); ?>

删除审核(双管理员)

清空
申请时间类型目标申请人状态审核人审核时间操作
- -
-
暂无符合条件的审核记录
prepare('SELECT * FROM files WHERE id=:id'); $stmt->execute([':id' => $id]); $f = $stmt->fetch(); if ($f) { $p = UPLOAD_DIR . '/' . $f['stored_name']; if (is_file($p)) { unlink($p); } $pdo->prepare('DELETE FROM files WHERE id=:id')->execute([':id' => $id]); flash('ok', '文件已删除'); header('Location: index.php?action=archive_form&id=' . (int)$f['archive_id']); exit; } flash('error', '文件不存在'); header('Location: index.php'); exit; } if ($action === 'rename_file') { requireAdmin(); $fileId = (int)($_POST['file_id'] ?? 0); $newNameRaw = trim((string)($_POST['new_name'] ?? '')); if ($fileId <= 0 || $newNameRaw === '') { flash('error', '重命名参数错误'); header('Location: index.php'); exit; } $stmt = $pdo->prepare('SELECT * FROM files WHERE id=:id'); $stmt->execute([':id' => $fileId]); $f = $stmt->fetch(); if (!$f) { flash('error', '文件不存在'); header('Location: index.php'); exit; } $currentStored = (string)$f['stored_name']; $dirPart = dirname($currentStored); $dirPart = $dirPart === '.' ? '' : $dirPart; $prefix = pathinfo($currentStored, PATHINFO_FILENAME); $dotPos = strpos($prefix, '_'); $uniqPrefix = $dotPos === false ? uniqid('f_', true) : substr($prefix, 0, $dotPos); $ext = strtolower((string)$f['file_ext']); $newOriginal = $newNameRaw; if (strtolower((string)pathinfo($newOriginal, PATHINFO_EXTENSION)) !== $ext) { $newOriginal .= '.' . $ext; } $normalized = normalizeFileName($newOriginal); $newBase = $uniqPrefix . '_' . $normalized; $newStored = ($dirPart !== '' ? $dirPart . '/' : '') . $newBase; $oldPath = UPLOAD_DIR . '/' . $currentStored; $newPath = UPLOAD_DIR . '/' . $newStored; if (!is_file($oldPath)) { flash('error', '原文件不存在'); header('Location: index.php?action=archive_form&id=' . (int)$f['archive_id']); exit; } if (is_file($newPath)) { $newStored = ($dirPart !== '' ? $dirPart . '/' : '') . uniqid('f_', true) . '_' . $normalized; $newPath = UPLOAD_DIR . '/' . $newStored; } if (!rename($oldPath, $newPath)) { flash('error', '文件重命名失败'); header('Location: index.php?action=archive_form&id=' . (int)$f['archive_id']); exit; } $pdo->prepare('UPDATE files SET original_name=:o, stored_name=:s WHERE id=:id') ->execute([':o' => $newOriginal, ':s' => $newStored, ':id' => $fileId]); flash('ok', '文件已重命名'); header('Location: index.php?action=archive_form&id=' . (int)$f['archive_id']); exit; } if ($action === 'download_file') { requireLogin(); $id = (int)($_GET['id'] ?? 0); $stmt = $pdo->prepare('SELECT * FROM files WHERE id=:id'); $stmt->execute([':id' => $id]); $f = $stmt->fetch(); if (!$f) { http_response_code(404); exit('Not found'); } $p = UPLOAD_DIR . '/' . $f['stored_name']; if (!is_file($p)) { http_response_code(404); exit('File missing'); } header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . rawurlencode($f['original_name']) . '"'); readfile($p); exit; } if ($action === 'preview_file') { requireLogin(); $id = (int)($_GET['id'] ?? 0); $stmt = $pdo->prepare('SELECT * FROM files WHERE id=:id'); $stmt->execute([':id' => $id]); $f = $stmt->fetch(); if (!$f || !canPreview($f['file_ext'])) { http_response_code(403); exit('不支持预览'); } $p = UPLOAD_DIR . '/' . $f['stored_name']; if (!is_file($p)) { http_response_code(404); exit('文件不存在'); } if ($f['file_ext'] === 'pdf') { header('Content-Type: application/pdf'); } else { header('Content-Type: image/' . ($f['file_ext'] === 'jpg' ? 'jpeg' : $f['file_ext'])); } readfile($p); exit; } if ($action === 'archive_detail') { requireLogin(); $id = (int)($_GET['id'] ?? 0); $stmt = $pdo->prepare('SELECT a.*, d.name AS dir_name FROM archives a JOIN directories d ON a.directory_id=d.id WHERE a.id=:id'); $stmt->execute([':id' => $id]); $a = $stmt->fetch(); if (!$a) { flash('error', '档案不存在'); header('Location: index.php'); exit; } $planAll = loadRepaymentPlans(); $planCfg = getArchiveRepaymentPlan($planAll, $id); $repaymentPlans = $planCfg['plans'] ?? []; $financeAll = loadFinanceConfigs(); $financeCfg = getArchiveFinanceConfig($financeAll, $id); $status = archiveStatusWithConfig($a['end_date'], $financeCfg); $detailKind = normalizeArchiveKind($a['archive_kind'] ?? ''); $interestPayable = calcCurrentInterest($a, $financeCfg, $repaymentPlans); $settlementInterestPayable = $detailKind === ARCHIVE_KIND_BANK_LOAN ? calcSettlementPeriodInterest($a, $financeCfg, $repaymentPlans, date('Y-m-d')) : 0.0; usort($repaymentPlans, function ($x, $y) { return strcmp((string)($x['due_date'] ?? ''), (string)($y['due_date'] ?? '')); }); $files = $pdo->prepare('SELECT * FROM files WHERE archive_id=:a ORDER BY id DESC'); $files->execute([':a' => $id]); pageHeader($me); ?>

()

状态: 类型:

借款主体
授信额度剩余额度
起止日到期日
年利率—' : e($a['annual_rate'] === null ? '' : ((string)$a['annual_rate'] . '%')) ?>担保类型
担保人
结息方式 结息日
逾期利率上浮%当前应付利息
结息日应付利息(本结息周期整段)
备注

分期还款计划

该档案未启用分期还款计划

还款日期还款金额状态
已还款'; } elseif ($st === 'delayed') { echo '已延期'; } elseif ($st === 'overdue') { echo '已逾期'; } else { echo '待处理'; } ?>

还款计划状态和延期调整请在“编辑档案”页面操作。

文件列表

文件名大小操作
KB
0) { $st = $pdo->prepare('SELECT * FROM archives WHERE id=:id'); $st->execute([':id' => $id]); $editing = $st->fetch(); if (!$editing) { flash('error', '档案不存在'); header('Location: index.php'); exit; } } $dirs = fetchDirectories($pdo); $loanInstitutions = loadLoanInstitutions(); $dirId = (int)($_GET['dir_id'] ?? ($editing['directory_id'] ?? 0)); $dirPath = dirPathMap($dirs); $planAll = loadRepaymentPlans(); $planCfg = $editing ? getArchiveRepaymentPlan($planAll, (int)$editing['id']) : ['enabled' => false, 'plans' => []]; $financeAll = loadFinanceConfigs(); $financeCfg = $editing ? getArchiveFinanceConfig($financeAll, (int)$editing['id']) : [ 'interest_mode' => '', 'settlement_rule' => 'monthly_day', 'settlement_day' => 21, 'overdue_float_pct' => 50, 'manual_overdue' => false, 'manual_early_settled' => false, 'remark' => '', 'manual_principal_repaid' => 0.0, 'manual_interest_paid' => 0.0, ]; $planRows = $planCfg['plans'] ?? []; if (empty($planRows)) { for ($i = 0; $i < 6; $i++) { $planRows[] = ['id' => '', 'due_date' => '', 'amount' => '']; } } $editArchiveId = (int)($editing['id'] ?? 0); $editFiles = []; if ($editArchiveId > 0) { $fs = $pdo->prepare('SELECT * FROM files WHERE archive_id=:a ORDER BY id DESC'); $fs->execute([':a' => $editArchiveId]); $editFiles = $fs->fetchAll(); } pageHeader($me); ?>

+ 添加机构 | - 删除当前机构

起始日
到期日
date('Y-m-d'); ?>
还款日期还款金额状态操作延期日期行操作
>

说明:到期日前可选“提前还款”;到期日后可选“按期还款/延期/逾期”。逾期未归还计划本金按上浮利率计息,剩余额度按已还款计划与手动归还本金自动计算。

贷款延期与归还

填写后保存为新的到期日,须不早于当前到期日与起始日。

/

可仅使用延期;本金利息归还适用于银行贷款。

归还本金从剩余额度中扣减;归还利息计入「已归还利息」,减少当前应付与结息日应付利息。

点击“创建”后会自动上传并在编辑页文件管理列表中显示。

0): ?>

文件管理(编辑页)

文件名大小重命名操作
KB
暂无文件
20) { flash('error', '用户名或密码格式不正确'); } else { try { $pdo->prepare('INSERT INTO users(username,password_hash,role,created_at) VALUES(:u,:p,:r,:c)') ->execute([':u' => $u, ':p' => password_hash($p, PASSWORD_DEFAULT), ':r' => 'user', ':c' => now()]); flash('ok', '用户已创建'); } catch (Throwable $e) { flash('error', '用户名已存在'); } } } elseif ($do === 'role') { $uid = (int)$_POST['uid']; $role = $_POST['role'] === 'admin' ? 'admin' : 'user'; $pdo->prepare('UPDATE users SET role=:r WHERE id=:id')->execute([':r' => $role, ':id' => $uid]); flash('ok', '角色已更新'); } elseif ($do === 'reset') { $uid = (int)$_POST['uid']; $np = (string)$_POST['new_password']; if (strlen($np) < 6 || strlen($np) > 20) { flash('error', '密码长度需为6-20'); } else { $pdo->prepare('UPDATE users SET password_hash=:p WHERE id=:id')->execute([':p' => password_hash($np, PASSWORD_DEFAULT), ':id' => $uid]); flash('ok', '密码已重置'); } } elseif ($do === 'delete') { $uid = (int)$_POST['uid']; if ($uid === (int)$me['id']) { flash('error', '不能删除当前登录账号'); } else { $pdo->prepare('DELETE FROM users WHERE id=:id')->execute([':id' => $uid]); flash('ok', '用户已删除'); } } header('Location: index.php?action=users'); exit; } $users = $pdo->query('SELECT id,username,role,created_at FROM users ORDER BY id ASC')->fetchAll(); pageHeader($me); ?>

用户管理

ID用户名角色创建时间操作
prepare('SELECT * FROM users WHERE id=:id'); $stmt->execute([':id' => (int)$me['id']]); $u = $stmt->fetch(); if (!$u || !password_verify($cur, $u['password_hash'])) { flash('error', '当前密码错误'); } elseif (strlen($new) < 6 || strlen($new) > 20) { flash('error', '新密码长度需为6-20位'); } elseif ($new !== $confirm) { flash('error', '新密码与确认密码不一致'); } else { $pdo->prepare('UPDATE users SET password_hash=:p WHERE id=:id')->execute([':p' => password_hash($new, PASSWORD_DEFAULT), ':id' => (int)$me['id']]); flash('ok', '密码修改成功'); } header('Location: index.php?action=profile'); exit; } pageHeader($me); ?>

个人设置 - 修改密码

返回

便捷查询

应付本息:统计履行中及逾期中银行贷款按机构当前应付利息、本结息周期整段结息日应付利息,以及当月还款计划金额。

对外担保:按主体名称检索“该主体作为担保人”的履行中及逾期中银行贷款与对外担保档案。

查询总金额:按选项汇总履行中及逾期中档案的授信总额与剩余额度合计。

利息计算器 / 承兑贴现计算器:本地浏览器计算,结果仅供参考。

历史还款记录:汇总分期计划中「已还款」本金及状态为「已完成」档案的授信本金规模。

返回首页

query('SELECT * FROM archives ORDER BY id DESC')->fetchAll(); $planAll = loadRepaymentPlans(); $financeAll = loadFinanceConfigs(); $hr = qqBuildRepaymentHistory($rowsAll, $planAll, $financeAll); $planRows = $hr['plan_rows']; $cb = $hr['completed_bank']; $cg = $hr['completed_guar']; pageHeader($me); ?>

历史还款记录查询

汇总

项目笔数金额合计(元)
分期还款计划中「已还款」本金
已完成 — 银行贷款(授信本金规模)
已完成 — 对外担保(授信本金规模)

「已完成」指合同状态为已完成;授信本金规模为档案授信额度合计,用于对应到期/结清贷款本金规模参考。分期已还与整笔结清可能同时存在,请勿简单相加避免重复理解。

分期计划已还款明细

档案ID档案名称机构借款主体计划到期日标记还款日还款金额操作
0): ?>查看
暂无已标记还款的分期计划
query('SELECT * FROM archives ORDER BY id DESC')->fetchAll(); $financeAll = loadFinanceConfigs(); $tot = qqBuildTotalsQuery($rowsAll, $financeAll, $scope, $inst !== '' ? $inst : null); $loanInstitutions = loadLoanInstitutions(); pageHeader($me); ?>

查询总金额

汇总结果

笔数(履行+逾期)授信总额合计剩余额度合计

统计范围含状态为「履行中」与「逾期中」的档案。授信总额、剩余额度均来自各档案字段;剩余额度含分期已还与手动归还本金后的结果。

query('SELECT * FROM archives ORDER BY id DESC')->fetchAll(); $financeAll = loadFinanceConfigs(); $planAll = loadRepaymentPlans(); $quickStats = qqBuildPayableInterestData($rowsAll, $financeAll, $planAll); $instRows = $quickStats['instRows']; $planRows = $quickStats['planRows']; $planTotal = (float)($quickStats['planTotal'] ?? 0); if ($exportFormat === 'xlsx' && class_exists('\PhpOffice\PhpSpreadsheet\Spreadsheet') && class_exists('\PhpOffice\PhpSpreadsheet\Writer\Xlsx')) { $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $sheet1 = $spreadsheet->getActiveSheet(); $sheet1->setTitle('机构利息汇总'); $sheet1->fromArray(['机构', '笔数(履行+逾期)', '当前应付利息', '结息日', '结息日应付利息'], null, 'A1'); $sheet1->fromArray(array_map(fn($x) => [$x['loan_institution'], $x['loan_count'], amount((float)$x['current_interest']), (string)($x['settlement_date'] ?? ''), amount((float)$x['settlement_interest'])], $instRows), null, 'A2'); foreach (range('A', 'E') as $col) { $sheet1->getColumnDimension($col)->setWidth(24); } $sheet2 = $spreadsheet->createSheet(); $sheet2->setTitle('当月还款计划'); $sheet2->fromArray(['机构', '档案名称', '借款主体', '到期日', '金额', '状态'], null, 'A1'); $sheet2->fromArray(array_map(fn($x) => [$x['loan_institution'], $x['archive_name'], $x['borrower'], $x['due_date'], amount((float)$x['amount']), $x['status']], $planRows), null, 'A2'); $sheet2->setCellValue('A' . (count($planRows) + 3), '合计'); $sheet2->setCellValue('E' . (count($planRows) + 3), amount($planTotal)); foreach (range('A', 'F') as $col) { $sheet2->getColumnDimension($col)->setWidth(22); } header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename="' . rawurlencode('应付本息查询_' . date('Y-m-d') . '.xlsx') . '"'); (new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet))->save('php://output'); exit; } pageHeader($me); ?>

应付本息查询

一、银行贷款机构利息统计(履行中及逾期中)

机构笔数当前应付利息结息日结息日应付利息
合计--
暂无数据

二、当月还款计划金额

机构档案名称借款主体到期日金额状态
合计--
暂无当月还款计划
query('SELECT * FROM archives ORDER BY id DESC')->fetchAll(); $financeAll = loadFinanceConfigs(); $resultRows = array_values(array_filter(qqSearchGuarantorArchives($rows, $kw), function ($r) use ($financeAll) { return archiveStatusIsOngoingAggregate((string)($r['end_date'] ?? ''), getArchiveFinanceConfig($financeAll, (int)$r['id'])); })); if ($format === 'xlsx' && $kw !== '' && class_exists('\PhpOffice\PhpSpreadsheet\Spreadsheet') && class_exists('\PhpOffice\PhpSpreadsheet\Writer\Xlsx')) { $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('对外担保查询'); $sheet->fromArray(['档案类型', '档案名称', '贷款机构/债权人', '借款主体', '担保人', '授信额度', '剩余额度', '到期日', '状态'], null, 'A1'); $sheet->fromArray(array_map(function ($r) use ($financeAll) { return [ archiveKindLabel(normalizeArchiveKind((string)($r['archive_kind'] ?? ''))), (string)($r['name'] ?? ''), (string)($r['loan_institution'] ?? ''), (string)($r['borrower'] ?? ''), (string)($r['guarantor'] ?? ''), amount($r['credit_limit'] !== null ? (float)$r['credit_limit'] : null), amount($r['remaining_limit'] !== null ? (float)$r['remaining_limit'] : null), (string)($r['end_date'] ?? ''), archiveStatusWithConfig((string)($r['end_date'] ?? ''), getArchiveFinanceConfig($financeAll, (int)$r['id'])), ]; }, $resultRows), null, 'A2'); foreach (range('A', 'I') as $col) { $sheet->getColumnDimension($col)->setWidth(18); } header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename="' . rawurlencode('对外担保查询_' . date('Y-m-d') . '.xlsx') . '"'); (new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet))->save('php://output'); exit; } pageHeader($me); ?>

对外担保查询

导出xlsx

仅列出状态为「履行中」或「逾期中」的档案。

档案类型档案名称贷款机构/债权人借款主体担保人授信额度剩余额度到期日状态
0) { $dirFilter = collectSubDirIds($dirs, $dirId); } $rows = $pdo->query('SELECT * FROM archives ORDER BY id DESC')->fetchAll(); $financeAll = loadFinanceConfigs(); $rows = array_values(array_filter($rows, function ($r) use ($mode, $selected, $dirFilter, $financeAll, $kindFilter) { if ($dirFilter && !in_array((int)$r['directory_id'], $dirFilter, true)) { return false; } $rowKind = normalizeArchiveKind((string)($r['archive_kind'] ?? '')); if ($kindFilter !== 'all' && $rowKind !== $kindFilter) { return false; } $status = archiveStatusWithConfig($r['end_date'], getArchiveFinanceConfig($financeAll, (int)$r['id'])); if ($mode === 'selected' && !in_array((int)$r['id'], $selected, true)) { return false; } return true; })); $modeName = ['all' => '全部', 'selected' => '选中'][$mode] ?? '全部'; $headers = ['档案类型', '贷款机构', '借款主体', '授信额度', '剩余额度', '起止日', '到期日', '年利率', '担保类型', '担保人', '合同状态']; $dataRows = []; foreach ($rows as $r) { $dataRows[] = [ archiveKindLabel(normalizeArchiveKind($r['archive_kind'] ?? '')), $r['loan_institution'], $r['borrower'], amount($r['credit_limit'] !== null ? (float)$r['credit_limit'] : null), amount($r['remaining_limit'] !== null ? (float)$r['remaining_limit'] : null), $r['start_date'], $r['end_date'], $r['annual_rate'] === null ? '' : ((string)$r['annual_rate'] . '%'), $r['guarantee_type'], $r['guarantor'], archiveStatusWithConfig($r['end_date'], getArchiveFinanceConfig($financeAll, (int)$r['id'])), ]; } $xlsxEnabled = class_exists('\PhpOffice\PhpSpreadsheet\Spreadsheet') && class_exists('\PhpOffice\PhpSpreadsheet\Writer\Xlsx'); if ($format === 'xlsx' && $xlsxEnabled) { $filename = '档案汇总_' . $modeName . '_' . date('Y-m-d') . '.xlsx'; $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->fromArray($headers, null, 'A1'); $sheet->fromArray($dataRows, null, 'A2'); foreach (range('A', 'K') as $col) { $sheet->getColumnDimension($col)->setWidth(16); } header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"'); $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); $writer->save('php://output'); exit; } $filename = '档案汇总_' . $modeName . '_' . date('Y-m-d') . '.csv'; header('Content-Type: text/csv; charset=UTF-8'); header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"'); echo "\xEF\xBB\xBF"; $out = fopen('php://output', 'wb'); fputcsv($out, $headers); foreach ($dataRows as $row) { fputcsv($out, $row); } fclose($out); exit; } $dirs = fetchDirectories($pdo); $dirId = (int)($_GET['dir_id'] ?? 0); $q = trim((string)($_GET['q'] ?? '')); $searchField = $_GET['search_field'] ?? 'all'; $statusFilter = $_GET['status'] ?? 'not_done'; $kindFilter = (string)($_GET['kind'] ?? ARCHIVE_KIND_BANK_LOAN); if (!in_array($kindFilter, ['all', ARCHIVE_KIND_BANK_LOAN, ARCHIVE_KIND_EXTERNAL_GUARANTEE], true)) { $kindFilter = ARCHIVE_KIND_BANK_LOAN; } $sortBy = $_GET['sort_by'] ?? 'end_date'; $sortOrder = strtolower((string)($_GET['sort_order'] ?? 'asc')) === 'desc' ? 'desc' : 'asc'; $rows = $pdo->query('SELECT * FROM archives ORDER BY id DESC')->fetchAll(); $financeAllGlobal = loadFinanceConfigs(); $dirPath = dirPathMap($dirs); $dirScope = []; if ($dirId > 0) { $dirScope = collectSubDirIds($dirs, $dirId); } $archives = array_values(array_filter($rows, function ($r) use ($q, $statusFilter, $dirScope, $searchField, $financeAllGlobal, $kindFilter) { if ($dirScope && !in_array((int)$r['directory_id'], $dirScope, true)) { return false; } $rowKind = normalizeArchiveKind((string)($r['archive_kind'] ?? '')); if ($kindFilter !== 'all' && $rowKind !== $kindFilter) { return false; } $status = archiveStatusWithConfig($r['end_date'], getArchiveFinanceConfig($financeAllGlobal, (int)$r['id'])); if ($statusFilter === 'not_done' && $status === '已完成') { return false; } if (!in_array($statusFilter, ['all', 'not_done'], true) && $statusFilter !== $status) { return false; } if ($q !== '') { if (!in_array($searchField, ['all', 'borrower', 'guarantor'], true)) { $searchField = 'all'; } if ($searchField === 'borrower') { $hay = mb_strtolower((string)$r['borrower']); } elseif ($searchField === 'guarantor') { $hay = mb_strtolower((string)$r['guarantor']); } else { $hay = mb_strtolower(implode(' ', [ $r['name'], $r['loan_institution'], $r['borrower'], $r['guarantee_type'], $r['guarantor'], ])); } if (mb_strpos($hay, mb_strtolower($q)) === false) { return false; } } return true; })); if (!in_array($sortBy, ['end_date', 'created_at', 'loan_institution'], true)) { $sortBy = 'end_date'; } usort($archives, function ($a, $b) use ($sortBy, $sortOrder, $financeAllGlobal) { $sa = archiveStatusWithConfig($a['end_date'], getArchiveFinanceConfig($financeAllGlobal, (int)$a['id'])); $sb = archiveStatusWithConfig($b['end_date'], getArchiveFinanceConfig($financeAllGlobal, (int)$b['id'])); $w = ['履行中' => 0, '逾期中' => 1, '待更新' => 2, '已完成' => 3]; $sw = ($w[$sa] ?? 9) <=> ($w[$sb] ?? 9); if ($sw !== 0) { return $sw; } $av = (string)($a[$sortBy] ?? ''); $bv = (string)($b[$sortBy] ?? ''); if ($sortBy === 'loan_institution') { $av = mb_strtolower($av); $bv = mb_strtolower($bv); } $cmp = $av <=> $bv; if ($cmp === 0) { $cmp = ((int)$a['id']) <=> ((int)$b['id']); } return $sortOrder === 'asc' ? $cmp : -$cmp; }); $totalCount = count($archives); $activeCount = 0; $doneCount = 0; foreach ($archives as $item) { $status = archiveStatusWithConfig($item['end_date'], getArchiveFinanceConfig($financeAllGlobal, (int)$item['id'])); if ($status === '履行中') { $activeCount++; } elseif ($status === '已完成') { $doneCount++; } elseif ($status === '逾期中') { $activeCount++; } } $today = date('Y-m-d'); $day60 = date('Y-m-d', strtotime('+60 days')); $dueSoonLoans = array_values(array_filter($archives, function ($a) use ($today, $day60, $financeAllGlobal) { if (empty($a['end_date'])) { return false; } $status = archiveStatusWithConfig($a['end_date'], getArchiveFinanceConfig($financeAllGlobal, (int)$a['id'])); if ($status === '已完成') { return false; } return $a['end_date'] >= $today && $a['end_date'] <= $day60; })); $dueSoonCount = count($dueSoonLoans); $dueSoonLoans = array_slice($dueSoonLoans, 0, 8); $overdueLoans = array_values(array_filter($archives, function ($a) use ($financeAllGlobal) { $status = archiveStatusWithConfig($a['end_date'], getArchiveFinanceConfig($financeAllGlobal, (int)$a['id'])); return $status === '逾期中'; })); $overdueLoanCount = count($overdueLoans); $overdueLoans = array_slice($overdueLoans, 0, 8); $repayPlanSoon = []; $overduePlanRows = []; $planAllForReminder = loadRepaymentPlans(); foreach ($planAllForReminder as $aid => $cfg) { if (empty($cfg['enabled']) || empty($cfg['plans']) || !is_array($cfg['plans'])) { continue; } $aidInt = (int)$aid; $arc = null; foreach ($archives as $tmpArc) { if ((int)$tmpArc['id'] === $aidInt) { $arc = $tmpArc; break; } } if (!$arc) { continue; } $day30 = date('Y-m-d', strtotime('+30 days')); foreach ($cfg['plans'] as $p) { $status = (string)($p['status'] ?? 'pending'); $d = (string)($p['due_date'] ?? ''); if ($status === 'paid' || $d === '') { continue; } if ($d >= $today && $d <= $day30) { $repayPlanSoon[] = [ 'archive_id' => $aidInt, 'plan_id' => (string)($p['id'] ?? ''), 'archive_kind' => normalizeArchiveKind((string)($arc['archive_kind'] ?? '')), 'loan_institution' => $arc['loan_institution'] ?? '', 'borrower' => $arc['borrower'] ?? '', 'amount' => (float)($p['amount'] ?? 0), 'due_date' => $d, ]; } if ($status === 'overdue' && $d <= $today) { $overduePlanRows[] = [ 'archive_id' => $aidInt, 'plan_id' => (string)($p['id'] ?? ''), 'archive_kind' => normalizeArchiveKind((string)($arc['archive_kind'] ?? '')), 'loan_institution' => $arc['loan_institution'] ?? '', 'borrower' => $arc['borrower'] ?? '', 'amount' => (float)($p['amount'] ?? 0), 'due_date' => $d, ]; } } } $repaySoonCount = count($repayPlanSoon); $repayPlanSoon = array_slice($repayPlanSoon, 0, 8); $overdueCount = $overdueLoanCount + count($overduePlanRows); $pendingUpdateRows = []; foreach ($archives as $a) { $aid = (int)$a['id']; $end = (string)($a['end_date'] ?? ''); if ($end === '' || $end >= $today) { continue; } if (archiveStatusWithConfig($end, getArchiveFinanceConfig($financeAllGlobal, $aid)) !== '待更新') { continue; } $pendingUpdateRows[] = [ 'row_kind' => 'loan', 'archive_id' => $aid, 'type_label' => '贷款到期', 'loan_institution' => (string)($a['loan_institution'] ?? ''), 'borrower' => (string)($a['borrower'] ?? ''), 'guarantor' => (string)($a['guarantor'] ?? ''), 'amount' => $a['remaining_limit'] !== null ? (float)$a['remaining_limit'] : null, 'date' => $end, 'plan_id' => '', ]; } $archiveIdsInScope = []; foreach ($archives as $a) { $archiveIdsInScope[(int)$a['id']] = true; } foreach ($planAllForReminder as $aidStr => $cfg) { $aidInt = (int)$aidStr; if (empty($archiveIdsInScope[$aidInt])) { continue; } if (empty($cfg['enabled']) || empty($cfg['plans']) || !is_array($cfg['plans'])) { continue; } $arc = null; foreach ($archives as $tmp) { if ((int)$tmp['id'] === $aidInt) { $arc = $tmp; break; } } if (!$arc) { continue; } foreach ($cfg['plans'] as $p) { $pst = (string)($p['status'] ?? 'pending'); $d = (string)($p['due_date'] ?? ''); if ($d === '' || $d >= $today) { continue; } if (!in_array($pst, ['pending', 'delayed'], true)) { continue; } $pendingUpdateRows[] = [ 'row_kind' => 'plan', 'archive_id' => $aidInt, 'type_label' => '还款计划到期', 'loan_institution' => (string)($arc['loan_institution'] ?? ''), 'borrower' => (string)($arc['borrower'] ?? ''), 'guarantor' => (string)($arc['guarantor'] ?? ''), 'amount' => (float)($p['amount'] ?? 0), 'date' => $d, 'plan_id' => (string)($p['id'] ?? ''), ]; } } usort($pendingUpdateRows, function ($x, $y) { $c = strcmp((string)($x['date'] ?? ''), (string)($y['date'] ?? '')); if ($c !== 0) { return $c; } return ((int)($x['archive_id'] ?? 0)) <=> ((int)($y['archive_id'] ?? 0)); }); $pendingUpdateCount = count($pendingUpdateRows); if ($action === 'pending_updates') { requireLogin(); $puBack = 'index.php'; if ($dirId > 0) { $puBack .= '?dir_id=' . $dirId; } pageHeader($me); ?>

待更新详情

以下为已过到期日且尚未在系统中标记结果的贷款与还款计划。处理后将从本列表移除。

← 返回首页

类型贷款机构借款主体担保人金额日期操作
查看档案
0): ?>
0): ?>
0): ?>
0): ?>
暂无待更新项
'贷款到期(60天)', 'archive_id' => (int)$r['id'], 'plan_id' => '', 'archive_kind' => archiveKindLabel(normalizeArchiveKind((string)($r['archive_kind'] ?? ''))), 'loan_institution' => (string)$r['loan_institution'], 'borrower' => (string)$r['borrower'], 'amount' => amount($r['remaining_limit'] !== null ? (float)$r['remaining_limit'] : null), 'date' => (string)$r['end_date']]; } foreach ($repayPlanSoon as $r) { $allReminderRows[] = ['type' => '还款计划(30天)', 'archive_id' => (int)$r['archive_id'], 'plan_id' => (string)$r['plan_id'], 'archive_kind' => archiveKindLabel(normalizeArchiveKind((string)($r['archive_kind'] ?? ''))), 'loan_institution' => (string)$r['loan_institution'], 'borrower' => (string)$r['borrower'], 'amount' => amount((float)$r['amount']), 'date' => (string)$r['due_date']]; } foreach ($overdueLoans as $r) { $allReminderRows[] = ['type' => '逾期中', 'archive_id' => (int)$r['id'], 'plan_id' => '', 'archive_kind' => archiveKindLabel(normalizeArchiveKind((string)($r['archive_kind'] ?? ''))), 'loan_institution' => (string)$r['loan_institution'], 'borrower' => (string)$r['borrower'], 'amount' => amount($r['remaining_limit'] !== null ? (float)$r['remaining_limit'] : null), 'date' => (string)$r['end_date']]; } foreach ($overduePlanRows as $r) { $allReminderRows[] = ['type' => '逾期中', 'archive_id' => (int)$r['archive_id'], 'plan_id' => (string)$r['plan_id'], 'archive_kind' => archiveKindLabel(normalizeArchiveKind((string)($r['archive_kind'] ?? ''))), 'loan_institution' => (string)$r['loan_institution'], 'borrower' => (string)$r['borrower'], 'amount' => amount((float)$r['amount']), 'date' => (string)$r['due_date']]; } $rows = array_values(array_filter($allReminderRows, function ($row) use ($reminderType) { if ($reminderType === 'all') return true; if ($reminderType === 'due') return $row['type'] === '贷款到期(60天)'; if ($reminderType === 'plan') return $row['type'] === '还款计划(30天)'; if ($reminderType === 'overdue') return $row['type'] === '逾期中'; return true; })); if ($exportFormat !== '') { $headers = ['类型', '档案类型', '贷款机构', '借款主体', '金额', '日期']; if ($exportFormat === 'xlsx' && class_exists('\PhpOffice\PhpSpreadsheet\Spreadsheet') && class_exists('\PhpOffice\PhpSpreadsheet\Writer\Xlsx')) { $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->fromArray($headers, null, 'A1'); $sheet->fromArray(array_map(fn($x) => [$x['type'], $x['archive_kind'], $x['loan_institution'], $x['borrower'], $x['amount'], $x['date']], $rows), null, 'A2'); foreach (range('A', 'F') as $col) { $sheet->getColumnDimension($col)->setWidth(20); } header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename="' . rawurlencode('提醒明细_' . date('Y-m-d') . '.xlsx') . '"'); (new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet))->save('php://output'); exit; } header('Content-Type: text/csv; charset=UTF-8'); header('Content-Disposition: attachment; filename="' . rawurlencode('提醒明细_' . date('Y-m-d') . '.csv') . '"'); echo "\xEF\xBB\xBF"; $out = fopen('php://output', 'wb'); fputcsv($out, $headers); foreach ($rows as $r) { fputcsv($out, [$r['type'], $r['archive_kind'], $r['loan_institution'], $r['borrower'], $r['amount'], $r['date']]); } fclose($out); exit; } pageHeader($me); ?>

到期提醒详情

清空
类型档案类型贷款机构借款主体金额日期操作
0): ?> 查看档案 0 && $r['type'] === '贷款到期(60天)' && isAdmin()): ?>
0 && $r['type'] === '还款计划(30天)' && isAdmin()): ?>
0 && $r['type'] === '逾期中' && isAdmin()): ?>
-
暂无提醒内容
$totalPages) { $currentPage = $totalPages; } $offset = ($currentPage - 1) * $perPage; $archivesPage = array_slice($archives, $offset, $perPage); $baseQuery = ['q' => $q, 'search_field' => $searchField, 'status' => $statusFilter, 'kind' => $kindFilter, 'per_page' => $perPage, 'sort_by' => $sortBy, 'sort_order' => $sortOrder]; if ($dirId) { $baseQuery['dir_id'] = $dirId; } unset($baseQuery['page']); $pathIds = collectAncestorIds($dirs, $dirId ?: null); pageHeader($me); ?>

目录结构

+ 一级目录

便捷查询

当前结果总数
履行中
已完成
待更新
贷款到期
还款计划
贷款逾期

档案列表

当前目录筛选中 + 新建档案
模板下载
一键清空
类型 档案名称 机构贷款行/债权人 借款主体 授信额度 剩余额度 起始日 到期日 担保人 年利率 状态 操作
担保贷款

/ 页,共
max(1, $currentPage - 1)])); $nextQuery = http_build_query(array_merge($baseQuery, ['page' => min($totalPages, $currentPage + 1)])); ?> 1): ?>上一页上一页 | 下一页下一页