Files
DXApp/DXApp.TemplateKitProject/Pages/Invoices/Details.cshtml
OlgunR 920dce13d5 Add support for invoice attachments
Introduced the `InvoiceAttachment` entity and its relationship with `ZugferdInvoice` to manage extracted invoice attachments. Updated `AppDbContext` and added a migration to create the `InvoiceAttachments` table with cascading delete behavior and an index for optimized queries.

Enhanced the UI to display attachments in `Details.cshtml`, including file type icons, file size, and extraction date. Added a new `ViewAttachment` page to render or download attachments based on their type, with support for XML, plain text, images, and downloads.

Implemented `AttachmentViewerService` to determine viewer types and MIME types for attachments. Registered the service in the DI container. Updated `Upload.cshtml.cs` to save extracted attachments to the database.

Improved user experience with syntax highlighting for XML files and appropriate messages for unsupported file types.
2026-06-02 15:03:37 +02:00

137 lines
5.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@page
@model DXApp.TemplateKitProject.Pages.Invoices.DetailsModel
@{
ViewData["Title"] = "Rechnungsdetails";
string FormatFileSize(long bytes)
{
if (bytes < 1024) return $"{bytes} Bytes";
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:N2} KB";
return $"{bytes / (1024.0 * 1024.0):N2} MB";
}
}
<h2>📄 Rechnungsdetails</h2>
<a href="/Invoices" class="btn btn-secondary mb-3">← Zurück zur Liste</a>
@if (!string.IsNullOrEmpty(Model.Invoice?.ResultFilePath))
{
<button class="btn btn-primary mb-3 ms-2"
onclick="openPdfViewer(@Model.Invoice.Id)">
📄 Ergebnis anzeigen
</button>
}
@if (Model.Invoice is null)
{
<div class="alert alert-danger">Rechnung nicht gefunden.</div>
}
else
{
<table class="table table-sm table-bordered w-auto">
<tr><th>ID</th><td>@Model.Invoice.Id</td></tr>
<tr><th>Rechnungsnummer</th><td>@Model.Invoice.InvoiceNumber</td></tr>
<tr><th>Rechnungsdatum</th><td>@Model.Invoice.InvoiceDate.ToString("dd.MM.yyyy")</td></tr>
<tr><th>Verkäufer</th><td>@Model.Invoice.SellerName</td></tr>
<tr><th>USt-ID Verkäufer</th><td>@Model.Invoice.SellerTaxId</td></tr>
<tr><th>Käufer</th><td>@Model.Invoice.BuyerName</td></tr>
<tr><th>Währung</th><td>@Model.Invoice.CurrencyCode</td></tr>
<tr><th>Steuerbetrag</th><td>@Model.Invoice.TaxAmount.ToString("N2")</td></tr>
<tr><th>Gesamtbetrag</th><td><strong>@Model.Invoice.TotalAmount.ToString("N2")</strong></td></tr>
<tr><th>IBAN</th><td>@Model.Invoice.Iban</td></tr>
<tr><th>Quelle</th><td>@Model.Invoice.SourceType</td></tr>
<tr><th>Guideline-ID</th><td><code>@Model.Invoice.GuidelineId</code></td></tr>
<tr><th>Importiert am</th><td>@Model.Invoice.ImportedAt.ToString("dd.MM.yyyy HH:mm")</td></tr>
<tr>
<th>Result-PDF</th>
<td>
@if (!string.IsNullOrEmpty(Model.Invoice.ResultFilePath))
{
<small class="text-muted">@Model.Invoice.ResultFilePath</small>
}
else
{
<span class="text-muted"> nicht vorhanden </span>
}
</td>
</tr>
</table>
@* Anhänge-Sektion *@
@if (Model.Invoice.Attachments.Any())
{
<h4 class="mt-4">📎 Anhänge (@Model.Invoice.Attachments.Count)</h4>
<div class="list-group">
@foreach (var attachment in Model.Invoice.Attachments)
{
var icon = attachment.IsZugferdXml ? "📋" : "📄";
var extension = System.IO.Path.GetExtension(attachment.OriginalFileName).ToLowerInvariant();
icon = extension switch
{
".xml" => "📋",
".pdf" => "📄",
".jpg" or ".jpeg" or ".png" or ".gif" => "🖼️",
".txt" => "📝",
_ => "📎"
};
<a href="/Invoices/ViewAttachment?filePath=@Uri.EscapeDataString(attachment.SavedFilePath)"
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
target="_blank">
<div>
<span class="me-2">@icon</span>
<strong>@attachment.OriginalFileName</strong>
@if (attachment.IsZugferdXml)
{
<span class="badge bg-success ms-2">ZUGFeRD-XML</span>
}
<br />
<small class="text-muted">
Größe: @FormatFileSize(attachment.FileSizeBytes) ·
Extrahiert: @attachment.ExtractedAt.ToString("dd.MM.yyyy HH:mm")
</small>
</div>
<span class="badge bg-primary">Öffnen</span>
</a>
}
</div>
}
else
{
<h4 class="mt-4">📎 Anhänge</h4>
<div class="alert alert-info">Keine Anhänge extrahiert.</div>
}
@(Html.DevExtreme().Popup()
.ID("pdf-viewer-popup")
.Title("PDF Viewer")
.Width("90%")
.Height("90%")
.ShowCloseButton(true)
.OnHiding("onPdfPopupHiding")
.ContentTemplate(new JS(@"function() {
return '<iframe id=""pdf-iframe"" style=""width:100%;height:100%;border:none;""></iframe>';
}"))
)
}
<script>
function openPdfViewer(invoiceId) {
var pdfUrl = window.location.origin + '/Invoices/ViewPdf?id=' + invoiceId;
var viewerUrl = '/js/pdfjs/web/viewer.html?file=' + encodeURIComponent(pdfUrl);
var popup = $('#pdf-viewer-popup').dxPopup('instance');
// onShown sicherstellen dass iframe im DOM ist
popup.option('onShown', function () {
$('#pdf-iframe').attr('src', viewerUrl);
});
popup.show();
}
function onPdfPopupHiding() {
// iframe src leeren beim Schließen → verhindert dass PDF im Hintergrund weiter läuft
$('#pdf-iframe').attr('src', '');
}
</script>