a0bdd2b5b4
Replace all corruption variants with HTML entities across 226 view files: - 3-char UTF-8-as-Win1252 sequences (ae-corruption) - Standalone smart/curly quotes that break C# Razor expressions - Partially re-corrupted variants where the 3rd byte was normalised to ASCII tools/Fix-Encoding.ps1: re-runnable sweep; uses [char] code points so the script itself never contains a literal non-ASCII character; supports -DryRun .githooks/pre-commit: blocks commits containing the ae-corruption byte signature (xc3xa2xe2x82xac); git core.hooksPath = .githooks so the hook is repo-committed and active for all future work on this machine. Build clean; 225 unit tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
198 lines
10 KiB
Plaintext
198 lines
10 KiB
Plaintext
@{
|
|
ViewData["Title"] = "Accounting Export";
|
|
ViewData["PageIcon"] = "bi-box-arrow-up";
|
|
}
|
|
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-7">
|
|
<form asp-action="Export" method="post" id="exportForm">
|
|
@Html.AntiForgeryToken()
|
|
|
|
<!-- Date Range -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold"><i class="bi bi-calendar-range me-2 text-primary"></i>Date Range</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-semibold">Start Date <span class="text-danger">*</span></label>
|
|
<input type="date" name="startDate" class="form-control" value="@ViewBag.DefaultStart" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-semibold">End Date <span class="text-danger">*</span></label>
|
|
<input type="date" name="endDate" class="form-control" value="@ViewBag.DefaultEnd" required>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 d-flex flex-wrap gap-2" id="quickRanges">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setRange('this-month')">This Month</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setRange('last-month')">Last Month</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setRange('this-quarter')">This Quarter</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setRange('last-quarter')">Last Quarter</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setRange('this-year')">This Year</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setRange('last-year')">Last Year</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Format -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold"><i class="bi bi-file-earmark-zip me-2 text-primary"></i>Export Format</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="format-card d-block border rounded p-3 cursor-pointer @("quickbooks" == "quickbooks" ? "" : "")" id="card-quickbooks">
|
|
<input type="radio" name="format" value="quickbooks" class="d-none format-radio" id="fmt-quickbooks">
|
|
<div class="d-flex align-items-start gap-3">
|
|
<div class="mt-1 text-success" style="font-size:1.6rem;"><i class="bi bi-building"></i></div>
|
|
<div>
|
|
<div class="fw-semibold">QuickBooks Desktop</div>
|
|
<div class="text-muted small">IIF files — import directly via File > Utilities > Import</div>
|
|
<div class="mt-2">
|
|
<span class="badge bg-light text-dark border me-1">customers.iif</span>
|
|
<span class="badge bg-light text-dark border me-1">invoices_payments.iif</span>
|
|
<span class="badge bg-light text-dark border">expenses_bills.iif</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="format-card d-block border rounded p-3 cursor-pointer" id="card-csv">
|
|
<input type="radio" name="format" value="csv" class="d-none format-radio" id="fmt-csv" checked>
|
|
<div class="d-flex align-items-start gap-3">
|
|
<div class="mt-1 text-primary" style="font-size:1.6rem;"><i class="bi bi-filetype-csv"></i></div>
|
|
<div>
|
|
<div class="fw-semibold">CSV (Universal)</div>
|
|
<div class="text-muted small">Works with QuickBooks Online, Xero, Wave, Excel, and more</div>
|
|
<div class="mt-2">
|
|
<span class="badge bg-light text-dark border me-1">invoices.csv</span>
|
|
<span class="badge bg-light text-dark border me-1">payments.csv</span>
|
|
<span class="badge bg-light text-dark border">+5 more</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- What's Included -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold"><i class="bi bi-list-check me-2 text-primary"></i>What's Included</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-2">
|
|
<div class="col-sm-6">
|
|
<div class="d-flex align-items-center gap-2 text-muted small">
|
|
<i class="bi bi-check-circle-fill text-success"></i> Customer list
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<div class="d-flex align-items-center gap-2 text-muted small">
|
|
<i class="bi bi-check-circle-fill text-success"></i> Invoices & line items
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<div class="d-flex align-items-center gap-2 text-muted small">
|
|
<i class="bi bi-check-circle-fill text-success"></i> Payments received
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<div class="d-flex align-items-center gap-2 text-muted small">
|
|
<i class="bi bi-check-circle-fill text-success"></i> Direct expenses
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<div class="d-flex align-items-center gap-2 text-muted small">
|
|
<i class="bi bi-check-circle-fill text-success"></i> Vendor bills (AP)
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<div class="d-flex align-items-center gap-2 text-muted small">
|
|
<i class="bi bi-check-circle-fill text-success"></i> Bill payments
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary px-4" id="exportBtn">
|
|
<i class="bi bi-download me-2"></i>Download Export Package
|
|
</button>
|
|
<a asp-controller="Reports" asp-action="Index" class="btn btn-outline-secondary">Cancel</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
// Format card selection
|
|
document.querySelectorAll('.format-radio').forEach(function(radio) {
|
|
radio.addEventListener('change', updateFormatCards);
|
|
});
|
|
|
|
function updateFormatCards() {
|
|
document.querySelectorAll('.format-card').forEach(function(card) {
|
|
const radio = card.querySelector('.format-radio');
|
|
card.classList.toggle('border-primary', radio.checked);
|
|
card.classList.toggle('bg-primary-subtle', radio.checked);
|
|
});
|
|
}
|
|
|
|
// Init — mark CSV as selected by default
|
|
document.getElementById('fmt-csv').checked = true;
|
|
updateFormatCards();
|
|
|
|
// Prevent double-submit
|
|
document.getElementById('exportForm').addEventListener('submit', function() {
|
|
const btn = document.getElementById('exportBtn');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Generating…';
|
|
setTimeout(function() { btn.disabled = false; btn.innerHTML = '<i class="bi bi-download me-2"></i>Download Export Package'; }, 8000);
|
|
});
|
|
|
|
// Quick date range shortcuts
|
|
function setRange(range) {
|
|
const now = new Date();
|
|
const y = now.getFullYear();
|
|
const m = now.getMonth(); // 0-indexed
|
|
let start, end;
|
|
|
|
if (range === 'this-month') {
|
|
start = new Date(y, m, 1);
|
|
end = new Date(y, m + 1, 0);
|
|
} else if (range === 'last-month') {
|
|
start = new Date(y, m - 1, 1);
|
|
end = new Date(y, m, 0);
|
|
} else if (range === 'this-quarter') {
|
|
const q = Math.floor(m / 3);
|
|
start = new Date(y, q * 3, 1);
|
|
end = new Date(y, q * 3 + 3, 0);
|
|
} else if (range === 'last-quarter') {
|
|
const q = Math.floor(m / 3) - 1;
|
|
const qy = q < 0 ? y - 1 : y;
|
|
const qq = q < 0 ? 3 : q;
|
|
start = new Date(qy, qq * 3, 1);
|
|
end = new Date(qy, qq * 3 + 3, 0);
|
|
} else if (range === 'this-year') {
|
|
start = new Date(y, 0, 1);
|
|
end = new Date(y, 11, 31);
|
|
} else if (range === 'last-year') {
|
|
start = new Date(y - 1, 0, 1);
|
|
end = new Date(y - 1, 11, 31);
|
|
}
|
|
|
|
const fmt = d => d.toISOString().slice(0, 10);
|
|
document.querySelector('[name="startDate"]').value = fmt(start);
|
|
document.querySelector('[name="endDate"]').value = fmt(end);
|
|
}
|
|
</script>
|
|
}
|