feat: T4 wave 10 – bottling quality, phenology and honey chain UI, Telegram link/receive and ops

- Bottling quality: water content above the legal limit (strict >) answers
  409 CONFIRMATION_REQUIRED bound to the measured value; prefill for jar
  size, best-before (shelfLifeMonths, month-end clamping) and water content;
  impossible dates give 400; water-content checks in src/common/quality.
- Frontend phenology: "Entwicklung melden" sheet, IndexedDB v4 queue,
  season header on the apiary page, confirmed revoke for all apiaries.
- Frontend honey chain: "Vorschlag aus …" hints with explicit take-over,
  prefill status note, movement ledger with withdraw/correct dialogs.
- Telegram: link tokens (hashed, single use), private-chat receiver with
  lease and offset CAS, bounded poison-update skip, Retry-After, pending
  deliveries skipped on disconnect; system API for deliveries and retry;
  worker-only secret wiring and manifest policy tests.
- ARCHITECTURE.md §7, §9, §10, §11.11–11.13, Annahmen 51–55.

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 22:47:17 +02:00
1 parent 16cc557462
commit fab7f7e12f
91 files changed
+8463 -104

No files matched your search

@@ -1,6 +1,12 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
Max,
Min,
ArrayMaxSize,
ArrayUnique,
IsArray,
IsDateString,
IsIn,
IsInt,
IsNumber,
IsOptional,
@@ -8,6 +14,12 @@ import {
IsString,
} from 'class-validator';
export const BOTTLING_ACKNOWLEDGEABLE_WARNINGS = [
'WATER_CONTENT_ABOVE_LEGAL',
] as const;
export type AcknowledgeableWarning =
(typeof BOTTLING_ACKNOWLEDGEABLE_WARNINGS)[number];
export class CreateBottlingBatchDto {
@ApiProperty()
@IsString()
@@ -19,22 +31,30 @@ export class CreateBottlingBatchDto {
productId?: string;
@ApiProperty()
@IsDateString()
@IsDateString({ strict: true })
date!: string;
@ApiProperty({ description: 'Glasgröße in Gramm' })
@ApiPropertyOptional({
description:
'Glasgröße in Gramm. Fehlt sie, gilt die Packungsgröße des Artikels.',
})
@IsOptional()
@IsNumber()
@IsPositive()
jarSize!: number;
jarSize?: number;
@ApiProperty()
@IsInt()
@IsPositive()
jarCount!: number;
@ApiProperty()
@IsDateString()
bestBeforeDate!: string;
@ApiPropertyOptional({
description:
'MHD. Fehlt es, gilt Abfülldatum plus Haltbarkeit des Artikels (Monatsende abgeschnitten).',
})
@IsOptional()
@IsDateString({ strict: true })
bestBeforeDate?: string;
@ApiPropertyOptional({
description:
@@ -43,4 +63,27 @@ export class CreateBottlingBatchDto {
@IsOptional()
@IsString()
lotNumber?: string;
@ApiPropertyOptional({
enum: BOTTLING_ACKNOWLEDGEABLE_WARNINGS,
isArray: true,
description:
'Bestätigte Warnungen. Über der gesetzlichen Wassergehaltsgrenze nötig, sonst 409 CONFIRMATION_REQUIRED.',
})
@IsOptional()
@IsArray()
@ArrayMaxSize(1)
@ArrayUnique()
@IsIn(BOTTLING_ACKNOWLEDGEABLE_WARNINGS, { each: true })
acknowledgedWarnings?: AcknowledgeableWarning[];
@ApiPropertyOptional({
description:
'Der Wassergehalt (%), den die Bestätigung meint: der Wert aus der 409-Antwort. Weicht der geprüfte Wert ab, folgt eine neue 409.',
})
@IsOptional()
@IsNumber()
@Min(0)
@Max(100)
acknowledgedWaterContent?: number;
}