feat: T4 wave 5 - colony end cleanup, withdrawal chain, next steps, inspection takeover UI

- Colony end releases the queen in the same transaction; open
  suggestions become obsolete and a CLEANUP suggestion lists open
  tasks and the queen; apply-cleanup validates evidence and blocks
  reactivated colonies
- Withdrawal chain: plausibility warnings on harvest and bottling,
  conflict notifications also when a treatment is entered or corrected
  after the harvest (naming bottled lots), withdrawal spans in the
  calendar and M365 sync, Berlin-day end events
- Next 3 steps per colony and the "Heute" endpoint with ranking and an
  explicit merge table by intent
- Inspection-triggered rule suggestions follow the colony's latest
  inspection (resolve when the finding is gone, refresh when it stays)
- Frontend: colony card takeover in the inspection form, feed status as
  condition, occupied frames 0-40; legacy offline entries keep syncing
- Notifications and preferences are deleted with their user

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-14 16:28:07 +02:00
1 parent 02eb16924e
commit ef33e5549b
122 files changed
+10707 -504

No files matched your search

+56 -1
View File
@@ -9,8 +9,9 @@ import { AuditLogService } from '../audit/audit-log.service';
import { WebhooksService } from '../webhooks/webhooks.service';
import { DomainEventsService } from '../domain-events/domain-events.service';
import { utcDay } from '../domain-events/domain-event-types';
import { assertTransactionClient } from '../prisma/db-client';
import { lockRowForUpdate, lockRowsForUpdate } from '../prisma/row-lock';
import { Queen, QueenStatus } from '../../generated/prisma';
import { Prisma, Queen, QueenStatus } from '../../generated/prisma';
import { CreateQueenDto } from './dto/create-queen.dto';
import { UpdateQueenDto } from './dto/update-queen.dto';
import { AssignColonyDto } from './dto/assign-colony.dto';
@@ -155,6 +156,60 @@ export class QueensService {
return queen;
}
/**
* Differs from update(): this is the status alone, on the caller's
* transaction, with the audit entry inside it. update() also lets go of
* the colony that holds a queen who becomes unavailable (and audits after
* its transaction); here that case is a 409, because the clean-up of an
* ended colony must never unlink a colony she was re-assigned to.
*
* Sets the status of a queen on the caller's transaction: locks the queen,
* reads it afterwards, audits the change. For a status that takes her out
* of use it refuses while a colony still holds her (409) - the colony
* side has to let go of her first (releaseQueenIn, assignColony); this
* method never touches a colony. The same status again changes nothing.
*/
async updateStatusIn(
tx: Prisma.TransactionClient,
id: string,
status: QueenStatus,
actingUserId: string,
): Promise<Queen> {
assertTransactionClient(tx, 'QueensService.updateStatusIn');
await lockRowForUpdate(tx, 'Queen', id);
const before = await tx.queen.findUnique({ where: { id } });
if (!before) {
throw new NotFoundException('Queen not found');
}
if (before.status === status) {
return before;
}
if (QUEEN_NO_LONGER_AVAILABLE_STATUSES.includes(status)) {
const holder = await tx.colony.findUnique({
where: { currentQueenId: id },
select: { id: true },
});
if (holder) {
throw new ConflictException(
'Die Königin sitzt noch in einem Volk - bitte zuerst dort lösen',
);
}
}
const updated = await tx.queen.update({ where: { id }, data: { status } });
await this.auditLog.record(
{
entityType: 'Queen',
entityId: id,
action: 'UPDATE',
userId: actingUserId,
beforeData: before,
afterData: updated,
},
tx,
);
return updated;
}
async assignColony(
id: string,
dto: AssignColonyDto,