mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-01 21:43:27 +00:00
Extends the warehouse hierarchy below zone with ground stacks and vertical slots, so a container's exact position is recorded rather than only its zone. - freight.warehouse_zone_stacks / warehouse_zone_slots, plus nullable stack_id / slot_id on warehouse_inventory (existing rows stay valid) - slot occupancy is derived from inventory status, guarded by a partial unique index, so no exit path has to remember to free a slot - placement service: hierarchy validation, bottom-up stacking rules, accessibility/blocking-container reads, capacity vs slot summaries - stack CRUD with auto-generated slots; reuses warehouse-zone permissions - slot support folded into the existing move()/store() paths - fix: validateLocation now rejects a mismatched warehouse/yard/zone triple - seed:warehouse-layout builds the layout from a JSON config
89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
import { ConflictException, NotFoundException } from '@nestjs/common';
|
|
|
|
import { WarehouseYardsService } from './warehouse-yards.service';
|
|
import { WarehouseZonesService } from './warehouse-zones.service';
|
|
|
|
/**
|
|
* Soft-deleting a parent would leave its children pointing at a row every
|
|
* joining query drops, so both removes refuse while children exist.
|
|
*/
|
|
function makeYardsService(yard: unknown) {
|
|
const yardsRepository = {
|
|
findById: jest.fn().mockResolvedValue(yard),
|
|
softDelete: jest.fn().mockResolvedValue(undefined),
|
|
};
|
|
const service = Object.create(WarehouseYardsService.prototype) as Record<string, unknown>;
|
|
service.yardsRepository = yardsRepository;
|
|
return { service: service as unknown as WarehouseYardsService, yardsRepository };
|
|
}
|
|
|
|
function makeZonesService(zone: unknown, heldInventory: number, configuredStacks = 0) {
|
|
const zonesRepository = {
|
|
findById: jest.fn().mockResolvedValue(zone),
|
|
softDelete: jest.fn().mockResolvedValue(undefined),
|
|
};
|
|
const inventoryRepository = {
|
|
findAndCount: jest.fn().mockResolvedValue([[], heldInventory]),
|
|
};
|
|
const service = Object.create(WarehouseZonesService.prototype) as Record<string, unknown>;
|
|
service.zonesRepository = zonesRepository;
|
|
service.inventoryRepository = inventoryRepository;
|
|
service.dataSource = { query: jest.fn().mockResolvedValue([{ count: configuredStacks }]) };
|
|
return { service: service as unknown as WarehouseZonesService, zonesRepository };
|
|
}
|
|
|
|
describe('WarehouseYardsService.remove', () => {
|
|
it('soft-deletes a yard with no zones', async () => {
|
|
const { service, yardsRepository } = makeYardsService({ id: 'y1', code: 'CY-A', zones: [] });
|
|
|
|
await expect(service.remove('y1')).resolves.toEqual({ id: 'y1', deleted: true });
|
|
expect(yardsRepository.softDelete).toHaveBeenCalledWith('y1');
|
|
});
|
|
|
|
it('refuses while zones remain', async () => {
|
|
const { service, yardsRepository } = makeYardsService({
|
|
id: 'y1',
|
|
code: 'CY-A',
|
|
zones: [{ id: 'z1' }],
|
|
});
|
|
|
|
await expect(service.remove('y1')).rejects.toBeInstanceOf(ConflictException);
|
|
expect(yardsRepository.softDelete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('404s on an unknown yard', async () => {
|
|
const { service } = makeYardsService(null);
|
|
|
|
await expect(service.remove('nope')).rejects.toBeInstanceOf(NotFoundException);
|
|
});
|
|
});
|
|
|
|
describe('WarehouseZonesService.remove', () => {
|
|
it('soft-deletes an empty zone', async () => {
|
|
const { service, zonesRepository } = makeZonesService({ id: 'z1', code: 'ZA' }, 0);
|
|
|
|
await expect(service.remove('z1')).resolves.toEqual({ id: 'z1', deleted: true });
|
|
expect(zonesRepository.softDelete).toHaveBeenCalledWith('z1');
|
|
});
|
|
|
|
it('refuses while inventory sits in it', async () => {
|
|
const { service, zonesRepository } = makeZonesService({ id: 'z1', code: 'ZA' }, 16);
|
|
|
|
await expect(service.remove('z1')).rejects.toBeInstanceOf(ConflictException);
|
|
expect(zonesRepository.softDelete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('refuses while ground stacks are still configured in it', async () => {
|
|
const { service, zonesRepository } = makeZonesService({ id: 'z1', code: 'ZA' }, 0, 20);
|
|
|
|
await expect(service.remove('z1')).rejects.toThrow(/still has 20 configured stack\(s\)/);
|
|
expect(zonesRepository.softDelete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('404s on an unknown zone', async () => {
|
|
const { service } = makeZonesService(null, 0);
|
|
|
|
await expect(service.remove('nope')).rejects.toBeInstanceOf(NotFoundException);
|
|
});
|
|
});
|