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:
@@ -10,6 +10,10 @@ namespace GerbilManager.Tests
|
||||
{
|
||||
private readonly string _dir;
|
||||
private readonly string _resolvedJsonPath;
|
||||
private Guid ContactId { get; set; }
|
||||
private Guid FatherId { get; set; }
|
||||
private Guid ContractId { get; set; }
|
||||
private Guid UnknownGerbilId { get; set; }
|
||||
|
||||
public IngestResolvedServiceTests()
|
||||
{
|
||||
@@ -22,6 +26,10 @@ namespace GerbilManager.Tests
|
||||
var motherId = Guid.NewGuid();
|
||||
var litterId = Guid.NewGuid();
|
||||
var photoId = Guid.NewGuid();
|
||||
ContactId = contactId;
|
||||
FatherId = fatherId;
|
||||
ContractId = Guid.NewGuid();
|
||||
UnknownGerbilId = Guid.NewGuid();
|
||||
|
||||
var data = new
|
||||
{
|
||||
@@ -127,6 +135,20 @@ namespace GerbilManager.Tests
|
||||
FileName = "photo1.jpg",
|
||||
SortOrder = 0
|
||||
}
|
||||
},
|
||||
SaleContracts = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
Id = ContractId,
|
||||
ContactId = contactId,
|
||||
Price = 35.0m,
|
||||
HandoverDate = "2023-09-29",
|
||||
ContractDate = "2023-09-29",
|
||||
FileName = "Zucht der kleinen Chaoten _ (Papa) - Test Breeder_.docx",
|
||||
// One known animal + one unknown id (must be skipped defensively).
|
||||
Animals = new[] { fatherId, UnknownGerbilId }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -199,6 +221,41 @@ namespace GerbilManager.Tests
|
||||
Assert.NotNull(litter.Provenance);
|
||||
Assert.Contains("aus Wurfchronik", litter.Provenance);
|
||||
Assert.Contains("fromWurfchronik", litter.Provenance);
|
||||
|
||||
// SaleContracts are created from the payload, and animal links to
|
||||
// unknown gerbils are skipped defensively (only the known father links).
|
||||
Assert.Equal(1, await db.SaleContracts.CountAsync());
|
||||
var contract = await db.SaleContracts.Include(c => c.Animals).SingleAsync();
|
||||
Assert.Equal(ContractId, contract.Id);
|
||||
Assert.Equal(ContactId, contract.ContactId);
|
||||
Assert.Equal(35.0m, contract.Price);
|
||||
Assert.Equal(new DateOnly(2023, 9, 29), contract.HandoverDate);
|
||||
var link = Assert.Single(contract.Animals);
|
||||
Assert.Equal(FatherId, link.GerbilId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IngestResolved_recreates_sale_contracts_on_every_ingest()
|
||||
{
|
||||
using var db = NewDb();
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
{ "Import:SourcePath", _dir }
|
||||
})
|
||||
.Build();
|
||||
|
||||
var service = new IngestResolvedService(db, config, null!);
|
||||
await service.RunAsync();
|
||||
Assert.Equal(1, await db.SaleContracts.CountAsync());
|
||||
|
||||
// Re-ingest wipes then recreates from the (deterministic) payload —
|
||||
// the contract survives by being part of the payload, no duplicate PK.
|
||||
await service.RunAsync();
|
||||
Assert.Equal(1, await db.SaleContracts.CountAsync());
|
||||
var contract = await db.SaleContracts.Include(c => c.Animals).SingleAsync();
|
||||
Assert.Equal(ContractId, contract.Id);
|
||||
Assert.Single(contract.Animals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user