feat: Fehler-melden + Datenherkunft auf Kontakte & Würfe erweitern
- Kontakt-Detailseite: „Fehler melden"- und „Datenherkunft"-Button. - Wurf-Ansicht: „Datenherkunft"-Button (Feedback war bereits vorhanden). - Feedback-Entity um loses, nullable ContactId erweitert (kein FK → übersteht Ingest-Wipe); Migration AddFeedbackContactId. - Contact.Provenance + Litter.Provenance (nullable text); Migration AddContactLitterProvenance; im Ingest gemappt und in den DTOs zurückgegeben. - Import: build_entity_provenance() generalisiert; Kontakte (sourceFiles, Züchter/Abnehmer-Hinweise) und Würfe (Wurfchronik vs. Diagramm-rekonstruiert, Geschwister-Merge) erhalten Herkunftsdaten in resolved_import.json. - Frontend: ProvenanceDialog generalisiert (EntityProvenance + entityLabel). Tests erweitert (Ingest-Round-trip Kontakt/Wurf, contact-scoped Feedback übersteht Wipe). dotnet(212)/vitest(129)/playwright(36)/tsc/eslint grün. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -44,9 +44,10 @@ namespace GerbilManagerWebAPI.Dtos
|
||||
Guid? FatherId,
|
||||
Guid? MotherId,
|
||||
DateOnly? ExpectedGoHomeDate,
|
||||
string? Notes);
|
||||
string? Notes,
|
||||
string? Provenance);
|
||||
|
||||
public record ContactDto(Guid Id, string Name, string? Email, string? Phone, string? Address, string? Notes, bool IsBreeder, bool IsReceiver, string? NameSuffix);
|
||||
public record ContactDto(Guid Id, string Name, string? Email, string? Phone, string? Address, string? Notes, bool IsBreeder, bool IsReceiver, string? NameSuffix, string? Provenance);
|
||||
|
||||
public record EnclosureDto(Guid Id, string Name, string? Notes);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace GerbilManagerWebAPI.Dtos
|
||||
string Context,
|
||||
Guid? GerbilId,
|
||||
Guid? LitterId,
|
||||
Guid? ContactId,
|
||||
string? EntityName,
|
||||
string? Url,
|
||||
DateTimeOffset? ClientTimestamp);
|
||||
@@ -17,6 +18,7 @@ namespace GerbilManagerWebAPI.Dtos
|
||||
string Context,
|
||||
Guid? GerbilId,
|
||||
Guid? LitterId,
|
||||
Guid? ContactId,
|
||||
string? EntityName,
|
||||
string? Url,
|
||||
DateTimeOffset? ClientTimestamp,
|
||||
|
||||
@@ -55,6 +55,6 @@ namespace GerbilManagerWebAPI.Endpoints
|
||||
return app;
|
||||
}
|
||||
|
||||
private static ContactDto ToDto(Contact c) => new(c.Id, c.Name, c.Email, c.Phone, c.Address, c.Notes, c.IsBreeder, c.IsReceiver, c.NameSuffix);
|
||||
private static ContactDto ToDto(Contact c) => new(c.Id, c.Name, c.Email, c.Phone, c.Address, c.Notes, c.IsBreeder, c.IsReceiver, c.NameSuffix, c.Provenance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace GerbilManagerWebAPI.Endpoints
|
||||
Context = string.IsNullOrWhiteSpace(input.Context) ? "unknown" : input.Context.Trim(),
|
||||
GerbilId = input.GerbilId,
|
||||
LitterId = input.LitterId,
|
||||
ContactId = input.ContactId,
|
||||
EntityName = input.EntityName,
|
||||
Url = input.Url,
|
||||
ClientTimestamp = input.ClientTimestamp,
|
||||
@@ -56,7 +57,7 @@ namespace GerbilManagerWebAPI.Endpoints
|
||||
}
|
||||
|
||||
private static FeedbackDto ToDto(Feedback f) =>
|
||||
new(f.Id, f.Message, f.Context, f.GerbilId, f.LitterId, f.EntityName, f.Url,
|
||||
new(f.Id, f.Message, f.Context, f.GerbilId, f.LitterId, f.ContactId, f.EntityName, f.Url,
|
||||
f.ClientTimestamp, f.UserAgent, f.CreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace GerbilManagerWebAPI.Endpoints
|
||||
|
||||
private static LitterDto ToDto(Litter l) => new(
|
||||
l.Id, l.Name, l.Date, l.TotalBorn, l.DeathsWithin8Weeks,
|
||||
l.FatherId, l.MotherId, l.ExpectedGoHomeDate, l.Notes);
|
||||
l.FatherId, l.MotherId, l.ExpectedGoHomeDate, l.Notes, l.Provenance);
|
||||
}
|
||||
|
||||
/// <summary>400 body for a father×mother gender mismatch; frontend localises by Code.</summary>
|
||||
|
||||
@@ -103,6 +103,8 @@ namespace GerbilManagerWebAPI.Import
|
||||
existingContact.Notes = c.Notes;
|
||||
existingContact.IsBreeder = c.IsBreeder;
|
||||
existingContact.IsReceiver = c.IsReceiver;
|
||||
existingContact.NameSuffix = c.NameSuffix;
|
||||
existingContact.Provenance = c.Provenance;
|
||||
contactsUpdated++;
|
||||
}
|
||||
else if (addedContactIds.Add(c.Id))
|
||||
@@ -130,7 +132,8 @@ namespace GerbilManagerWebAPI.Import
|
||||
Notes = l.Notes,
|
||||
PairingCode = l.PairingCode,
|
||||
ExternalRef = l.ExternalRef,
|
||||
LitterLetter = l.LitterLetter
|
||||
LitterLetter = l.LitterLetter,
|
||||
Provenance = l.Provenance
|
||||
});
|
||||
}
|
||||
_db.Litters.AddRange(littersToInsert);
|
||||
|
||||
1542
GerbilManagerWebAPI/Migrations/20260622135306_AddFeedbackContactId.Designer.cs
generated
Normal file
1542
GerbilManagerWebAPI/Migrations/20260622135306_AddFeedbackContactId.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GerbilManagerWebAPI.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddFeedbackContactId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "ContactId",
|
||||
table: "Feedback",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ContactId",
|
||||
table: "Feedback");
|
||||
}
|
||||
}
|
||||
}
|
||||
1548
GerbilManagerWebAPI/Migrations/20260622135338_AddContactLitterProvenance.Designer.cs
generated
Normal file
1548
GerbilManagerWebAPI/Migrations/20260622135338_AddContactLitterProvenance.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GerbilManagerWebAPI.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddContactLitterProvenance : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Provenance",
|
||||
table: "Litters",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Provenance",
|
||||
table: "Contacts",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Provenance",
|
||||
table: "Litters");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Provenance",
|
||||
table: "Contacts");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -741,6 +741,9 @@ namespace GerbilManagerWebAPI.Migrations
|
||||
b.Property<string>("Phone")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Provenance")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Contacts");
|
||||
@@ -802,6 +805,9 @@ namespace GerbilManagerWebAPI.Migrations
|
||||
b.Property<DateTimeOffset?>("ClientTimestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("ContactId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Context")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
@@ -1046,6 +1052,9 @@ namespace GerbilManagerWebAPI.Migrations
|
||||
b.Property<string>("PairingCode")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Provenance")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("TotalBorn")
|
||||
.HasColumnType("integer");
|
||||
|
||||
|
||||
@@ -24,5 +24,11 @@ namespace GerbilManagerWebAPI.Models
|
||||
/// „von den Wüstenwinden“. Für Tiere fremder Züchter pflegbar.
|
||||
/// </summary>
|
||||
public string? NameSuffix { get; set; }
|
||||
|
||||
/// <summary>Data-provenance / traceability for the import: a JSON object describing
|
||||
/// WHICH source information produced this contact (sourceFiles, mergedRecordCount,
|
||||
/// notes). Written by the Python merge_and_resolve step and surfaced read-only
|
||||
/// ("Datenherkunft"). Null = manually-added contact / no import data.</summary>
|
||||
public string? Provenance { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace GerbilManagerWebAPI.Models
|
||||
/// <summary>Loose reference (no FK) to the litter the report is about, if any.</summary>
|
||||
public Guid? LitterId { get; set; }
|
||||
|
||||
/// <summary>Loose reference (no FK) to the contact the report is about, if any.</summary>
|
||||
public Guid? ContactId { get; set; }
|
||||
|
||||
/// <summary>Captured name of the referenced animal/litter (survives an ingest wipe).</summary>
|
||||
public string? EntityName { get; set; }
|
||||
|
||||
|
||||
@@ -38,5 +38,12 @@ namespace GerbilManagerWebAPI.Models
|
||||
/// <summary>FEAT-NAMEGEN: Wurfbuchstabe (A, B, C … AA, AB …) — alle Welpen dieses
|
||||
/// Wurfs erhalten Namen mit diesem Anfangsbuchstaben (gängige Zuchtkonvention).</summary>
|
||||
public string? LitterLetter { get; set; }
|
||||
|
||||
/// <summary>Data-provenance / traceability for the import: a JSON object describing
|
||||
/// WHICH source information produced this litter (sourceFiles, mergedRecordCount,
|
||||
/// fromWurfchronik, notes — e.g. "aus Wurfchronik", "aus Stammbaum-Diagramm rekonstruiert").
|
||||
/// Written by the Python merge_and_resolve step and surfaced read-only ("Datenherkunft").
|
||||
/// Null = manually-added litter / no import data.</summary>
|
||||
public string? Provenance { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user