feat: T4b/T4c wave 9 - phenology reports, autopilot A4-A10, honey ledger, Telegram delivery
Backend Image / build-and-push (push) Successful in 4m26s
Frontend Image / build-and-push (push) Successful in 7m19s
Manifests / validate (push) Successful in 17s

- Phenology reports API with corrections and revokes; recompute per
  apiary via PhenologyRecomputeRequested, allowBackward for corrections;
  season changes are announced in the morning digest (changes since the
  previous digest, each once)
- Season autopilot A4-A10 with per-apiary sync (Apiary before Colony
  locks) and WinterCheckRecorded
- Honey batch ledger at runtime: initial amount, BOTTLING bookings,
  manual movements, one gram rounding, untracked bottlings never book
- Telegram delivery: planning, Berlin delivery times incl. DST, quiet
  hours, permission re-check and stale skip at send time, rate limits,
  cleanup of old deliveries
- ARCHITECTURE.md: assumptions 47-50 and updated sections 11.x

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 21:13:23 +02:00
1 parent 1971b2d9c4
commit 16cc557462
74 files changed
+9634 -340

No files matched your search

@@ -0,0 +1,54 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsEnum,
IsIn,
IsNumber,
IsOptional,
IsString,
Max,
MaxLength,
Min,
NotEquals,
} from 'class-validator';
import { WithdrawalReason } from '../../../generated/prisma';
/** The limit of one booking, in kg: far above any batch, below float noise. */
export const MAX_MOVEMENT_KG = 100000;
export const MOVEMENT_TYPES = ['WITHDRAWAL', 'CORRECTION'] as const;
export type ManualMovementType = (typeof MOVEMENT_TYPES)[number];
/**
* A manual booking in the movement book of a honey batch. Bottlings book
* themselves; this is for loose goods, samples, breakage (WITHDRAWAL, minus)
* and for weighing again (CORRECTION, plus or minus).
*/
export class CreateHoneyBatchMovementDto {
@ApiProperty({ enum: MOVEMENT_TYPES })
@IsIn(MOVEMENT_TYPES)
type!: ManualMovementType;
@ApiProperty({
description:
'Änderung der Restmenge in kg mit Vorzeichen, nicht 0 (Entnahme: negativ)',
})
@IsNumber({ allowNaN: false, allowInfinity: false })
@NotEquals(0)
@Min(-MAX_MOVEMENT_KG)
@Max(MAX_MOVEMENT_KG)
deltaKg!: number;
@ApiPropertyOptional({
enum: WithdrawalReason,
description: 'Pflicht bei einer Entnahme',
})
@IsOptional()
@IsEnum(WithdrawalReason)
reason?: WithdrawalReason;
@ApiPropertyOptional()
@IsOptional()
@IsString()
@MaxLength(500)
notes?: string;
}