.
This commit is contained in:
parent
d8f2407ddf
commit
9689bfcc18
23 changed files with 1815 additions and 955 deletions
|
|
@ -1,10 +1,16 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { defaultProject } from '../src/lib/defaults';
|
||||
import { flooringCuts, groupFlooringCuts } from '../src/lib/cut-list';
|
||||
import { defaultProject, rectangle } from '../src/lib/defaults';
|
||||
import {
|
||||
flooringCuts,
|
||||
groupFlooringCuts,
|
||||
groupSkirtingCuts,
|
||||
skirtingCuts,
|
||||
} from '../src/lib/cut-list';
|
||||
import { planFlooring } from '../src/lib/flooring';
|
||||
import { planSkirting } from '../src/lib/skirting';
|
||||
import type { FlooringPiece, FlooringPlan } from '../src/lib/types';
|
||||
|
||||
function piece(id: string, changes: Partial<FlooringPiece> = {}): FlooringPiece {
|
||||
function piece(id: string, stockId: string, changes: Partial<FlooringPiece> = {}): FlooringPiece {
|
||||
return {
|
||||
id,
|
||||
roomId: 'room-1',
|
||||
|
|
@ -13,14 +19,11 @@ function piece(id: string, changes: Partial<FlooringPiece> = {}): FlooringPiece
|
|||
polygon: [],
|
||||
length: 600,
|
||||
width: 190,
|
||||
stockId: 'B1',
|
||||
stockId,
|
||||
reused: false,
|
||||
cut: true,
|
||||
shaped: false,
|
||||
template: [],
|
||||
factoryLeft: true,
|
||||
factoryRight: false,
|
||||
ripEdge: null,
|
||||
...changes,
|
||||
};
|
||||
}
|
||||
|
|
@ -28,8 +31,12 @@ function plan(pieces: FlooringPiece[]): FlooringPlan {
|
|||
return {
|
||||
pieces,
|
||||
rows: [],
|
||||
stocks: [],
|
||||
boardCount: 0,
|
||||
stocks: ['B1', 'B2', 'B3'].map((id) => ({
|
||||
id,
|
||||
pieceIds: pieces.filter((p) => p.stockId === id).map((p) => p.id),
|
||||
remaining: 0,
|
||||
})),
|
||||
boardCount: 3,
|
||||
cuts: 0,
|
||||
wasteArea: 0,
|
||||
wastePercent: 0,
|
||||
|
|
@ -39,81 +46,54 @@ function plan(pieces: FlooringPiece[]): FlooringPlan {
|
|||
};
|
||||
}
|
||||
|
||||
describe('flooring cut batches', () => {
|
||||
it('combines matching cuts across rows and stocks, preserving every placement exactly once', () => {
|
||||
const pieces = [
|
||||
piece('F1'),
|
||||
piece('F2', { row: 4, stockId: 'B4', reused: true }),
|
||||
piece('F3', { cut: false, length: 1200 }),
|
||||
piece('F4', { cut: false, reused: true }),
|
||||
piece('F5', { roomId: 'room-2' }),
|
||||
];
|
||||
const source = plan(pieces),
|
||||
before = structuredClone(source);
|
||||
describe('cuts grouped by purchased board', () => {
|
||||
it('keeps different cuts and cross-room offcuts together, separating identical cuts from different boards', () => {
|
||||
const source = plan([
|
||||
piece('F1', 'B2'),
|
||||
piece('F2', 'B1'),
|
||||
piece('F3', 'B2', { roomId: 'room-2', row: 4, length: 397, reused: true }),
|
||||
piece('FU1', 'B3', { length: 1200, cut: false }),
|
||||
]);
|
||||
const before = structuredClone(source);
|
||||
const groups = groupFlooringCuts(source);
|
||||
expect(groups.map((group) => group.map((p) => p.id))).toEqual([['F1', 'F2'], ['F4'], ['F5']]);
|
||||
expect(
|
||||
groups
|
||||
.flat()
|
||||
.map((p) => p.id)
|
||||
.sort(),
|
||||
).toEqual(
|
||||
flooringCuts(source)
|
||||
.map((p) => p.id)
|
||||
.sort(),
|
||||
);
|
||||
expect(groups.map((group) => group.map((p) => p.id))).toEqual([['F2'], ['F1', 'F3']]);
|
||||
expect(source).toEqual(before);
|
||||
expect(groupFlooringCuts(source)).toEqual(groups);
|
||||
});
|
||||
|
||||
it('keeps different factory ends, rip edges and thousandth-millimetre dimensions separate', () => {
|
||||
it('includes ready-to-use offcuts in their original board group', () => {
|
||||
const groups = groupFlooringCuts(
|
||||
plan([
|
||||
piece('F1'),
|
||||
piece('F2', { factoryLeft: false, factoryRight: true }),
|
||||
piece('F3', { ripEdge: 'top' }),
|
||||
piece('F4', { ripEdge: 'bottom' }),
|
||||
piece('F5', { length: 600.001 }),
|
||||
piece('F6', { width: 190.001 }),
|
||||
]),
|
||||
plan([piece('F1', 'B1'), piece('F2', 'B1', { cut: false, reused: true })]),
|
||||
);
|
||||
expect(groups).toHaveLength(6);
|
||||
expect(groups.map((group) => group.map((p) => p.id))).toEqual([['F1', 'F2']]);
|
||||
});
|
||||
|
||||
it('groups shapes only when their complete measured templates match', () => {
|
||||
const template = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 600, y: 0 },
|
||||
{ x: 600, y: 190 },
|
||||
{ x: 0, y: 100 },
|
||||
];
|
||||
const groups = groupFlooringCuts(
|
||||
plan([
|
||||
piece('F1', { shaped: true, template }),
|
||||
piece('F2', { shaped: true, template: structuredClone(template) }),
|
||||
piece('F3', { shaped: true, template: [...template.slice(0, 3), { x: 0, y: 100.001 }] }),
|
||||
piece('F4'),
|
||||
]),
|
||||
);
|
||||
expect(groups.map((group) => group.map((p) => p.id))).toEqual([['F1', 'F2'], ['F3'], ['F4']]);
|
||||
});
|
||||
|
||||
it('reduces the default room to a few batches while keeping all cut and offcut codes', () => {
|
||||
it('includes every flooring and skirting cut once in stock order across multiple rooms', () => {
|
||||
const project = defaultProject();
|
||||
project.rooms.push(rectangle('room-2', 'Study', 3200, 3100));
|
||||
const floor = planFlooring(project.rooms, project.flooring, project.tolerance);
|
||||
const groups = groupFlooringCuts(floor);
|
||||
expect(groups.length).toBeLessThanOrEqual(7);
|
||||
expect(groups.length).toBeLessThan(flooringCuts(floor).length / 2);
|
||||
const skirt = planSkirting(project.rooms, project.skirting, project.tolerance);
|
||||
for (const [groups, pieces, stocks] of [
|
||||
[groupFlooringCuts(floor), flooringCuts(floor), floor.stocks],
|
||||
[groupSkirtingCuts(skirt), skirtingCuts(skirt), skirt.stocks],
|
||||
] as const) {
|
||||
expect(
|
||||
groups
|
||||
.flat()
|
||||
.map((p) => p.id)
|
||||
.sort(),
|
||||
).toEqual(pieces.map((p) => p.id).sort());
|
||||
expect(new Set(groups.flat().map((p) => p.id)).size).toBe(pieces.length);
|
||||
const used = new Set(pieces.map((p) => p.stockId));
|
||||
expect(groups.map((group) => group[0].stockId)).toEqual(
|
||||
stocks.filter((s) => used.has(s.id)).map((s) => s.id),
|
||||
);
|
||||
for (const group of groups) expect(new Set(group.map((p) => p.stockId)).size).toBe(1);
|
||||
}
|
||||
expect(
|
||||
groups
|
||||
.flat()
|
||||
.map((p) => p.id)
|
||||
.sort(),
|
||||
).toEqual(
|
||||
flooringCuts(floor)
|
||||
.map((p) => p.id)
|
||||
.sort(),
|
||||
);
|
||||
expect(new Set(groups.flat().map((p) => p.id)).size).toBe(flooringCuts(floor).length);
|
||||
[...groupFlooringCuts(floor), ...groupSkirtingCuts(skirt)].some(
|
||||
(group) => new Set(group.map((p) => p.roomId)).size > 1,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue