Compare commits
3 Commits
6a46bf4f4b
...
245f7a8268
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
245f7a8268 | ||
|
|
6582370c08 | ||
|
|
87f27682ce |
82
DXApp.TemplateKitProject/Migrations/20260527094043_AddResultFilePath.Designer.cs
generated
Normal file
82
DXApp.TemplateKitProject/Migrations/20260527094043_AddResultFilePath.Designer.cs
generated
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DXApp.TemplateKitProject.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DXApp.TemplateKitProject.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260527094043_AddResultFilePath")]
|
||||||
|
partial class AddResultFilePath
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DXApp.TemplateKitProject.Models.ZugferdInvoice", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("BuyerName")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("CurrencyCode")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Iban")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ImportedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<DateTime>("InvoiceDate")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceNumber")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("RawXml")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ResultFilePath")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("SellerName")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("SellerTaxId")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("SourceType")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("TaxAmount")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalAmount")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ZugferdInvoices");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DXApp.TemplateKitProject.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddResultFilePath : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ResultFilePath",
|
||||||
|
table: "ZugferdInvoices",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ResultFilePath",
|
||||||
|
table: "ZugferdInvoices");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
DXApp.TemplateKitProject/Migrations/20260527133241_AddGuidelineId.Designer.cs
generated
Normal file
85
DXApp.TemplateKitProject/Migrations/20260527133241_AddGuidelineId.Designer.cs
generated
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DXApp.TemplateKitProject.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DXApp.TemplateKitProject.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260527133241_AddGuidelineId")]
|
||||||
|
partial class AddGuidelineId
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DXApp.TemplateKitProject.Models.ZugferdInvoice", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("BuyerName")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("CurrencyCode")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("GuidelineId")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Iban")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ImportedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<DateTime>("InvoiceDate")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceNumber")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("RawXml")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ResultFilePath")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("SellerName")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("SellerTaxId")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("SourceType")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("TaxAmount")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalAmount")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ZugferdInvoices");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DXApp.TemplateKitProject.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddGuidelineId : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "GuidelineId",
|
||||||
|
table: "ZugferdInvoices",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "GuidelineId",
|
||||||
|
table: "ZugferdInvoices");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,9 @@ namespace DXApp.TemplateKitProject.Migrations
|
|||||||
b.Property<string>("CurrencyCode")
|
b.Property<string>("CurrencyCode")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("GuidelineId")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("Iban")
|
b.Property<string>("Iban")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
@@ -51,6 +54,9 @@ namespace DXApp.TemplateKitProject.Migrations
|
|||||||
b.Property<string>("RawXml")
|
b.Property<string>("RawXml")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ResultFilePath")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("SellerName")
|
b.Property<string>("SellerName")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
|||||||
@@ -15,5 +15,7 @@
|
|||||||
public string RawXml { get; set; } = string.Empty; // Original-XML zur Sicherheit
|
public string RawXml { get; set; } = string.Empty; // Original-XML zur Sicherheit
|
||||||
public DateTime ImportedAt { get; set; }
|
public DateTime ImportedAt { get; set; }
|
||||||
public string SourceType { get; set; } = string.Empty; // "Upload" oder "Email"
|
public string SourceType { get; set; } = string.Empty; // "Upload" oder "Email"
|
||||||
|
public string ResultFilePath { get; set; } = string.Empty; // Pfad der Result-PDF
|
||||||
|
public string GuidelineId { get; set; } = string.Empty; // ZUGFeRD Guideline-ID aus XMP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,13 @@
|
|||||||
{
|
{
|
||||||
<strong>⚠ Kein ZUGFeRD-XML gefunden.</strong>
|
<strong>⚠ Kein ZUGFeRD-XML gefunden.</strong>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.ResultFilePath))
|
||||||
|
{
|
||||||
|
<div class="alert alert-success mt-2">
|
||||||
|
📦 <strong>Result-PDF erstellt:</strong>
|
||||||
|
<small class="text-muted">@Model.ResultFilePath</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@* PDF/A-Konformitätsstufe anzeigen *@
|
@* PDF/A-Konformitätsstufe anzeigen *@
|
||||||
@@ -97,5 +104,12 @@
|
|||||||
<tr><th>Importiert am</th><td>@Model.ImportedInvoice.ImportedAt.ToString("dd.MM.yyyy HH:mm")</td></tr>
|
<tr><th>Importiert am</th><td>@Model.ImportedInvoice.ImportedAt.ToString("dd.MM.yyyy HH:mm")</td></tr>
|
||||||
</table>
|
</table>
|
||||||
<div class="alert alert-success mt-2">✔ Rechnung wurde in der Datenbank gespeichert (ID: @Model.ImportedInvoice.Id)</div>
|
<div class="alert alert-success mt-2">✔ Rechnung wurde in der Datenbank gespeichert (ID: @Model.ImportedInvoice.Id)</div>
|
||||||
|
@if (Model.IsDuplicate)
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning mt-2">
|
||||||
|
⚠️ <strong>Duplikat:</strong> Diese Rechnung wurde bereits importiert (ID: @Model.ImportedInvoice!.Id).
|
||||||
|
Es wurde kein neuer Eintrag angelegt.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using DXApp.TemplateKitProject.Models;
|
using DXApp.TemplateKitProject.Data;
|
||||||
|
using DXApp.TemplateKitProject.Models;
|
||||||
using DXApp.TemplateKitProject.Services;
|
using DXApp.TemplateKitProject.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
@@ -8,6 +9,8 @@ namespace DXApp.TemplateKitProject.Pages.Invoices;
|
|||||||
public class UploadModel(
|
public class UploadModel(
|
||||||
PdfAttachmentExtractorService extractor,
|
PdfAttachmentExtractorService extractor,
|
||||||
ZugferdImportService zugferdImportService,
|
ZugferdImportService zugferdImportService,
|
||||||
|
PdfResultPackageService resultPackageService,
|
||||||
|
AppDbContext db,
|
||||||
ILogger<UploadModel> logger) : PageModel
|
ILogger<UploadModel> logger) : PageModel
|
||||||
{
|
{
|
||||||
[BindProperty]
|
[BindProperty]
|
||||||
@@ -17,6 +20,8 @@ public class UploadModel(
|
|||||||
public bool ExtractionDone { get; private set; }
|
public bool ExtractionDone { get; private set; }
|
||||||
public string? ErrorMessage { get; private set; }
|
public string? ErrorMessage { get; private set; }
|
||||||
public ZugferdInvoice? ImportedInvoice { get; private set; }
|
public ZugferdInvoice? ImportedInvoice { get; private set; }
|
||||||
|
public string? ResultFilePath { get; private set; }
|
||||||
|
public bool IsDuplicate { get; private set; }
|
||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
{ }
|
{ }
|
||||||
@@ -42,8 +47,9 @@ public class UploadModel(
|
|||||||
// Stream in MemoryStream puffern → kann zweimal gelesen werden
|
// Stream in MemoryStream puffern → kann zweimal gelesen werden
|
||||||
using var memStream = new MemoryStream();
|
using var memStream = new MemoryStream();
|
||||||
await PdfFile.CopyToAsync(memStream);
|
await PdfFile.CopyToAsync(memStream);
|
||||||
|
var originalBytes = memStream.ToArray(); // ← neu: als byte[] merken
|
||||||
|
|
||||||
// 1. Anhänge extrahieren und auf Disk speichern
|
// 1. Anhänge extrahieren
|
||||||
memStream.Position = 0;
|
memStream.Position = 0;
|
||||||
Result = extractor.ExtractAttachments(memStream, PdfFile.FileName);
|
Result = extractor.ExtractAttachments(memStream, PdfFile.FileName);
|
||||||
|
|
||||||
@@ -51,7 +57,25 @@ public class UploadModel(
|
|||||||
if (Result.HasZugferdXml)
|
if (Result.HasZugferdXml)
|
||||||
{
|
{
|
||||||
memStream.Position = 0;
|
memStream.Position = 0;
|
||||||
ImportedInvoice = await zugferdImportService.ImportAsync(memStream, "Upload");
|
ImportedInvoice = await zugferdImportService.ImportAsync(memStream, "Upload", Result.ZugferdGuidelineId);
|
||||||
|
|
||||||
|
// Duplikat erkennen: vorhandener Eintrag hat ImportedAt von früher
|
||||||
|
if (ImportedInvoice is not null && ImportedInvoice.ImportedAt < DateTime.UtcNow.AddSeconds(-5))
|
||||||
|
IsDuplicate = true;
|
||||||
|
|
||||||
|
// 3. Result-Package erstellen (nur wenn Import erfolgreich UND kein Duplikat)
|
||||||
|
if (ImportedInvoice is not null && !IsDuplicate)
|
||||||
|
{
|
||||||
|
ResultFilePath = await resultPackageService.CreateResultPackageAsync(
|
||||||
|
originalBytes, PdfFile.FileName, ImportedInvoice);
|
||||||
|
|
||||||
|
// ResultFilePath in DB aktualisieren
|
||||||
|
if (ResultFilePath is not null)
|
||||||
|
{
|
||||||
|
ImportedInvoice.ResultFilePath = ResultFilePath;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ builder.Services.AddDbContext<AppDbContext>(options =>
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
builder.Services.AddScoped<PdfAttachmentExtractorService>();
|
builder.Services.AddScoped<PdfAttachmentExtractorService>();
|
||||||
|
builder.Services.AddScoped<PdfResultPackageService>();
|
||||||
builder.Services.AddScoped<ZugferdExtractorService>();
|
builder.Services.AddScoped<ZugferdExtractorService>();
|
||||||
builder.Services.AddScoped<ZugferdParserService>();
|
builder.Services.AddScoped<ZugferdParserService>();
|
||||||
builder.Services.AddScoped<ZugferdImportService>();
|
builder.Services.AddScoped<ZugferdImportService>();
|
||||||
|
|||||||
93
DXApp.TemplateKitProject/Services/PdfResultPackageService.cs
Normal file
93
DXApp.TemplateKitProject/Services/PdfResultPackageService.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using DevExpress.Pdf;
|
||||||
|
using DXApp.TemplateKitProject.Models;
|
||||||
|
|
||||||
|
namespace DXApp.TemplateKitProject.Services;
|
||||||
|
|
||||||
|
public class PdfResultPackageService(
|
||||||
|
IConfiguration configuration,
|
||||||
|
ILogger<PdfResultPackageService> logger)
|
||||||
|
{
|
||||||
|
public async Task<string?> CreateResultPackageAsync(
|
||||||
|
byte[] originalPdfBytes,
|
||||||
|
string originalFileName,
|
||||||
|
ZugferdInvoice invoice)
|
||||||
|
{
|
||||||
|
// 1. Bericht-PDF suchen
|
||||||
|
var reportPath = FindReportFile(originalFileName);
|
||||||
|
if (reportPath is null)
|
||||||
|
{
|
||||||
|
logger.LogWarning(
|
||||||
|
"Kein Ergebnisbericht gefunden für '{FileName}'.", originalFileName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation(
|
||||||
|
"Ergebnisbericht gefunden: '{ReportPath}'.", reportPath);
|
||||||
|
|
||||||
|
// 2. Ausgabepfad bestimmen
|
||||||
|
var outputDir = configuration["PdfResults:OutputDirectory"]
|
||||||
|
?? Path.Combine(Path.GetTempPath(), "PdfResults");
|
||||||
|
Directory.CreateDirectory(outputDir);
|
||||||
|
|
||||||
|
var baseName = Path.GetFileNameWithoutExtension(originalFileName);
|
||||||
|
var outputPath = Path.Combine(outputDir, $"{baseName}_result.pdf");
|
||||||
|
|
||||||
|
// 3. Original auf PDF/A-3b hochstufen + Bericht anhängen
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
// Original in MemoryStream laden
|
||||||
|
using var inputStream = new MemoryStream(originalPdfBytes);
|
||||||
|
using var outputStream = new MemoryStream();
|
||||||
|
|
||||||
|
// PDF/A-3b Konvertierung
|
||||||
|
var converter = new PdfDocumentConverter(inputStream);
|
||||||
|
converter.Convert(PdfCompatibility.PdfA3b);
|
||||||
|
|
||||||
|
// Konvertiertes PDF in MemoryStream speichern
|
||||||
|
using var convertedStream = new MemoryStream();
|
||||||
|
converter.SaveDocument(convertedStream);
|
||||||
|
convertedStream.Position = 0;
|
||||||
|
|
||||||
|
// Bericht als Anhang einbetten
|
||||||
|
using var processor = new PdfDocumentProcessor();
|
||||||
|
processor.LoadDocument(convertedStream);
|
||||||
|
|
||||||
|
processor.AttachFile(new PdfFileAttachment
|
||||||
|
{
|
||||||
|
FileName = Path.GetFileName(reportPath),
|
||||||
|
Description = "Ergebnisbericht",
|
||||||
|
MimeType = "application/pdf",
|
||||||
|
Relationship = PdfAssociatedFileRelationship.Supplement,
|
||||||
|
CreationDate = DateTime.Now,
|
||||||
|
Data = File.ReadAllBytes(reportPath)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Speichern
|
||||||
|
processor.SaveDocument(outputPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.LogInformation(
|
||||||
|
"Result-PDF gespeichert: '{OutputPath}'.", outputPath);
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? FindReportFile(string originalFileName)
|
||||||
|
{
|
||||||
|
var inputDir = configuration["PdfResultReports:InputDirectory"]
|
||||||
|
?? Path.Combine(Path.GetTempPath(), "PdfResultReports");
|
||||||
|
|
||||||
|
if (!Directory.Exists(inputDir))
|
||||||
|
{
|
||||||
|
logger.LogWarning("Berichtsverzeichnis nicht gefunden: '{Dir}'.", inputDir);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Konvention Option A: {originalname}_report.pdf
|
||||||
|
var baseName = Path.GetFileNameWithoutExtension(originalFileName);
|
||||||
|
var reportName = $"{baseName}_report.pdf";
|
||||||
|
var reportPath = Path.Combine(inputDir, reportName);
|
||||||
|
|
||||||
|
return File.Exists(reportPath) ? reportPath : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using DXApp.TemplateKitProject.Data;
|
using DXApp.TemplateKitProject.Data;
|
||||||
using DXApp.TemplateKitProject.Models;
|
using DXApp.TemplateKitProject.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace DXApp.TemplateKitProject.Services;
|
namespace DXApp.TemplateKitProject.Services;
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ public class ZugferdImportService(
|
|||||||
AppDbContext db,
|
AppDbContext db,
|
||||||
ILogger<ZugferdImportService> logger)
|
ILogger<ZugferdImportService> logger)
|
||||||
{
|
{
|
||||||
public async Task<ZugferdInvoice?> ImportAsync(Stream pdfStream, string sourceType)
|
public async Task<ZugferdInvoice?> ImportAsync(Stream pdfStream, string sourceType, string guidelineId = "")
|
||||||
{
|
{
|
||||||
var xml = extractor.ExtractXml(pdfStream);
|
var xml = extractor.ExtractXml(pdfStream);
|
||||||
|
|
||||||
@@ -20,11 +21,31 @@ public class ZugferdImportService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var invoice = parser.Parse(xml);
|
var invoice = parser.Parse(xml);
|
||||||
|
|
||||||
|
// Duplikatprüfung
|
||||||
|
var duplicate = await db.ZugferdInvoices.FirstOrDefaultAsync(i =>
|
||||||
|
i.InvoiceNumber == invoice.InvoiceNumber &&
|
||||||
|
i.SellerTaxId == invoice.SellerTaxId);
|
||||||
|
|
||||||
|
if (duplicate is not null)
|
||||||
|
{
|
||||||
|
logger.LogWarning(
|
||||||
|
"Duplikat erkannt: Rechnung '{Number}' von '{Seller}' existiert bereits (ID: {Id}).",
|
||||||
|
invoice.InvoiceNumber, invoice.SellerName, duplicate.Id);
|
||||||
|
return duplicate;
|
||||||
|
}
|
||||||
|
|
||||||
invoice.SourceType = sourceType;
|
invoice.SourceType = sourceType;
|
||||||
|
invoice.GuidelineId = guidelineId;
|
||||||
|
invoice.ImportedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
db.ZugferdInvoices.Add(invoice);
|
db.ZugferdInvoices.Add(invoice);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
logger.LogInformation(
|
||||||
|
"Rechnung '{Number}' von '{Seller}' importiert (ID: {Id}).",
|
||||||
|
invoice.InvoiceNumber, invoice.SellerName, invoice.Id);
|
||||||
|
|
||||||
return invoice;
|
return invoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,5 +11,11 @@
|
|||||||
},
|
},
|
||||||
"PdfExtraction": {
|
"PdfExtraction": {
|
||||||
"OutputDirectory": "C:\\PdfExtractions"
|
"OutputDirectory": "C:\\PdfExtractions"
|
||||||
|
},
|
||||||
|
"PdfResultReports": {
|
||||||
|
"InputDirectory": "C:\\PdfResultReports"
|
||||||
|
},
|
||||||
|
"PdfResults": {
|
||||||
|
"OutputDirectory": "C:\\PdfResults"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user