626 lines
40 KiB
HTML
626 lines
40 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Document Detail{% endblock %}
|
|
{% block content %}
|
|
{% if error == "line_count_mismatch" %}
|
|
<div class="error-box">
|
|
Could not save reviewed OCR because line count did not match OCR layout.
|
|
Expected {{ error_expected }}, got {{ error_actual }}.
|
|
</div>
|
|
{% elif error == "save_ocr_corrected_failed" %}
|
|
<div class="error-box">
|
|
Could not save OCR-corrected PDF. Check that reviewed OCR line count matches raw OCR line count.
|
|
</div>
|
|
{% elif success == "rerun_ocr" %}
|
|
<div class="success-message">OCR rerun successfully.</div>
|
|
{% elif success == "regenerated_line_items" %}
|
|
<div class="success-message">Line items regenerated successfully.</div>
|
|
{% elif success == "saved_reviewed_ocr" %}
|
|
<div class="success-message">Reviewed OCR saved.</div>
|
|
{% elif success == "saved_reviewed_ocr" %}
|
|
<div class="success-message">Reviewed OCR saved.</div>
|
|
{% elif error == "rerun_ocr_failed" %}
|
|
<div class="error-box">OCR rerun failed.</div>
|
|
{% elif error == "save_field_enriched_failed" %}
|
|
<div class="error-box">Could not save field-enriched PDF.</div>
|
|
{% endif %}
|
|
|
|
<div class="detail-sticky-header">
|
|
<div class="topbar">
|
|
<div>
|
|
<h1 class="page-title">{{ document.document_id }}</h1>
|
|
<p class="page-subtitle">{{ document.original_filename or document.canonical_filename or document.document_type }}</p>
|
|
</div>
|
|
<div class="badges">
|
|
<span class="badge {% if document.review_status == 'reviewed' %}reviewed{% else %}pending{% endif %}">{{ document.review_status }}</span>
|
|
{% if review_state and review_state.reviewed_at %}
|
|
<span class="badge reviewed">doc reviewed</span>
|
|
{% endif %}
|
|
{% if review_state and review_state.is_approved %}
|
|
<span class="badge reviewed">approved</span>
|
|
{% endif %}
|
|
{% if review_state and review_state.is_excluded %}
|
|
<span class="badge">excluded</span>
|
|
{% endif %}
|
|
<span class="badge">{{ document.document_type }}</span>
|
|
<span class="badge">{{ document.mime_type }}</span>
|
|
</div>
|
|
</div> <div class="card" style="margin-bottom: 0;">
|
|
<div style="display:flex; flex-direction:column; gap:0.75rem;">
|
|
|
|
|
|
<div class="detail-doc-actions-row">
|
|
<div class="detail-doc-actions-grid">
|
|
<form method="post" action="/documents/{{ document.document_id }}/save-document-type" class="detail-doc-type-form">
|
|
<div class="detail-type-input-wrap" style="position:relative;">
|
|
<label for="document_type_input">Document type</label>
|
|
<input
|
|
id="document_type_input"
|
|
type="text"
|
|
name="document_type"
|
|
value="{{ document.document_type or '' }}"
|
|
placeholder="receipt"
|
|
autocomplete="off"
|
|
style="min-width:160px; max-width:260px;"
|
|
>
|
|
<div id="document-type-suggestions" style="display:none; position:absolute; top:100%; left:0; right:0; z-index:20; background:#fff; border:1px solid #d7dce5; border-radius:12px; margin-top:0.35rem; max-height:220px; overflow-y:auto; box-shadow:0 10px 24px rgba(15,23,42,0.10);"></div>
|
|
</div>
|
|
<button type="submit" class="top-pill-button detail-update-button" style="height:38px;">Update</button>
|
|
</form>
|
|
|
|
<div class="detail-flags-stack">
|
|
<label class="detail-check-label detail-check-inline">
|
|
<input type="checkbox" form="save-review-flags-form" name="is_approved" value="1" {% if review_state and review_state.is_approved %}checked{% endif %}>
|
|
<span>Approved</span>
|
|
</label>
|
|
<label class="detail-check-label detail-check-inline">
|
|
<input type="checkbox" form="save-review-flags-form" name="is_excluded" value="1" {% if review_state and review_state.is_excluded %}checked{% endif %}>
|
|
<span>Excluded</span>
|
|
</label>
|
|
</div>
|
|
|
|
<form method="post" action="/documents/{{ document.document_id }}/save-review-flags" class="detail-review-flags-form" id="save-review-flags-form">
|
|
<button type="submit" class="top-pill-button detail-saveflags-button" style="height:38px;">Save flags</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<form method="post" action="/documents/{{ document.document_id }}/save-pdf" id="save-pdf-form" class="detail-save-pdf-form" style="display:flex; align-items:flex-end; gap:0.6rem; flex-wrap:wrap; margin:0;">
|
|
<div class="detail-path-row" style="display:flex; align-items:flex-end; gap:0.6rem; width:100%; flex-wrap:nowrap;">
|
|
<div style="flex:1; min-width:260px;">
|
|
<label for="proposed_storage_path_input">Proposed path</label>
|
|
<input
|
|
id="proposed_storage_path_input"
|
|
type="text"
|
|
name="output_path"
|
|
value="{{ proposed_storage_path }}"
|
|
data-default-path="{{ proposed_storage_path }}"
|
|
readonly
|
|
style="width:100%;"
|
|
>
|
|
</div>
|
|
<button type="button" id="toggle-path-edit" class="top-pill-button">Edit path</button>
|
|
</div>
|
|
</form>
|
|
|
|
</div>
|
|
|
|
<div class="queue-nav-row">
|
|
<a class="button-link" href="/queue/">Back to Queue</a>
|
|
{% if next_ocr_doc %}
|
|
<a class="button-link" href="/documents/{{ next_ocr_doc.document_id }}">Next in Queue</a>
|
|
{% elif next_fields_doc %}
|
|
<a class="button-link" href="/documents/{{ next_fields_doc.document_id }}">Next in Queue</a>
|
|
{% elif next_doc %}
|
|
<a class="button-link" href="/documents/{{ next_doc.document_id }}">Next in Queue</a>
|
|
{% endif %}
|
|
<form method="post" action="/documents/{{ document.document_id }}/move-to-trash" class="detail-trash-form detail-trash-inline">
|
|
<button class="danger" type="submit">Move to trash</button>
|
|
</form>
|
|
<button type="submit" form="save-pdf-form" class="button-link primary detail-save-document-button">Save Document</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{% if error == "storage_unavailable" %}
|
|
<div style="background:#ffe4e6; border:1px solid #fecdd3; color:#7f1d1d; padding:0.75rem 1rem; border-radius:10px; margin-bottom:1rem;">
|
|
Storage mount unavailable. Please retry in a moment.
|
|
</div>
|
|
{% endif %}
|
|
|
|
|
|
<div class="detail-view-mode-bar">
|
|
<button type="button" class="detail-view-mode-button active" data-detail-mode="split">Split</button>
|
|
<button type="button" class="detail-view-mode-button" data-detail-mode="preview">PDF</button>
|
|
<button type="button" class="detail-view-mode-button" data-detail-mode="review">Review</button>
|
|
</div>
|
|
|
|
<div class="workspace-grid">
|
|
<section>
|
|
<div class="card preview-card">
|
|
<h2 class="card-title">Document preview</h2>
|
|
{% if not storage_available %}
|
|
<p class="empty-state">Storage mount unavailable. Preview is temporarily unavailable.</p>
|
|
{% elif file_url %}
|
|
{% if document.mime_type == "application/pdf" %}
|
|
<embed class="preview-frame" src="{{ file_url }}" type="application/pdf">
|
|
{% elif document.mime_type in ["image/jpeg", "image/png"] %}
|
|
<img class="preview-image" src="{{ file_url }}" alt="Document image">
|
|
{% else %}
|
|
<p><a href="{{ file_url }}" target="_blank">Open file</a></p>
|
|
{% endif %}
|
|
{% else %}
|
|
<p class="empty-state">No preview available.</p>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<div class="card">
|
|
<div class="right-pane-tabs">
|
|
<button class="tab-button{% if active_tab == 'ocr-review' %} active{% endif %}" type="button" data-tab="ocr-review">OCR Review</button>
|
|
<button class="tab-button{% if active_tab == 'extracted-fields' %} active{% endif %}" type="button" data-tab="extracted-fields">Extracted Fields</button>
|
|
<button class="tab-button{% if active_tab == 'additional-fields' %} active{% endif %}" type="button" data-tab="additional-fields">Additional Fields</button>
|
|
<button class="tab-button{% if active_tab == 'line-items' %} active{% endif %}" type="button" data-tab="line-items">Line Items</button>
|
|
<button class="tab-button{% if active_tab == 'versions' %} active{% endif %}" type="button" data-tab="versions">Versions</button>
|
|
<button class="tab-button{% if active_tab == 'raw-ocr' %} active{% endif %}" type="button" data-tab="raw-ocr">Raw OCR</button>
|
|
<button class="tab-button{% if active_tab == 'source-options' %} active{% endif %}" type="button" data-tab="source-options">Source Options</button>
|
|
</div>
|
|
|
|
<div class="tab-panel{% if active_tab == 'ocr-review' %} active{% endif %}" data-panel="ocr-review">
|
|
<div class="ocr-review-header-row">
|
|
<h2 class="card-title">Reviewed OCR</h2>
|
|
<form method="post" action="/documents/{{ document.document_id }}/rerun-ocr" class="ocr-rerun-inline-form">
|
|
<button type="submit">Rerun OCR</button>
|
|
</form>
|
|
</div>
|
|
{% if current_text_version %}
|
|
<p>Current OCR version: v{{ current_text_version.version_number }} — {{ current_text_version.version_type }} — {{ current_text_version.created_at }}</p>
|
|
{% else %}
|
|
<p class="empty-state">No OCR version available yet.</p>
|
|
{% endif %}
|
|
|
|
<p>
|
|
Expected OCR lines: <span id="expected-lines">{{ expected_line_count }}</span>
|
|
|
|
|
Current editor lines: <span id="actual-lines">{{ actual_line_count }}</span>
|
|
</p>
|
|
|
|
<form method="post" action="/documents/{{ document.document_id }}/review-text">
|
|
<div class="form-field full">
|
|
<label for="reviewed_text">
|
|
Review OCR text (1 line each; mismatch affects PDF)
|
|
</label>
|
|
<div class="editor-wrap">
|
|
<pre class="line-numbers" id="line-numbers">{% for n in line_numbers %}{{ n }}
|
|
{% endfor %}</pre>
|
|
<textarea id="reviewed_text" name="reviewed_text" rows="34" spellcheck="false">{{ review_text_value }}</textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-field full">
|
|
<label>Quality flags</label>
|
|
<div>
|
|
{% for flag in quality_flag_options %}
|
|
<label style="display:block; margin-bottom: 0.25rem;">
|
|
<input type="checkbox" name="quality_flags" value="{{ flag }}" {% if flag in current_quality_flags %}checked{% endif %}>
|
|
{{ flag }}
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-field full">
|
|
<label for="quality_note">Quality note</label>
|
|
<textarea id="quality_note" name="quality_note" rows="4">{{ current_quality_note }}</textarea>
|
|
</div>
|
|
|
|
<div class="button-row">
|
|
<button class="primary" type="submit" id="save-reviewed-btn">Save reviewed OCR</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="tab-panel{% if active_tab == 'extracted-fields' %} active{% endif %}" data-panel="extracted-fields">
|
|
<h2 class="card-title">Extracted fields</h2>
|
|
{% if current_extracted_version_number %}
|
|
{% set current_extracted_meta = (
|
|
extracted_version_options
|
|
| selectattr(0, "equalto", current_extracted_version_number)
|
|
| list
|
|
| first
|
|
) %}
|
|
<p>
|
|
Current extracted version: v{{ current_extracted_version_number }}
|
|
{% if current_extracted_meta %}— {{ current_extracted_meta[1] }}{% endif %}
|
|
</p>
|
|
{% endif %}
|
|
|
|
{% if current_extracted %}
|
|
|
|
{% else %}
|
|
<p class="empty-state">No extracted fields saved yet.</p>
|
|
{% endif %}
|
|
|
|
<form method="get" action="/documents/{{ document.document_id }}">
|
|
<input type="hidden" name="autofill_extracted" value="1">
|
|
<input type="hidden" name="tab" value="extracted-fields">
|
|
<div class="button-row">
|
|
<button type="submit">Auto-extract fields</button>
|
|
</div>
|
|
</form>
|
|
|
|
<form method="post" action="/documents/{{ document.document_id }}/save-extracted-fields" style="margin-top: 1rem;">
|
|
<div class="form-grid">
|
|
<div class="form-field"><label>Merchant raw</label><input type="text" name="merchant_raw" value="{{ extracted_form.merchant_raw }}"></div>
|
|
<div class="form-field"><label>Merchant normalized</label><input type="text" name="merchant_normalized" value="{{ extracted_form.merchant_normalized }}"></div>
|
|
<div class="form-field"><label>Transaction date</label><input type="date" name="transaction_date" value="{{ extracted_form.transaction_date }}"></div>
|
|
<div class="form-field"><label>Transaction time</label><input type="text" name="transaction_time" value="{{ extracted_form.transaction_time }}"></div>
|
|
<div class="form-field"><label>Subtotal</label><input type="text" name="subtotal" value="{{ extracted_form.subtotal }}"></div>
|
|
<div class="form-field"><label>Tax</label><input type="text" name="tax" value="{{ extracted_form.tax }}"></div>
|
|
<div class="form-field"><label>Total</label><input type="text" name="total" value="{{ extracted_form.total }}"></div>
|
|
<div class="form-field"><label>Currency</label><input type="text" name="currency" value="{{ extracted_form.currency }}"></div>
|
|
<div class="form-field"><label>Payment method</label><input type="text" name="payment_method" value="{{ extracted_form.payment_method }}"></div>
|
|
<div class="form-field"><label>Reference number</label><input type="text" name="receipt_number" value="{{ extracted_form.receipt_number }}"></div>
|
|
<div class="form-field full"><label>Location</label><input type="text" name="location" value="{{ extracted_form.location }}"></div>
|
|
<div class="form-field full"><label>Counterparty</label><input type="text" name="counterparty" value="{{ extracted_form.counterparty }}"></div>
|
|
<div class="form-field full"><label>Extra JSON</label><textarea name="extra_json" rows="8">{{ extracted_form.extra_json }}</textarea></div>
|
|
</div>
|
|
|
|
<div class="button-row" style="margin-top: 1rem;">
|
|
<button class="primary" type="submit">Save extracted fields</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="tab-panel{% if active_tab == 'additional-fields' %} active{% endif %}" data-panel="additional-fields">
|
|
<h2 class="card-title">Additional fields</h2>
|
|
{% if current_additional_version_number %}{% set current_additional_meta = (
|
|
additional_version_options
|
|
| selectattr(0, "equalto", current_additional_version_number)
|
|
| list
|
|
| first
|
|
) %}
|
|
<p>
|
|
Current additional version: v{{ current_additional_version_number }}
|
|
{% if current_additional_meta %}— {{ current_additional_meta[1] }}{% endif %}
|
|
</p>{% endif %}
|
|
|
|
{% if current_additional %}
|
|
|
|
{% else %}
|
|
<p class="empty-state">No additional fields saved yet.</p>
|
|
{% endif %}
|
|
|
|
{% if presets %}
|
|
<form method="get" action="/documents/{{ document.document_id }}" style="margin-bottom: 1rem;">
|
|
<input type="hidden" name="tab" value="additional-fields">
|
|
<div class="button-row">
|
|
<select name="preset_id">
|
|
<option value="">Select preset</option>
|
|
{% for preset in presets %}
|
|
<option value="{{ preset.id }}" {% if selected_preset_id == preset.id %}selected{% endif %}>{{ preset.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="submit">Apply preset</button>
|
|
<a class="button-link" href="/presets/">Manage presets</a>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
<div class="button-row" style="margin-bottom: 1rem;">
|
|
<a class="button-link" href="/presets/">Create first preset</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<form method="post" action="/documents/{{ document.document_id }}/save-additional-fields">
|
|
<div class="form-grid">
|
|
<div class="form-field">
|
|
<label>Primary owner</label>
|
|
<input type="text" name="owner_primary" value="{{ additional_form.owner_primary }}">
|
|
</div>
|
|
<div class="form-field">
|
|
<label>Secondary owner</label>
|
|
<input type="text" name="owner_secondary" value="{{ additional_form.owner_secondary }}">
|
|
</div>
|
|
<div class="form-field">
|
|
<label>Paid by person</label>
|
|
<input type="text" name="paid_by_person" value="{{ additional_form.paid_by_person }}">
|
|
</div>
|
|
<div class="form-field full">
|
|
<label>Covered people</label>
|
|
<input type="text" name="covered_people" value="{{ additional_form.covered_people }}" placeholder="Full Name, Full Name">
|
|
</div>
|
|
<div class="form-field full">
|
|
<label>Attendees</label>
|
|
<input type="text" name="attendees" value="{{ additional_form.attendees }}" placeholder="Full Name, Full Name">
|
|
</div>
|
|
<div class="form-field full">
|
|
<label>Occasion note</label>
|
|
<input type="text" name="occasion_note" value="{{ additional_form.occasion_note }}" placeholder="Dinner with Camie and friends">
|
|
</div>
|
|
<div class="form-field">
|
|
<label><input type="checkbox" name="is_shared_expense" value="1" {% if additional_form.is_shared_expense %}checked{% endif %}> Shared expense</label>
|
|
</div>
|
|
<div class="form-field full">
|
|
<label>Reimbursement expected from</label>
|
|
<input type="text" name="reimbursement_expected_from" value="{{ additional_form.reimbursement_expected_from }}" placeholder="Full Name, Full Name">
|
|
</div>
|
|
<div class="form-field">
|
|
<label>Reimbursement paid by</label>
|
|
<input type="text" name="reimbursement_paid_by" value="{{ additional_form.reimbursement_paid_by }}">
|
|
</div>
|
|
<div class="form-field">
|
|
<label>Reimbursement paid to</label>
|
|
<input type="text" name="reimbursement_paid_to" value="{{ additional_form.reimbursement_paid_to }}">
|
|
</div>
|
|
<div class="form-field">
|
|
<label>Reimbursement paid amount</label>
|
|
<input type="text" name="reimbursement_paid_amount" value="{{ additional_form.reimbursement_paid_amount }}">
|
|
</div>
|
|
<div class="form-field">
|
|
<label>Reimbursement paid date</label>
|
|
<input type="date" name="reimbursement_paid_date" value="{{ additional_form.reimbursement_paid_date }}">
|
|
</div>
|
|
<div class="form-field full">
|
|
<label>Reimbursement note</label>
|
|
<textarea name="reimbursement_note" rows="4">{{ additional_form.reimbursement_note }}</textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="button-row" style="margin-top: 1rem;">
|
|
<button class="primary" type="submit">Save additional fields</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
|
|
<div class="tab-panel{% if active_tab == 'line-items' %} active{% endif %}" data-panel="line-items">
|
|
<h2 class="card-title">Line Items</h2>
|
|
|
|
{% if current_line_item_version %}
|
|
<p>Current line item version: v{{ current_line_item_version.version_number }} — {{ current_line_item_version.created_at }}</p>
|
|
{% else %}
|
|
<p class="empty-state">No line items saved yet.</p>
|
|
{% endif %}
|
|
|
|
<div class="button-row" style="margin-bottom: 0.75rem;">
|
|
<form method="post" action="/documents/{{ document.document_id }}/regenerate-line-items" style="display:inline;">
|
|
<button type="submit">Regenerate Line Items</button>
|
|
</form>
|
|
</div>
|
|
|
|
<form method="post" action="/documents/{{ document.document_id }}/save-line-items">
|
|
{% set base_count = line_items|length %}
|
|
{% set row_count = base_count + 3 if base_count > 0 else 12 %}
|
|
<input type="hidden" name="row_count" value="{{ row_count }}">
|
|
|
|
<div style="margin-bottom: 0.5rem;">
|
|
<button type="button" onclick="addRow()">+ Add Row</button>
|
|
|
|
</div>
|
|
|
|
<div style="overflow-x:auto;">
|
|
<table style="width:100%; border-collapse:collapse;">
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align:left; padding:0.5rem;">#</th>
|
|
<th style="text-align:left; padding:0.5rem;">Date</th>
|
|
<th style="text-align:left; padding:0.5rem;">Description</th>
|
|
<th style="text-align:left; padding:0.5rem;">Qty</th>
|
|
<th style="text-align:left; padding:0.5rem;">Unit</th>
|
|
<th style="text-align:left; padding:0.5rem;">Total</th>
|
|
<th style="text-align:left; padding:0.5rem;">Tax</th>
|
|
<th style="text-align:left; padding:0.5rem;">Category</th>
|
|
<th style="text-align:left; padding:0.5rem;">Notes</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for i in range(row_count) %}
|
|
{% set item = line_items[i] if i < line_items|length else None %}
|
|
<tr>
|
|
<td style="padding:0.35rem;">{{ i + 1 }}</td>
|
|
<td style="padding:0.35rem;"><input type="date" name="entry_date_{{ i }}" value="{{ item.entry_date.isoformat() if item and item.entry_date else '' }}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="description_{{ i }}" value="{{ item.description if item else '' }}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="quantity_{{ i }}" value="{{ item.quantity if item and item.quantity is not none else '' }}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="unit_price_{{ i }}" value="{{ item.unit_price if item and item.unit_price is not none else '' }}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="line_total_{{ i }}" value="{{ item.line_total if item and item.line_total is not none else '' }}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="tax_amount_{{ i }}" value="{{ item.tax_amount if item and item.tax_amount is not none else '' }}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="category_{{ i }}" value="{{ item.category if item else '' }}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="notes_{{ i }}" value="{{ item.notes if item else '' }}" style="width:100%;"></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="button-row" style="margin-top: 1rem;">
|
|
<button class="primary" type="submit">Save line items</button>
|
|
</div>
|
|
</form>
|
|
|
|
<script>
|
|
function addRow() {
|
|
const panel = document.querySelector('[data-panel="line-items"]');
|
|
if (!panel) return;
|
|
|
|
const tbody = panel.querySelector("tbody");
|
|
const rowCountInput = panel.querySelector('input[name="row_count"]');
|
|
const i = tbody.querySelectorAll("tr").length;
|
|
|
|
const row = document.createElement("tr");
|
|
row.innerHTML = `
|
|
<td style="padding:0.35rem;">${i + 1}</td>
|
|
<td style="padding:0.35rem;"><input type="date" name="entry_date_${i}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="description_${i}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="quantity_${i}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="unit_price_${i}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="line_total_${i}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="tax_amount_${i}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="category_${i}" style="width:100%;"></td>
|
|
<td style="padding:0.35rem;"><input type="text" name="notes_${i}" style="width:100%;"></td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
|
|
rowCountInput.value = i + 1;
|
|
}
|
|
</script>
|
|
</div>
|
|
|
|
<div class="tab-panel{% if active_tab == 'versions' %} active{% endif %}" data-panel="versions">
|
|
<h2 class="card-title">Document versions</h2>
|
|
{% if version_rows %}
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Version</th>
|
|
<th>Type</th>
|
|
<th>Path</th>
|
|
<th>Size</th>
|
|
<th>Created</th>
|
|
<th>Notes</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for version, file_exists in version_rows %}
|
|
<tr>
|
|
<td>v{{ version.version_number }}</td>
|
|
<td>{{ version.version_type }}</td>
|
|
<td>
|
|
{{ version.file_path }}
|
|
<div style="margin-top:0.25rem;">
|
|
{% if file_exists %}
|
|
<span class="badge">Available</span>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td>{{ human_size(version.file_size_bytes) }}</td>
|
|
<td>{{ version.created_at }}</td>
|
|
<td>{{ version.notes or "" }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="empty-state">No versions found.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
|
|
<div class="tab-panel{% if active_tab == 'source-options' %} active{% endif %}" data-panel="source-options">
|
|
<h2 class="card-title">Source Options</h2>
|
|
|
|
<form method="post" action="/documents/{{ document.document_id }}/source-options" style="display:flex; flex-direction:column; gap:1rem;">
|
|
<div class="card" style="padding:1rem;">
|
|
<h3 style="margin-top:0;">File Source</h3>
|
|
<div style="display:flex; flex-direction:column; gap:0.75rem;">
|
|
<label style="display:flex; align-items:center; gap:0.5rem;">
|
|
<input type="radio" name="file_action" value="none" checked>
|
|
<span>No file change</span>
|
|
</label>
|
|
<label style="display:flex; align-items:center; gap:0.5rem;">
|
|
<input type="radio" name="file_action" value="revert_original">
|
|
<span>Revert to original file</span>
|
|
</label>
|
|
<label style="display:flex; align-items:center; gap:0.5rem;">
|
|
<input type="radio" name="file_action" value="revert_current_version">
|
|
<span>Revert to current saved version</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" style="padding:1rem;">
|
|
<h3 style="margin-top:0;">Data Reset</h3>
|
|
<div style="display:grid; grid-template-columns: 180px 1fr; gap:0.75rem; align-items:center; margin-bottom:0.75rem;">
|
|
<strong>OCR</strong>
|
|
<select name="ocr_restore_choice">
|
|
<option value="none" selected>No change</option>
|
|
<option value="original">Original</option>
|
|
{% for version_number, version_type, created_at in ocr_version_options %}
|
|
<option value="version:{{ version_number }}">v{{ version_number }} — {{ version_type }} — {{ created_at }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div style="display:grid; grid-template-columns: 180px 1fr; gap:0.75rem; align-items:center; margin-bottom:0.75rem;">
|
|
<strong>Extracted fields</strong>
|
|
<select name="extracted_restore_choice">
|
|
<option value="none" selected>No change</option>
|
|
<option value="original">Original</option>
|
|
{% for version_number, created_at in extracted_version_options %}
|
|
<option value="version:{{ version_number }}">v{{ version_number }} — {{ created_at }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div style="display:grid; grid-template-columns: 180px 1fr; gap:0.75rem; align-items:center; margin-bottom:0.75rem;">
|
|
<strong>Additional fields</strong>
|
|
<select name="additional_restore_choice">
|
|
<option value="none" selected>No change</option>
|
|
<option value="original">Original</option>
|
|
{% for version_number, created_at in additional_version_options %}
|
|
<option value="version:{{ version_number }}">v{{ version_number }} — {{ created_at }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div style="display:grid; grid-template-columns: 180px 1fr; gap:0.75rem; align-items:center;">
|
|
<strong>Line items</strong>
|
|
<select name="line_item_restore_choice">
|
|
<option value="none" selected>No change</option>
|
|
<option value="clear">Clear</option>
|
|
{% for version in document.line_item_set_versions %}
|
|
<option value="version:{{ version.version_number }}">v{{ version.version_number }} — {{ version.created_at }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<button class="btn btn-primary" type="submit">Apply Source Options</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="tab-panel{% if active_tab == 'raw-ocr' %} active{% endif %}" data-panel="raw-ocr">
|
|
<h2 class="card-title">Raw OCR</h2>
|
|
{% if raw_ocr %}
|
|
<div class="meta-grid">
|
|
<div class="meta-item"><span class="meta-label">Text version</span>v{{ raw_ocr.version_number }}</div>
|
|
<div class="meta-item"><span class="meta-label">OCR engine</span>{{ raw_ocr.ocr_engine or "unknown" }}</div>
|
|
<div class="meta-item"><span class="meta-label">Engine version</span>{{ raw_ocr.ocr_engine_version or "unknown" }}</div>
|
|
<div class="meta-item"><span class="meta-label">Rerun source</span>{{ raw_ocr.rerun_source or "unknown" }}</div>
|
|
<div class="meta-item"><span class="meta-label">Quality score</span>{{ raw_ocr.quality_score if raw_ocr.quality_score is not none else "not scored yet" }}</div>
|
|
<div class="meta-item"><span class="meta-label">Quality note</span>{{ raw_ocr.quality_note or "" }}</div>
|
|
</div>
|
|
<p><strong>Quality flags:</strong> {{ raw_ocr.quality_flags if raw_ocr and raw_ocr.quality_flags else [] }}</p>
|
|
<pre class="codeblock">{{ raw_ocr.text_content }}</pre>
|
|
{% else %}
|
|
<p class="empty-state">No raw OCR text found.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2 class="card-title">Metadata</h2>
|
|
<div class="meta-grid">
|
|
<div class="meta-item"><span class="meta-label">Type</span>{{ document.document_type }}</div>
|
|
<div class="meta-item"><span class="meta-label">Review status</span>{{ document.review_status }}</div>
|
|
<div class="meta-item"><span class="meta-label">Source path</span>{{ document.source_path }}</div>
|
|
<div class="meta-item"><span class="meta-label">Current path</span>{{ document.current_path }}</div>
|
|
<div class="meta-item"><span class="meta-label">Original filename</span>{{ document.original_filename }}</div>
|
|
<div class="meta-item"><span class="meta-label">Canonical filename</span>{{ document.canonical_filename }}</div>
|
|
<div class="meta-item"><span class="meta-label">MIME type</span>{{ document.mime_type }}</div>
|
|
<div class="meta-item"><span class="meta-label">File size</span>{{ human_size(document.file_size) }}</div>
|
|
<div class="meta-item"><span class="meta-label">Page count</span>{{ document.page_count }}</div>
|
|
<div class="meta-item"><span class="meta-label">Share path</span>{{ document.share_path or "" }}</div>
|
|
<div class="meta-item"><span class="meta-label">Created at</span>{{ document.created_at }}</div>
|
|
<div class="meta-item"><span class="meta-label">Updated at</span>{{ document.updated_at }}</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|