Merge branch 'worktree-agent-a9d8edbd439109a45'

This commit is contained in:
2026-06-22 22:53:33 +02:00
15 changed files with 2071 additions and 14 deletions

View File

@@ -49,7 +49,10 @@ namespace GerbilManagerWebAPI.Dtos
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);
public record EnclosureDto(
Guid Id, string Name, string? Notes,
string? Size, int? Capacity,
DateOnly? LastCleanedDate, int? CleaningCycleDays, DateOnly? NextCleaningDate);
public record ColorVarietyDto(Guid Id, string Name, string? CanonicalGenotype, int SortOrder);
@@ -101,7 +104,10 @@ namespace GerbilManagerWebAPI.Dtos
public record ContactInput(string Name, string? Email, string? Phone, string? Address, string? Notes, bool IsBreeder, bool IsReceiver, string? NameSuffix);
public record EnclosureInput(string Name, string? Notes);
public record EnclosureInput(
string Name, string? Notes,
string? Size, int? Capacity,
DateOnly? LastCleanedDate, int? CleaningCycleDays);
public record ColorVarietyInput(string Name, string? CanonicalGenotype, int? SortOrder);

View File

@@ -25,6 +25,7 @@ namespace GerbilManagerWebAPI.Endpoints
group.MapPost("/", async (EnclosureInput input, ApplicationContext db) =>
{
var e = new Enclosure { Id = Guid.NewGuid(), Name = input.Name, Notes = input.Notes };
Apply(e, input);
db.Enclosures.Add(e);
await db.SaveChangesAsync();
return TypedResults.Created($"/enclosures/{e.Id}", ToDto(e));
@@ -35,10 +36,21 @@ namespace GerbilManagerWebAPI.Endpoints
var e = await db.Enclosures.FirstOrDefaultAsync(x => x.Id == id);
if (e is null) return TypedResults.NotFound();
e.Name = input.Name; e.Notes = input.Notes;
Apply(e, input);
await db.SaveChangesAsync();
return TypedResults.NoContent();
});
// Reinigung dokumentieren: setzt LastCleanedDate auf heute (NextCleaningDate folgt aus dem Zyklus).
group.MapPost("/{id:guid}/mark-cleaned", async Task<Results<Ok<EnclosureDto>, NotFound>> (Guid id, ApplicationContext db) =>
{
var e = await db.Enclosures.FirstOrDefaultAsync(x => x.Id == id);
if (e is null) return TypedResults.NotFound();
e.LastCleanedDate = DateOnly.FromDateTime(DateTime.Today);
await db.SaveChangesAsync();
return TypedResults.Ok(ToDto(e));
});
// 409 if the enclosure still houses gerbils.
group.MapDelete("/{id:guid}", async Task<Results<NoContent, NotFound, Conflict<string>>> (Guid id, ApplicationContext db) =>
{
@@ -54,6 +66,16 @@ namespace GerbilManagerWebAPI.Endpoints
return app;
}
private static EnclosureDto ToDto(Enclosure e) => new(e.Id, e.Name, e.Notes);
private static void Apply(Enclosure e, EnclosureInput input)
{
e.Size = input.Size;
e.Capacity = input.Capacity;
e.LastCleanedDate = input.LastCleanedDate;
e.CleaningCycleDays = input.CleaningCycleDays;
}
private static EnclosureDto ToDto(Enclosure e) =>
new(e.Id, e.Name, e.Notes, e.Size, e.Capacity,
e.LastCleanedDate, e.CleaningCycleDays, e.NextCleaningDate);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GerbilManagerWebAPI.Migrations
{
/// <inheritdoc />
public partial class AddEnclosureCleaning : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Capacity",
table: "Enclosures",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "CleaningCycleDays",
table: "Enclosures",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<DateOnly>(
name: "LastCleanedDate",
table: "Enclosures",
type: "date",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Size",
table: "Enclosures",
type: "text",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Capacity",
table: "Enclosures");
migrationBuilder.DropColumn(
name: "CleaningCycleDays",
table: "Enclosures");
migrationBuilder.DropColumn(
name: "LastCleanedDate",
table: "Enclosures");
migrationBuilder.DropColumn(
name: "Size",
table: "Enclosures");
}
}
}

View File

@@ -755,6 +755,15 @@ namespace GerbilManagerWebAPI.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int?>("Capacity")
.HasColumnType("integer");
b.Property<int?>("CleaningCycleDays")
.HasColumnType("integer");
b.Property<DateOnly?>("LastCleanedDate")
.HasColumnType("date");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
@@ -762,6 +771,9 @@ namespace GerbilManagerWebAPI.Migrations
b.Property<string>("Notes")
.HasColumnType("text");
b.Property<string>("Size")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Enclosures");

View File

@@ -10,6 +10,22 @@ namespace GerbilManagerWebAPI.Models
public required string Name { get; set; }
public string? Notes { get; set; }
// Reinigungszyklus (aus RennmausPro becken_tb: _SIZE/_MENGE/_CLEANED/_CYCLUS).
/// <summary>Maße als Freitext (z. B. "120×50 cm").</summary>
public string? Size { get; set; }
/// <summary>Empfohlene/maximale Tieranzahl.</summary>
public int? Capacity { get; set; }
/// <summary>Datum der letzten Reinigung.</summary>
public DateOnly? LastCleanedDate { get; set; }
/// <summary>Reinigungsintervall in Tagen.</summary>
public int? CleaningCycleDays { get; set; }
/// <summary>Nächste fällige Reinigung = LastCleanedDate + CleaningCycleDays (berechnet, nicht persistiert).</summary>
public DateOnly? NextCleaningDate =>
LastCleanedDate is { } last && CleaningCycleDays is { } cycle and > 0
? last.AddDays(cycle)
: null;
public ICollection<Gerbil> Gerbils { get; } = new List<Gerbil>();
}
}