feat: T6 wave 3 – treatment books from lot, label print lock, restricted zone, authority notices
- Treatments book their dose from a medicine lot (delta booking on edit, bulk all-or-nothing, idempotent); Bestandsbuch columns from the lot, FEHLT exactly where treatmentLogGaps reports a gap; Bestandsbuch export needs veterinary-medicines:read. - Label printing: 409 LABEL_PARTICULARS_MISSING on gaps, proof print with watermark, PRINT audit before the PDF, check endpoints; digit rules only from the seeded ComplianceSettings row (fail closed). - Restricted zone (Berlin day): create, update, move, merge and CSV import blocked inside an active restriction; override with reason and compliance:manage, audited, never stored on the colony; split, harvest and dissolve stay free; restriction details only with compliance:read. - Authority notices: settings, herd snapshot, registration/herd change/TSK notices with one open draft per kind under the settings lock. - Label check refinements (geometric reading order, number|unit pairs). - ARCHITECTURE.md §12.6–12.8, Annahmen 71–77; spec §5.6 Berlin day. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
c18860c75c
commit
edc193f128
73 files changed
+9470
-171
No files matched your search
@@ -0,0 +1,77 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
ArrayMaxSize,
|
||||
ArrayMinSize,
|
||||
ArrayUnique,
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Matches,
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export const MAX_RESTRICTION_APIARIES = 50;
|
||||
const MAX_ID_LENGTH = 64;
|
||||
const MAX_TEXT_LENGTH = 200;
|
||||
const MAX_NOTE_LENGTH = 2000;
|
||||
const ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
||||
const DAY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
||||
const DAY_MESSAGE = 'muss ein Datum im Format JJJJ-MM-TT sein';
|
||||
|
||||
/** A restricted zone declared by the authority, for one or several apiaries. */
|
||||
export class CreateApiaryRestrictionDto {
|
||||
@ApiProperty({
|
||||
type: [String],
|
||||
description: 'Ein Datensatz je Stand, 1 bis 50 Stände',
|
||||
})
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ArrayMaxSize(MAX_RESTRICTION_APIARIES)
|
||||
@ArrayUnique()
|
||||
@IsString({ each: true })
|
||||
@Matches(ID_PATTERN, { each: true })
|
||||
@MaxLength(MAX_ID_LENGTH, { each: true })
|
||||
apiaryIds!: string[];
|
||||
|
||||
@ApiProperty({ description: 'Seuche, z. B. Amerikanische Faulbrut' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Matches(/\S/, { message: 'disease darf nicht nur aus Leerzeichen bestehen' })
|
||||
@MaxLength(MAX_TEXT_LENGTH)
|
||||
disease!: string;
|
||||
|
||||
@ApiPropertyOptional({ nullable: true, description: 'Anordnende Behörde' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(MAX_TEXT_LENGTH)
|
||||
authority?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ nullable: true, description: 'Aktenzeichen' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(MAX_TEXT_LENGTH)
|
||||
reference?: string | null;
|
||||
|
||||
@ApiProperty({ example: '2026-10-01', description: 'Erster Tag der Sperre' })
|
||||
@Matches(DAY_PATTERN, { message: `startsOn ${DAY_MESSAGE}` })
|
||||
@IsDateString({ strict: true })
|
||||
startsOn!: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
nullable: true,
|
||||
example: '2026-12-01',
|
||||
description: 'Letzter Tag der Sperre; leer = bis auf Weiteres',
|
||||
})
|
||||
@IsOptional()
|
||||
@Matches(DAY_PATTERN, { message: `endsOn ${DAY_MESSAGE}` })
|
||||
@IsDateString({ strict: true })
|
||||
endsOn?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(MAX_NOTE_LENGTH)
|
||||
notes?: string | null;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsOptional } from 'class-validator';
|
||||
import { booleanQuery } from '../../veterinary-medicines/dto/medicine-list-query.dto';
|
||||
|
||||
export class RestrictionListQuery {
|
||||
@ApiPropertyOptional({
|
||||
description: 'true: nur heute geltende Sperren; false: nur beendete',
|
||||
})
|
||||
@IsOptional()
|
||||
@Transform(booleanQuery)
|
||||
@IsBoolean()
|
||||
active?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { OmitType, PartialType } from '@nestjs/swagger';
|
||||
import { CreateApiaryRestrictionDto } from './create-apiary-restriction.dto';
|
||||
|
||||
/**
|
||||
* The apiary of a restriction is fixed: a restriction for another stand is a
|
||||
* new restriction. Omitted stays, null clears a nullable field (endsOn ends
|
||||
* the restriction as open again), null on disease or startsOn is a 400.
|
||||
*/
|
||||
export class UpdateApiaryRestrictionDto extends PartialType(
|
||||
OmitType(CreateApiaryRestrictionDto, ['apiaryIds'] as const),
|
||||
{ skipNullProperties: false },
|
||||
) {}
|
||||
Reference in new issue
Block a user