feat: platform backbone and field capture complete (checkpoint)
Platform backbone (plan 2026-10-13-plattform-rueckgrat), all tasks reviewed: - webhooks with delivery table, retries and stable delivery id - calendar handlers; a finished task removes its Outlook event - tasks, automations, inspections, varroa, treatments, feedings, harvests, colonies, wintering, stock and bottling record domain events in the same transaction; legacy post-commit calls removed - row locks through one helper (lockRowForUpdate, lockRowsForShare, lockRowsForUpdate) - job framework with JobRun, cli exit codes, cleanup, task templates, daily evaluation, hourly app-backup and the rekey job - system API and page "Verwaltung > System" with retry - @nestjs/schedule removed, guard tests against in-process schedulers Field capture T3 (plan 2026-10-13-t3-erfassung-am-stand), all tasks reviewed: - inspection photo offline without metadata - bulk capture for treatments, feedings and varroa (backend + frontend) - offline stand mode with service worker, offline pages and packs - offline session, cold start and logout on shared devices Docs: ARCHITECTURE.md assumptions and sections updated, TASKS.md. Gates: backend lint, typecheck, 1770 unit, 420 e2e, build, no schema drift; frontend lint, typecheck, 913 tests, build. Not rolled out. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
c01a38835a
commit
cdd30cf3ee
288 files changed
+39165
-2853
No files matched your search
@@ -0,0 +1,84 @@
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
ArrayMaxSize,
|
||||
ArrayMinSize,
|
||||
ArrayUnique,
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { BULK_MAX_COLONIES } from './bulk-limits';
|
||||
|
||||
const DUPLICATE_COLONY_MESSAGE = '$property enthält ein Volk doppelt';
|
||||
|
||||
// The size rule every bulk list shares: at least one colony, at most
|
||||
// BULK_MAX_COLONIES.
|
||||
function bulkListSize(): PropertyDecorator[] {
|
||||
return [
|
||||
IsArray({ message: '$property muss eine Liste sein' }),
|
||||
ArrayMinSize(1, { message: '$property braucht mindestens ein Volk' }),
|
||||
ArrayMaxSize(BULK_MAX_COLONIES, {
|
||||
message: `$property darf höchstens ${BULK_MAX_COLONIES} Völker enthalten`,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
// The rule for one colony id - in colonyIds (each) and in every entry - so an
|
||||
// empty id is invalid input on both paths, never an "unknown colony".
|
||||
function colonyIdChecks(each: boolean): PropertyDecorator[] {
|
||||
return [
|
||||
IsString({ each, message: '$property enthält eine ungültige Volk-ID' }),
|
||||
IsNotEmpty({ each, message: '$property enthält eine leere Volk-ID' }),
|
||||
];
|
||||
}
|
||||
|
||||
/** `colonyIds` of a bulk entry with shared values: distinct, non-empty ids. */
|
||||
export function IsBulkColonyIds(): PropertyDecorator {
|
||||
return applyDecorators(
|
||||
ApiProperty({
|
||||
type: [String],
|
||||
minItems: 1,
|
||||
maxItems: BULK_MAX_COLONIES,
|
||||
uniqueItems: true,
|
||||
description: `Die Völker, für die derselbe Eintrag gilt (1 bis ${BULK_MAX_COLONIES}, jedes nur einmal)`,
|
||||
}),
|
||||
...bulkListSize(),
|
||||
ArrayUnique({ message: DUPLICATE_COLONY_MESSAGE }),
|
||||
...colonyIdChecks(true),
|
||||
);
|
||||
}
|
||||
|
||||
/** `colonyId` of one entry in `entries`: a non-empty id. */
|
||||
export function IsBulkEntryColonyId(): PropertyDecorator {
|
||||
return applyDecorators(
|
||||
ApiProperty({ description: 'Das Volk dieses Eintrags' }),
|
||||
...colonyIdChecks(false),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `entries` of a bulk entry with one value set per colony: each entry is
|
||||
* validated as its class, and each colony appears once.
|
||||
*/
|
||||
export function IsBulkEntries(
|
||||
entryType: new () => { colonyId: string },
|
||||
): PropertyDecorator {
|
||||
return applyDecorators(
|
||||
ApiProperty({
|
||||
type: [entryType],
|
||||
minItems: 1,
|
||||
maxItems: BULK_MAX_COLONIES,
|
||||
description: `Ein Eintrag je Volk (1 bis ${BULK_MAX_COLONIES}, jedes Volk nur einmal)`,
|
||||
}),
|
||||
...bulkListSize(),
|
||||
// class-validator skips null entries before calling the selector.
|
||||
ArrayUnique((entry: { colonyId?: unknown }) => entry.colonyId, {
|
||||
message: DUPLICATE_COLONY_MESSAGE,
|
||||
}),
|
||||
ValidateNested({ each: true }),
|
||||
Type(() => entryType),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user