GerbilStatus: Active→Breeding, add Pet; Deceased/GivenAway are now DERIVED. GerbilStatusService.Derive(): central precedence: DateOfDeath→Deceased (1), ReceiverContactId→GivenAway (2), age>7y→Deceased presumed (3), user choice (4). All write paths (gerbil CRUD, contracts, importers) call GerbilStatusService.Apply(). Startup sweep flips >7y gerbils to Deceased at next app restart. Migration StatusModel: Active→Breeding rename + backfill derived statuses. 10 new tests; 200/200 green; has-pending=No. Enum string values: Breeding (was Active), Pet (new), Deceased, GivenAway, ForSale. FE contract: status field values updated (see Done-Report).
54 lines
2.5 KiB
C#
54 lines
2.5 KiB
C#
using GerbilManagerWebAPI.Models;
|
|
|
|
namespace GerbilManagerWebAPI.Services
|
|
{
|
|
/// <summary>
|
|
/// Central status-derivation logic. Every write path (create/update gerbil, SaleContract
|
|
/// Abgabe, import, startup sweep) calls Apply() after setting the other fields so the
|
|
/// derived statuses (Deceased, GivenAway) are always consistent.
|
|
///
|
|
/// Precedence (highest wins):
|
|
/// 1) DateOfDeath set → Deceased (explicit, always)
|
|
/// 2) Abgabe (ReceiverContactId set) → GivenAway
|
|
/// 3) Age > MaxAgeYears without a death date or Abgabe → Deceased (presumed)
|
|
/// 4) User-supplied {Breeding, Pet, ForSale}; defaults Breeding if invalid
|
|
///
|
|
/// Age-based death is time-dependent. The stored column is kept up-to-date by:
|
|
/// a) Apply() on every write (catches the animal at write time)
|
|
/// b) A startup sweep in Program.cs (catches animals that silently crossed the threshold)
|
|
/// Gridify filters on the stored value, so status==Breeding never surfaces >7y animals.
|
|
/// </summary>
|
|
public static class GerbilStatusService
|
|
{
|
|
public const int MaxAgeYears = 7;
|
|
|
|
/// <summary>Derives and sets g.Status using the gerbil's current field values.
|
|
/// Must be called AFTER all other fields (DateOfDeath, ReceiverContactId, DateOfBirth)
|
|
/// have been applied. today = DateOnly.FromDateTime(DateTime.UtcNow).</summary>
|
|
public static void Apply(Gerbil g, DateOnly today)
|
|
{
|
|
g.Status = Derive(g.Status, g.DateOfBirth, g.DateOfDeath,
|
|
isAbgegeben: g.ReceiverContactId is not null, today);
|
|
}
|
|
|
|
/// <summary>Pure derivation — useful for tests and the migration backfill.</summary>
|
|
public static GerbilStatus Derive(
|
|
GerbilStatus requested,
|
|
DateOnly? dateOfBirth,
|
|
DateOnly? dateOfDeath,
|
|
bool isAbgegeben,
|
|
DateOnly today)
|
|
{
|
|
if (dateOfDeath is not null) return GerbilStatus.Deceased;
|
|
if (isAbgegeben) return GerbilStatus.GivenAway;
|
|
if (dateOfBirth is not null && IsOlderThan(dateOfBirth.Value, MaxAgeYears, today))
|
|
return GerbilStatus.Deceased;
|
|
return requested is GerbilStatus.Breeding or GerbilStatus.Pet or GerbilStatus.ForSale
|
|
? requested : GerbilStatus.Breeding;
|
|
}
|
|
|
|
private static bool IsOlderThan(DateOnly dob, int years, DateOnly today) =>
|
|
today >= dob.AddYears(years);
|
|
}
|
|
}
|