fix: release a colony's queen when it is dissolved or merged away

Colony.currentQueenId is unique across colonies. dissolve() and merge()
set status to DISSOLVED/MERGED but never cleared currentQueenId, so a
colony that still had a queen when it was dissolved or merged kept
permanently "owning" her - any later attempt to assign that queen to a
different colony (e.g. requeening another colony with her) would fail
with a unique-constraint conflict, since the dead colony's stale
reference was still occupying that slot.

Both operations now clear currentQueenId and close the open
QueenColonyHistory row for that queen, mirroring the bookkeeping
assignColony() and the queen-status-transition fix already do
elsewhere. The merge survivor keeps its own queen; a merged-away
colony's queen is released, not auto-transferred, since which queen
"wins" a merge is a beekeeper decision.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-08 20:58:53 +02:00
1 parent cc46fa4d9e
commit 20981ec1ce
2 files changed
+129 -7

No files matched your search

@@ -172,10 +172,30 @@ export class ColoniesService {
throw new NotFoundException(`Colony ${mergedColonyId} not found`);
}
this.assertActive(merged);
// currentQueenId is unique across colonies - if it stayed set on
// this now-dead colony, any later attempt to assign her queen to a
// different colony would fail with a unique-constraint conflict.
// The survivor keeps its own queen; merged-away queens are simply
// released, not auto-transferred (which colony's queen "wins" a
// merge is a beekeeper decision, not one to guess here).
await tx.colony.update({
where: { id: mergedColonyId },
data: { status: 'MERGED', deactivatedAt: new Date() },
data: {
status: 'MERGED',
deactivatedAt: new Date(),
currentQueenId: null,
},
});
if (merged.currentQueenId) {
await tx.queenColonyHistory.updateMany({
where: {
queenId: merged.currentQueenId,
colonyId: mergedColonyId,
endDate: null,
},
data: { endDate: new Date() },
});
}
await tx.colonyLineage.create({
data: {
type: 'MERGE',
@@ -200,9 +220,26 @@ export class ColoniesService {
async dissolve(id: string, actingUserId: string): Promise<Colony> {
const before = await this.findById(id);
const colony = await this.prisma.colony.update({
where: { id },
data: { status: 'DISSOLVED', deactivatedAt: new Date() },
const colony = await this.prisma.$transaction(async (tx) => {
const updated = await tx.colony.update({
where: { id },
data: {
status: 'DISSOLVED',
deactivatedAt: new Date(),
currentQueenId: null,
},
});
if (before.currentQueenId) {
await tx.queenColonyHistory.updateMany({
where: {
queenId: before.currentQueenId,
colonyId: id,
endDate: null,
},
data: { endDate: new Date() },
});
}
return updated;
});
await this.auditLog.record({
entityType: 'Colony',