feat(import): Abgabeverträge als SaleContract-Datensätze importieren
Die ~1095 geparsten Verträge werden jetzt als echte SaleContract-Records erzeugt, sodass die Verträge-Seite gefüllt ist (statt 0). enrich_from_contracts() liefert zusätzlich eine saleContracts-Liste (deterministische Id aus Dateiname, Käufer- ContactId, Preis, Abgabe-/Vertragsdatum, FileName, gematchte Tier-Ids); main() schreibt sie als Top-Level-Key in resolved_import.json. IngestResolvedService legt SaleContract + SaleContractAnimal an (FK-sicher nach Kontakten/Tieren, unbekannte Links übersprungen) — Tabelle wird wie gehabt gewischt und aus dem Payload neu befüllt (idempotent). Ergebnis: 1033 Verträge (611 mit ≥1 Tier, alle datiert; 54 Dateinamen-Dubletten zusammengefasst, 7 datumlose + 1 ohne Käufer übersprungen). Kein Schema-Change (datumlose übersprungen statt Spalten nullable → keine Migration). DOCX-Download: importierte Verträge haben keine Word-Datei im contract-storage → DTO.HasFile=false, Frontend blendet den Word-Button aus (Hinweis „Keine Word- Datei"), PDF wird weiterhin generiert. Download-Endpoint liefert sauber 404 statt 500 bei fehlender Datei. Tests: dotnet 213, vitest 129, playwright 16 (neuer vertraege.spec.ts), Python grün. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -30,18 +30,24 @@ namespace GerbilManagerWebAPI.Endpoints
|
||||
var group = app.MapGroup("/contracts").WithTags("Contracts");
|
||||
|
||||
// GET /contracts (Gridify; GerbilIds via Include mitgeladen)
|
||||
group.MapGet("/", async ([AsParameters] GridifyParams query, ApplicationContext db) =>
|
||||
TypedResults.Ok(await db.SaleContracts.AsNoTracking()
|
||||
// HasFile pro Zeile per File.Exists (nur die Seite, i. d. R. 20 Zeilen).
|
||||
group.MapGet("/", async ([AsParameters] GridifyParams query, ApplicationContext db,
|
||||
IConfiguration config, IWebHostEnvironment env) =>
|
||||
{
|
||||
var root = ContractRoot(config, env);
|
||||
return TypedResults.Ok(await db.SaleContracts.AsNoTracking()
|
||||
.Include(c => c.Animals)
|
||||
.ToPagedResultAsync(query, ToDto)));
|
||||
.ToPagedResultAsync(query, c => ToDto(c, root)));
|
||||
});
|
||||
|
||||
// GET /contracts/{id}
|
||||
group.MapGet("/{id:guid}", async Task<Results<Ok<SaleContractDto>, NotFound>> (Guid id, ApplicationContext db) =>
|
||||
group.MapGet("/{id:guid}", async Task<Results<Ok<SaleContractDto>, NotFound>> (
|
||||
Guid id, ApplicationContext db, IConfiguration config, IWebHostEnvironment env) =>
|
||||
{
|
||||
var c = await db.SaleContracts.AsNoTracking()
|
||||
.Include(x => x.Animals)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
return c is null ? TypedResults.NotFound() : TypedResults.Ok(ToDto(c));
|
||||
return c is null ? TypedResults.NotFound() : TypedResults.Ok(ToDto(c, ContractRoot(config, env)));
|
||||
});
|
||||
|
||||
// POST /contracts — der Abgabe-Abschluss.
|
||||
@@ -272,9 +278,15 @@ namespace GerbilManagerWebAPI.Endpoints
|
||||
return cleaned.Trim('-');
|
||||
}
|
||||
|
||||
internal static SaleContractDto ToDto(SaleContract c) => new(
|
||||
internal static SaleContractDto ToDto(SaleContract c, string? contractRoot = null) => new(
|
||||
c.Id, c.ContactId, c.Price, c.HandoverDate, c.ContractDate, c.FileName, c.CreatedAt,
|
||||
c.Animals.Select(a => a.GerbilId).ToList(),
|
||||
$"/contracts/{c.Id}/file");
|
||||
$"/contracts/{c.Id}/file",
|
||||
// Imported contracts have no staged .docx -> HasFile=false (Word
|
||||
// download 404s gracefully). When no root is given (e.g. POST, where
|
||||
// the file was just written) we assume the file exists.
|
||||
HasFile: contractRoot is null
|
||||
|| (!string.IsNullOrEmpty(c.FileName)
|
||||
&& File.Exists(Path.Combine(contractRoot, c.FileName))));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user