Customer 360: extended Client fields, auto-migrate, updated Rolodex CRUD/templates, QDRO routes/views, importer mapping
QDRO links appear in rolodex_view.html case rows and case.html header when QDRO data exists, matching legacy flows.
This commit is contained in:
578
sync_result.html
Normal file
578
sync_result.html
Normal file
@@ -0,0 +1,578 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Panel - Delphi Database</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link href="http://localhost:8000/static/css/custom.css" rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
<body class="">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">
|
||||
Delphi Database
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/dashboard">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/rolodex">Rolodex</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/payments">Payments</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/admin">Admin</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav">
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-person-circle me-1"></i>admin
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="userDropdown">
|
||||
<li><a class="dropdown-item" href="/profile">Profile</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container-fluid mt-4">
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1 class="mb-4">
|
||||
<i class="bi bi-gear me-2"></i>Admin Panel
|
||||
</h1>
|
||||
|
||||
<!-- Alert Messages -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Upload Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-upload me-2"></i>File Upload
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="/admin/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="files" class="form-label">
|
||||
<i class="bi bi-file-earmark-spreadsheet me-2"></i>Select CSV Files
|
||||
</label>
|
||||
<input type="file" class="form-control" id="files" name="files" multiple accept=".csv">
|
||||
<div class="form-text">
|
||||
<strong>Supported formats:</strong> ROLODEX, PHONE, FILES, LEDGER, PAYMENTS, DEPOSITS, QDROS, PENSIONS, PLANINFO,
|
||||
TRNSTYPE, TRNSLKUP, FOOTERS, FILESTAT, EMPLOYEE, GRUPLKUP, FILETYPE, and all related tables (*.csv)
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="auto_import" name="auto_import" checked>
|
||||
<label class="form-check-label" for="auto_import">
|
||||
<strong>Auto-import after upload (follows Import Order Guide)</strong>
|
||||
<br>
|
||||
<small class="text-muted">Will stop on the first file that reports any row errors.</small>
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-cloud-upload me-2"></i>Upload Files
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Results -->
|
||||
|
||||
|
||||
<!-- Auto Import Results -->
|
||||
|
||||
|
||||
<!-- Upload Errors -->
|
||||
|
||||
|
||||
<!-- Database Status -->
|
||||
|
||||
|
||||
<!-- Import Order Guide -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-list-ol me-2"></i>Import Order Guide
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-3">For best results, import tables in this recommended order:</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-primary"><i class="bi bi-1-circle me-2"></i>Reference Tables (Import First)</h6>
|
||||
<ul class="list-unstyled ms-3">
|
||||
<li><i class="bi bi-arrow-right me-2"></i>TRNSTYPE</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>TRNSLKUP</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>FOOTERS</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>FILESTAT</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>EMPLOYEE</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>GRUPLKUP</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>FILETYPE</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>FVARLKUP, RVARLKUP</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-success"><i class="bi bi-2-circle me-2"></i>Core Data Tables</h6>
|
||||
<ul class="list-unstyled ms-3">
|
||||
<li><i class="bi bi-arrow-right me-2"></i>ROLODEX</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>PHONE, ROLEX_V</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>FILES (+ FILES_R, FILES_V, FILENOTS)</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>LEDGER</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>DEPOSITS, PAYMENTS</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>PLANINFO</li>
|
||||
<li><i class="bi bi-arrow-right me-2"></i>QDROS, PENSIONS (+ related tables)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-warning mt-3 mb-0">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>Important:</strong> Reference tables must be imported before core data to avoid foreign key errors.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sync to Modern Models -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-arrow-repeat me-2"></i>Sync to Modern Models
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>After importing legacy CSV data, sync it to the simplified modern application models (Client, Phone, Case, Transaction, Payment, Document).</p>
|
||||
<form action="/admin/sync" method="post" id="syncForm">
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="clearExisting" name="clear_existing" value="true">
|
||||
<label class="form-check-label" for="clearExisting">
|
||||
<strong>Clear existing modern data before sync</strong>
|
||||
<br>
|
||||
<small class="text-muted">Warning: This will delete all current Client, Phone, Case, Transaction, Payment, and Document records!</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-success" onclick="confirmSync()">
|
||||
<i class="bi bi-arrow-repeat me-2"></i>Start Sync Process
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sync Results -->
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-check-circle me-2"></i>Sync Results
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="mb-0 text-success">78360</h3>
|
||||
<small class="text-muted">Records Synced</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="mb-0 text-warning">26579</h3>
|
||||
<small class="text-muted">Records Skipped</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="mb-0 text-danger">26579</h3>
|
||||
<small class="text-muted">Errors</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="mb-3">Detailed Results by Table:</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Modern Table</th>
|
||||
<th>Synced</th>
|
||||
<th>Skipped</th>
|
||||
<th>Errors</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td><strong>Clients</strong></td>
|
||||
<td class="text-success">52076</td>
|
||||
<td class="text-warning">0</td>
|
||||
<td class="text-danger">0</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td><strong>Phones</strong></td>
|
||||
<td class="text-success">26284</td>
|
||||
<td class="text-warning">10</td>
|
||||
<td class="text-danger">10</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<details>
|
||||
<summary class="text-danger">View Errors (10)</summary>
|
||||
<ul class="mt-2 mb-0">
|
||||
|
||||
<li><small>No client found for rolodex ID: PINES BACH</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: PINES BACH</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: SPORLEDER\CL</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: SPORLEDER\CL</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: VOITH_SULZER</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: VOITH_SULZER</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: VOIT\T</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: VOIT\T</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: ZUEHLSDORF-MACK</small></li>
|
||||
|
||||
<li><small>No client found for rolodex ID: ZUEHLSDORF-MACK</small></li>
|
||||
|
||||
|
||||
</ul>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td><strong>Cases</strong></td>
|
||||
<td class="text-success">0</td>
|
||||
<td class="text-warning">0</td>
|
||||
<td class="text-danger">0</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td><strong>Transactions</strong></td>
|
||||
<td class="text-success">0</td>
|
||||
<td class="text-warning">0</td>
|
||||
<td class="text-danger">0</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td><strong>Payments</strong></td>
|
||||
<td class="text-success">0</td>
|
||||
<td class="text-warning">0</td>
|
||||
<td class="text-danger">0</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td><strong>Documents</strong></td>
|
||||
<td class="text-success">0</td>
|
||||
<td class="text-warning">26569</td>
|
||||
<td class="text-danger">26569</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<details>
|
||||
<summary class="text-danger">View Errors (26569)</summary>
|
||||
<ul class="mt-2 mb-0">
|
||||
|
||||
<li><small>No case found for file: 1989.001</small></li>
|
||||
|
||||
<li><small>No case found for file: 1989.0011</small></li>
|
||||
|
||||
<li><small>No case found for file: 1989.0012</small></li>
|
||||
|
||||
<li><small>No case found for file: 1989.0012</small></li>
|
||||
|
||||
<li><small>No case found for file: 1992.175B</small></li>
|
||||
|
||||
<li><small>No case found for file: 1994.064B</small></li>
|
||||
|
||||
<li><small>No case found for file: 1995.047</small></li>
|
||||
|
||||
<li><small>No case found for file: 1995.047</small></li>
|
||||
|
||||
<li><small>No case found for file: 1995.097</small></li>
|
||||
|
||||
<li><small>No case found for file: 1995.105</small></li>
|
||||
|
||||
|
||||
<li><small><em>... and 26559 more errors</em></small></li>
|
||||
|
||||
</ul>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Import Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-warning">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-arrow-down-circle me-2"></i>Data Import
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>No CSV files available for import. Upload files first.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Results -->
|
||||
|
||||
|
||||
<!-- Recent Import History -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Auto-refresh import status for running imports
|
||||
function refreshRunningImports() {
|
||||
const runningImports = document.querySelectorAll('span.badge.bg-warning');
|
||||
if (runningImports.length > 0) {
|
||||
// In a real application, you might implement WebSocket or polling here
|
||||
setTimeout(refreshRunningImports, 5000); // Check every 5 seconds
|
||||
}
|
||||
}
|
||||
|
||||
// Start refresh cycle if there are running imports
|
||||
refreshRunningImports();
|
||||
|
||||
// Select All functionality
|
||||
document.querySelectorAll('.select-all-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const importType = this.getAttribute('data-import-type');
|
||||
const form = this.closest('form');
|
||||
const checkboxes = form.querySelectorAll('.file-checkbox');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
|
||||
// Toggle all checkboxes in this form
|
||||
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = !allChecked;
|
||||
});
|
||||
|
||||
// Update button text
|
||||
this.innerHTML = allChecked ?
|
||||
'<i class="bi bi-check-all me-1"></i>Select All' :
|
||||
'<i class="bi bi-dash-square me-1"></i>Deselect All';
|
||||
|
||||
// Update submit button state
|
||||
const hasSelection = Array.from(checkboxes).some(cb => cb.checked);
|
||||
submitBtn.disabled = !hasSelection;
|
||||
});
|
||||
});
|
||||
|
||||
// File selection helpers
|
||||
document.querySelectorAll('.file-checkbox').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
const form = this.closest('form');
|
||||
const checkboxes = form.querySelectorAll('.file-checkbox');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const selectAllBtn = form.querySelector('.select-all-btn');
|
||||
|
||||
// Enable/disable submit button based on selection
|
||||
const hasSelection = Array.from(checkboxes).some(cb => cb.checked);
|
||||
submitBtn.disabled = !hasSelection;
|
||||
|
||||
// Update select all button state
|
||||
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||
const noneChecked = Array.from(checkboxes).every(cb => !cb.checked);
|
||||
|
||||
if (allChecked) {
|
||||
selectAllBtn.innerHTML = '<i class="bi bi-dash-square me-1"></i>Deselect All';
|
||||
} else if (noneChecked) {
|
||||
selectAllBtn.innerHTML = '<i class="bi bi-check-all me-1"></i>Select All';
|
||||
} else {
|
||||
selectAllBtn.innerHTML = '<i class="bi bi-check-square me-1"></i>Select All';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize submit buttons as disabled
|
||||
document.querySelectorAll('form[action*="/admin/import/"]').forEach(form => {
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Sync confirmation function
|
||||
function confirmSync() {
|
||||
const clearCheckbox = document.getElementById('clearExisting');
|
||||
const clearExisting = clearCheckbox.checked;
|
||||
|
||||
let message = "Are you sure you want to sync legacy data to modern models?";
|
||||
if (clearExisting) {
|
||||
message += "\n\n⚠️ WARNING: This will DELETE all existing Client, Phone, Case, Transaction, Payment, and Document records before syncing!";
|
||||
}
|
||||
|
||||
if (confirm(message)) {
|
||||
document.getElementById('syncForm').submit();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete file function
|
||||
async function deleteFile(filename, event) {
|
||||
// Prevent label click from triggering checkbox
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!confirm(`Are you sure you want to delete "${filename}"?\n\nThis action cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/admin/delete-file/${encodeURIComponent(filename)}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Reload the page to refresh the file list
|
||||
window.location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(`Error deleting file: ${error.detail || 'Unknown error'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting file:', error);
|
||||
alert(`Error deleting file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Map selected unknown files to a chosen import type
|
||||
async function mapSelectedFiles(buttonEl, importType) {
|
||||
// Find the surrounding card and form
|
||||
const cardBody = buttonEl.closest('.card-body');
|
||||
const form = cardBody.querySelector('form');
|
||||
const selectEl = cardBody.querySelector('select.form-select');
|
||||
if (!form || !selectEl) return;
|
||||
|
||||
// Collect selected filenames
|
||||
const checked = Array.from(form.querySelectorAll('.file-checkbox:checked'))
|
||||
.map(cb => cb.value);
|
||||
if (checked.length === 0) {
|
||||
alert('Select at least one file to map.');
|
||||
return;
|
||||
}
|
||||
|
||||
const targetType = selectEl.value;
|
||||
if (!targetType) {
|
||||
alert('Choose a target type.');
|
||||
return;
|
||||
}
|
||||
|
||||
buttonEl.disabled = true;
|
||||
try {
|
||||
const resp = await fetch('/admin/map-files', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ target_type: targetType, filenames: checked })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Mapping failed');
|
||||
}
|
||||
// Refresh UI
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert(`Mapping failed: ${e.message}`);
|
||||
} finally {
|
||||
buttonEl.disabled = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-light text-center text-muted mt-5 py-3">
|
||||
<div class="container">
|
||||
<small>© 2025 Delphi Database. All rights reserved.</small>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap 5 JS Bundle CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Custom JS -->
|
||||
<script src="http://localhost:8000/static/js/custom.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user