feat: T4 wave 3 - notifications per user, suggestions API, varroa bands, split/queen events

- Schema III: per-user notifications (broadcast fan-out migration,
  category, link, requiredPermissions filter), notification preferences
  and settings endpoints, calendar withdrawal switch, occupiedFrames
- Suggestions API: list/summary with permission visibility and paging,
  accept/dismiss/reopen incl. bulk, SuggestionView on all responses
- Varroa traffic light by season and method, young colonies, treatment
  effect window, sample-based percentage; shared withdrawal-window helper
  on Berlin calendar days; GET /varroa-measurements/thresholds
- ColonySplit and QueenAssigned domain events with calendar and webhook
  handlers
- Outbox claims compare against now() rounded to timestamp(3); fixes the
  flaky e2e claims of rows written with SQL now()

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 14:25:24 +02:00
1 parent d99a697739
commit f468dc5f75
87 files changed
+6022 -510

No files matched your search

+12 -13
View File
@@ -232,28 +232,27 @@ describe('Varroa measurements (e2e)', () => {
expect(measurements.some((entry) => entry.id === measurementId)).toBe(true);
});
describe('GET /varroa-measurements/threshold', () => {
const thresholdBody = (token: string) =>
describe('GET /varroa-measurements/thresholds', () => {
const thresholdsBody = (token: string, query = '') =>
request(app.getHttpServer())
.get('/api/v1/varroa-measurements/threshold')
.get(`/api/v1/varroa-measurements/thresholds${query}`)
.set('Authorization', `Bearer ${token}`);
it('returns the threshold shape and is not shadowed by :id', async () => {
const response = await thresholdBody(staffToken).expect(200);
it('returns the bands shape and is not shadowed by :id', async () => {
const response = await thresholdsBody(staffToken).expect(200);
const body = response.body as { thresholdMitesPerDay: number | null };
expect(Object.keys(body)).toEqual(['thresholdMitesPerDay']);
expect(
body.thresholdMitesPerDay === null ||
typeof body.thresholdMitesPerDay === 'number',
).toBe(true);
const body = response.body as { day: string; active: boolean };
expect(Object.keys(body).sort()).toEqual(
['active', 'bands', 'day', 'postTreatmentUntil'].sort(),
);
expect(typeof body.active).toBe('boolean');
});
// The shared rule is never written here (parallel e2e workers); the
// inactive / custom value cases are covered by varroa-threshold.spec.ts.
// inactive / custom value cases are covered by the unit specs.
it('rejects a role without any varroa permission', () =>
thresholdBody(noVarroaToken).expect(403));
thresholdsBody(noVarroaToken).expect(403));
});
describe('POST /varroa-measurements/bulk', () => {