feat(backend): Lagerbehälter-Umlagerung für Honigchargen (Modul 32)

Neues HoneyBatchRelocation-Modell dokumentiert jede Umlagerung einer
Honigcharge zwischen Lagerbehältern (von/nach, Zeitpunkt, Benutzer,
Notiz). HoneyBatchesService.relocate() aktualisiert
HoneyBatch.storageContainerId transaktional zusammen mit dem
Historieneintrag; wirft NotFoundException bei unbekanntem Zielbehälter.

Neue Endpunkte: POST /honey-batches/:id/relocate,
GET /honey-batches/:id/relocations. honeyBatchInclude liefert jetzt
zusätzlich den aktuell zugeordneten storageContainer mit.

Unit- und E2E-Tests ergänzt (Mehrfachumlagerung, Historienreihenfolge,
404 bei unbekanntem Behälter). Migration auf Dev- und Test-Datenbank
angewendet.
This commit is contained in:
Nick Szaunig committed 2026-08-24 16:55:15 +02:00
1 parent fa13543578
commit 59a320ae36
7 files changed
+328 -44

No files matched your search

@@ -11,8 +11,12 @@ describe('HoneyBatchesService', () => {
findMany: jest.Mock;
findUnique: jest.Mock;
create: jest.Mock;
update: jest.Mock;
};
honeyMeasurement: { create: jest.Mock };
storageContainer: { findUnique: jest.Mock };
honeyBatchRelocation: { create: jest.Mock; findMany: jest.Mock };
$transaction: jest.Mock;
};
let auditLog: { record: jest.Mock };
@@ -47,8 +51,12 @@ describe('HoneyBatchesService', () => {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
},
honeyMeasurement: { create: jest.fn() },
storageContainer: { findUnique: jest.fn() },
honeyBatchRelocation: { create: jest.fn(), findMany: jest.fn() },
$transaction: jest.fn(),
};
auditLog = { record: jest.fn() };
const webhooks = { dispatch: jest.fn().mockResolvedValue(undefined) };
@@ -98,4 +106,94 @@ describe('HoneyBatchesService', () => {
},
]);
});
describe('relocate', () => {
beforeEach(() => {
prisma.$transaction.mockImplementation(
async (
callback: (tx: typeof prisma) => Promise<unknown>,
): Promise<unknown> => callback(prisma),
);
});
it('throws NotFoundException when the target container does not exist', async () => {
prisma.honeyBatch.findUnique.mockResolvedValue({
...honeyBatch,
storageContainerId: null,
});
prisma.storageContainer.findUnique.mockResolvedValue(null);
await expect(
service.relocate(
'batch-1',
{ storageContainerId: 'missing' },
'user-1',
),
).rejects.toBeInstanceOf(NotFoundException);
});
it('records the relocation and updates the batch container', async () => {
prisma.honeyBatch.findUnique
.mockResolvedValueOnce({
...honeyBatch,
storageContainerId: 'container-old',
})
.mockResolvedValueOnce({
...honeyBatch,
storageContainerId: 'container-new',
});
prisma.storageContainer.findUnique.mockResolvedValue({
id: 'container-new',
});
prisma.honeyBatchRelocation.create.mockResolvedValue({
id: 'relocation-1',
honeyBatchId: 'batch-1',
fromStorageContainerId: 'container-old',
toStorageContainerId: 'container-new',
});
await service.relocate(
'batch-1',
{ storageContainerId: 'container-new', notes: 'Umzug ins Lager 2' },
'user-1',
);
const [[createArg]] = prisma.honeyBatchRelocation.create.mock.calls as [
[
{
data: {
fromStorageContainerId: string | null;
toStorageContainerId: string;
};
},
],
];
expect(createArg.data.fromStorageContainerId).toBe('container-old');
expect(createArg.data.toStorageContainerId).toBe('container-new');
const [[updateArg]] = prisma.honeyBatch.update.mock.calls as [
[{ data: { storageContainerId: string } }],
];
expect(updateArg.data.storageContainerId).toBe('container-new');
expect(auditLog.record).toHaveBeenCalledWith(
expect.objectContaining({ entityType: 'HoneyBatchRelocation' }),
);
});
});
describe('findRelocations', () => {
it('returns the relocation history ordered by date', async () => {
prisma.honeyBatch.findUnique.mockResolvedValue(honeyBatch);
prisma.honeyBatchRelocation.findMany.mockResolvedValue([
{ id: 'relocation-1', honeyBatchId: 'batch-1' },
]);
const result = await service.findRelocations('batch-1');
expect(result).toEqual([{ id: 'relocation-1', honeyBatchId: 'batch-1' }]);
const [[findArg]] = prisma.honeyBatchRelocation.findMany.mock.calls as [
[{ where: { honeyBatchId: string } }],
];
expect(findArg.where.honeyBatchId).toBe('batch-1');
});
});
});