fix: mark a colony QUEENLESS when its queen is lost, dies, or is replaced

A very common real-world event - a queen dying over winter, absconding,
being superseded, sold, or replaced - had no side effect on the colony
record at all. Colony.currentQueenId kept pointing at a queen who was
no longer really there, colony.status stayed whatever it was, and the
QUEENLESS dashboard alert could never fire from this path.

QueensService.update() now detects a transition into
LOST/DECEASED/REPLACED/SOLD and, inside the same transaction, clears
currentQueenId on whichever colony currently points at that queen,
flips its status to QUEENLESS (unless the colony is already in a
terminal lifecycle status like DISSOLVED, which is left alone), and
closes the open QueenColonyHistory row - mirroring the bookkeeping
assignColony() already does when a queen changes colonies.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-07 13:30:18 +02:00
1 parent 32f11e5b39
commit cc46fa4d9e
2 files changed
+109 -5

No files matched your search

+44 -3
View File
@@ -40,6 +40,19 @@ export interface QueenLineage {
const MAX_LINEAGE_DEPTH = 8;
// Once a queen reaches one of these statuses she is no longer really "in"
// whatever colony currently points at her - a colony's currentQueenId and
// status would otherwise silently go stale (still showing a dead/lost
// queen as current, never flagged QUEENLESS) until someone happens to
// notice and fix it by hand.
const QUEEN_NO_LONGER_AVAILABLE_STATUSES: QueenStatus[] = [
'LOST',
'DECEASED',
'REPLACED',
'SOLD',
];
const TERMINAL_COLONY_STATUSES = ['DISSOLVED', 'SOLD', 'DECEASED', 'MERGED'];
@Injectable()
export class QueensService {
constructor(
@@ -92,9 +105,37 @@ export class QueensService {
}
await this.assertMotherQueenExists(dto.motherQueenId);
}
const queen = await this.prisma.queen.update({
where: { id },
data: { ...dto },
const becameUnavailable =
dto.status !== undefined &&
dto.status !== before.status &&
QUEEN_NO_LONGER_AVAILABLE_STATUSES.includes(dto.status);
const queen = await this.prisma.$transaction(async (tx) => {
const updated = await tx.queen.update({
where: { id },
data: { ...dto },
});
if (becameUnavailable) {
const colony = await tx.colony.findUnique({
where: { currentQueenId: id },
});
if (colony) {
await tx.colony.update({
where: { id: colony.id },
data: {
currentQueenId: null,
status: TERMINAL_COLONY_STATUSES.includes(colony.status)
? undefined
: 'QUEENLESS',
},
});
await tx.queenColonyHistory.updateMany({
where: { queenId: id, colonyId: colony.id, endDate: null },
data: { endDate: new Date() },
});
}
}
return updated;
});
await this.auditLog.record({
entityType: 'Queen',