feat: T4 waves 1-2 - rule configs, schema, suggestions core, season, rule forms

- Rule configs with typed parsers and registry; Berlin day helpers
- Schema: T4 enums, Suggestion table, feed vocabulary (feedStatus as
  ColonyCondition with legacy text mapping), split queen source,
  varroa sample size; migration harness for data migrations
- SuggestionsService: propose/refresh/resolve/obsolete/expire,
  accept/dismiss/reopen incl. bulk, lock order, concurrency e2e
- SeasonService: phenology-based segments, inspection/varroa due dates,
  queen age; TimeEventsStep with JobPartialFailure
- Frontend: per-rule settings forms with visible value sources

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 13:42:32 +02:00
1 parent 7097fcd97c
commit d99a697739
116 files changed
+11239 -801

No files matched your search

@@ -3,7 +3,11 @@ import { JobRun, Prisma } from '../../generated/prisma';
import { JOB_NAMES } from '../jobs/job-names';
import { NotificationsService } from '../notifications/notifications.service';
import { PrismaService } from '../prisma/prisma.service';
import { JOB_ABORTED_ERROR, JobRunService } from './job-run.service';
import {
JOB_ABORTED_ERROR,
JobPartialFailure,
JobRunService,
} from './job-run.service';
type RawCall = [readonly string[], ...unknown[]];
@@ -125,6 +129,26 @@ describe('JobRunService', () => {
expect(JSON.stringify(errorSpy.mock.calls)).not.toContain('Mustermann');
});
it('stores the counters of a partial failure with the FAILED run', async () => {
const run = await service.run('daily-evaluation', () =>
Promise.reject(
new JobPartialFailure({ WithdrawalEnded: 2, TaskDue: 1 }, [
'time-events',
]),
),
);
expect(run.status).toBe('FAILED');
const [finish] = prisma.$queryRaw.mock.calls;
expect(finish.slice(1)).toEqual([
'FAILED',
'{"WithdrawalEnded":2,"TaskDue":1}',
'Daily evaluation failed for: time-events',
'run-1',
]);
expect(notifications.recordForPermission).toHaveBeenCalledTimes(1);
});
// finishedAt of every run - finished, failed or aborted - comes from
// the database clock, like the comparisons of cleanup.
it.each([