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:
1 parent
7097fcd97c
commit
d99a697739
116 files changed
+11239
-801
No files matched your search
@@ -133,6 +133,42 @@ describe('ImportService', () => {
|
||||
expect(createArg.data.code).toBe('V-100');
|
||||
});
|
||||
|
||||
it('maps CSV feed text "niedrig" to WEAK and keeps unknown text in the notes', async () => {
|
||||
prisma.apiary.findFirst.mockResolvedValue({ id: 'apiary-1' });
|
||||
prisma.colony.findUnique.mockResolvedValue(null);
|
||||
prisma.colony.create.mockResolvedValue({ id: 'colony-1' });
|
||||
|
||||
const report = await service.importCsv(
|
||||
'colonies',
|
||||
Buffer.from(
|
||||
[
|
||||
'code,apiaryName,colonyType,feedStatus,notes',
|
||||
'V-1,Teststand,PRODUCTION_COLONY,niedrig,',
|
||||
'V-2,Teststand,PRODUCTION_COLONY,halb voll,Hinweis',
|
||||
'V-3,Teststand,PRODUCTION_COLONY,CRITICAL,',
|
||||
].join('\n'),
|
||||
),
|
||||
'user-1',
|
||||
);
|
||||
|
||||
expect(report.created).toBe(3);
|
||||
const data = (
|
||||
prisma.colony.create.mock.calls as [
|
||||
[{ data: { feedStatus?: string; notes?: string } }],
|
||||
]
|
||||
).map(([call]) => call.data);
|
||||
expect(data[0]).toMatchObject({ feedStatus: 'WEAK' });
|
||||
expect(data[0].notes).toBeUndefined();
|
||||
expect(data[1].feedStatus).toBeUndefined();
|
||||
expect(data[1].notes).toBe('Hinweis\nFutter (alt): halb voll');
|
||||
expect(data[2]).toMatchObject({ feedStatus: 'CRITICAL' });
|
||||
expect(report.rows.map((row) => row.message)).toEqual([
|
||||
undefined,
|
||||
'Futterangabe „halb voll“ nicht erkannt, in die Notizen übernommen.',
|
||||
undefined,
|
||||
]);
|
||||
});
|
||||
|
||||
it('reports a validation error for an invalid colonyType', async () => {
|
||||
prisma.apiary.findFirst.mockResolvedValue({ id: 'apiary-1' });
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ import { AuditLogService } from '../audit/audit-log.service';
|
||||
import { CreateApiaryDto } from '../apiaries/dto/create-apiary.dto';
|
||||
import { CreateColonyDto } from '../colonies/dto/create-colony.dto';
|
||||
import { CreateQueenDto } from '../queens/dto/create-queen.dto';
|
||||
import {
|
||||
appendLegacyFeedNote,
|
||||
feedConditionFromInput,
|
||||
} from '../common/feed-vocabulary';
|
||||
import { parseCsv } from './csv';
|
||||
|
||||
const IMPORT_ENTITIES = ['apiaries', 'colonies', 'queens'] as const;
|
||||
@@ -166,6 +170,8 @@ export class ImportService {
|
||||
};
|
||||
}
|
||||
|
||||
// Old exports carry free text here; known words map to the enum.
|
||||
const feed = feedConditionFromInput(record.feedStatus);
|
||||
const plain = {
|
||||
code: emptyToUndefined(record.code),
|
||||
apiaryId: apiary.id,
|
||||
@@ -174,9 +180,13 @@ export class ImportService {
|
||||
originText: emptyToUndefined(record.originText),
|
||||
strength: parseNumberField(record.strength),
|
||||
broodStatus: emptyToUndefined(record.broodStatus),
|
||||
feedStatus: emptyToUndefined(record.feedStatus),
|
||||
feedStatus: feed.feedStatus ?? undefined,
|
||||
healthStatus: emptyToUndefined(record.healthStatus),
|
||||
notes: emptyToUndefined(record.notes),
|
||||
notes:
|
||||
appendLegacyFeedNote(
|
||||
emptyToUndefined(record.notes) ?? null,
|
||||
feed.notesSuffix,
|
||||
) ?? undefined,
|
||||
};
|
||||
const dto = plainToInstance(CreateColonyDto, plain);
|
||||
const errors = await validate(dto);
|
||||
@@ -199,7 +209,16 @@ export class ImportService {
|
||||
};
|
||||
}
|
||||
const colony = await this.prisma.colony.create({ data: { ...dto } });
|
||||
return { row: rowNumber, status: 'created', id: colony.id };
|
||||
return {
|
||||
row: rowNumber,
|
||||
status: 'created',
|
||||
id: colony.id,
|
||||
...(feed.notesSuffix
|
||||
? {
|
||||
message: `Futterangabe „${record.feedStatus}“ nicht erkannt, in die Notizen übernommen.`,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
private async importQueenRow(
|
||||
|
||||
Reference in new issue
Block a user