FEAT-14a: Charakterbogen on Gerbil (traits + note) + sale-ad request extension
- Gerbil.CharacterTraits (List<string>, JSON text column, opaque labels) + CharacterNote (string?). On GerbilDto + Input; round-trips on GET/POST/PUT. Additive migration. - SaleAdAnimal gains Traits (German labels) + CharacterNote; prompt builder emits "Charakter: …" + "Charakter-Notiz: …" lines for the AI Verkaufstext. - Tests: traits/note round-trip (POST->GET via SQLite host), empty-default, prompt inclusion.
This commit is contained in:
@@ -49,6 +49,19 @@ public class ApplicationContext : DbContext
|
||||
e.Property(g => g.Gender).HasConversion<string>();
|
||||
e.Property(g => g.Status).HasConversion<string>();
|
||||
|
||||
// FEAT-14: character traits stored as a JSON text column (works on both
|
||||
// Npgsql and the SQLite test host; opaque labels, no backend vocabulary).
|
||||
var traitsConverter = new Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<List<string>, string>(
|
||||
v => System.Text.Json.JsonSerializer.Serialize(v, (System.Text.Json.JsonSerializerOptions?)null),
|
||||
v => string.IsNullOrEmpty(v)
|
||||
? new List<string>()
|
||||
: System.Text.Json.JsonSerializer.Deserialize<List<string>>(v, (System.Text.Json.JsonSerializerOptions?)null) ?? new List<string>());
|
||||
var traitsComparer = new Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer<List<string>>(
|
||||
(a, b) => (a ?? new List<string>()).SequenceEqual(b ?? new List<string>()),
|
||||
v => v == null ? 0 : v.Aggregate(0, (h, s) => HashCode.Combine(h, s.GetHashCode())),
|
||||
v => v.ToList());
|
||||
e.Property(g => g.CharacterTraits).HasConversion(traitsConverter, traitsComparer);
|
||||
|
||||
e.HasOne(g => g.Litter).WithMany()
|
||||
.HasForeignKey(g => g.LitterId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasOne(g => g.OriginContact).WithMany()
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace GerbilManagerWebAPI.Dtos
|
||||
string? Notes,
|
||||
string? ImportSource,
|
||||
string? ExternalRef,
|
||||
string? OriginBreeder);
|
||||
string? OriginBreeder,
|
||||
List<string> CharacterTraits,
|
||||
string? CharacterNote);
|
||||
|
||||
public record LitterDto(
|
||||
Guid Id,
|
||||
@@ -71,7 +73,9 @@ namespace GerbilManagerWebAPI.Dtos
|
||||
string? Notes,
|
||||
string? ImportSource,
|
||||
string? ExternalRef,
|
||||
string? OriginBreeder);
|
||||
string? OriginBreeder,
|
||||
List<string>? CharacterTraits,
|
||||
string? CharacterNote);
|
||||
|
||||
public record LitterInput(
|
||||
string Name,
|
||||
|
||||
@@ -95,11 +95,14 @@ namespace GerbilManagerWebAPI.Endpoints
|
||||
g.ImportSource = i.ImportSource;
|
||||
g.ExternalRef = i.ExternalRef;
|
||||
g.OriginBreeder = i.OriginBreeder;
|
||||
g.CharacterTraits = i.CharacterTraits ?? new List<string>();
|
||||
g.CharacterNote = i.CharacterNote;
|
||||
}
|
||||
|
||||
internal static GerbilDto ToDto(Gerbil g) => new(
|
||||
g.Id, g.Name, g.Gender, g.Status, g.LitterId, g.OriginContactId, g.ReceiverContactId,
|
||||
g.EnclosureId, g.ColorVarietyId, g.DateOfBirth, g.DateOfDeath, g.CauseOfDeath,
|
||||
g.GoHomeDate, g.Genotype, g.Notes, g.ImportSource, g.ExternalRef, g.OriginBreeder);
|
||||
g.GoHomeDate, g.Genotype, g.Notes, g.ImportSource, g.ExternalRef, g.OriginBreeder,
|
||||
g.CharacterTraits, g.CharacterNote);
|
||||
}
|
||||
}
|
||||
|
||||
1043
GerbilManagerWebAPI/Migrations/20260606070929_AddCharacterFields.Designer.cs
generated
Normal file
1043
GerbilManagerWebAPI/Migrations/20260606070929_AddCharacterFields.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GerbilManagerWebAPI.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCharacterFields : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CharacterNote",
|
||||
table: "Gerbils",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CharacterTraits",
|
||||
table: "Gerbils",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CharacterNote",
|
||||
table: "Gerbils");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CharacterTraits",
|
||||
table: "Gerbils");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,6 +661,13 @@ namespace GerbilManagerWebAPI.Migrations
|
||||
b.Property<string>("CauseOfDeath")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CharacterNote")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CharacterTraits")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("ColorVarietyId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
|
||||
@@ -60,6 +60,13 @@ namespace GerbilManagerWebAPI.Models
|
||||
/// Kept in sync automatically on save (see ApplicationContext.SaveChanges). Gridify-filterable
|
||||
/// so "clan kleine chaoten" matches "Clan-Kleine-Chaoten" (client strips separators too).</summary>
|
||||
public string? NameSearch { get; set; }
|
||||
|
||||
/// <summary>FEAT-14: Charakterbogen trait labels (opaque to the backend — the
|
||||
/// {key,label} vocabulary lives frontend-side). Stored as a JSON text column.</summary>
|
||||
public List<string> CharacterTraits { get; set; } = new();
|
||||
|
||||
/// <summary>FEAT-14: free-text character note; feeds the AI Verkaufstext.</summary>
|
||||
public string? CharacterNote { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Shared normalisation for the separator-insensitive name search.</summary>
|
||||
|
||||
@@ -9,7 +9,11 @@ namespace GerbilManagerWebAPI.SaleAd
|
||||
string? Farbschlag,
|
||||
/// <summary>ISO "YYYY-MM-DD" (frontend sends the DTO string verbatim).</summary>
|
||||
string? DateOfBirth,
|
||||
string? Notes);
|
||||
string? Notes,
|
||||
/// <summary>FEAT-14: Charakterbogen trait LABELS (German, human-readable) for the prompt.</summary>
|
||||
List<string>? Traits = null,
|
||||
/// <summary>FEAT-14: free-text character note for the prompt.</summary>
|
||||
string? CharacterNote = null);
|
||||
|
||||
public sealed record SaleAdRequest(
|
||||
List<SaleAdAnimal> Animals,
|
||||
|
||||
@@ -87,6 +87,10 @@ namespace GerbilManagerWebAPI.SaleAd
|
||||
sb.Append($" | geboren am {FormatGermanDate(animal.DateOfBirth)}");
|
||||
if (!string.IsNullOrWhiteSpace(animal.Notes))
|
||||
sb.Append($" | Notizen: {animal.Notes}");
|
||||
if (animal.Traits is { Count: > 0 })
|
||||
sb.Append($" | Charakter: {string.Join(", ", animal.Traits)}");
|
||||
if (!string.IsNullOrWhiteSpace(animal.CharacterNote))
|
||||
sb.Append($" | Charakter-Notiz: {animal.CharacterNote}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(request.Hints))
|
||||
|
||||
Reference in New Issue
Block a user