feat(allocation): implement resource allocation feature

- Add AllocationController with CRUD + bulk endpoints
- Add AllocationValidationService for capacity/estimate validation
- Add AllocationMatrixService for optimized matrix queries
- Add AllocationPolicy for authorization
- Add AllocationResource for API responses
- Add frontend allocationService and matrix UI
- Add E2E tests for allocation matrix (20 tests)
- Add unit tests for validation service and policies
- Fix month format conversion (YYYY-MM to YYYY-MM-01)
This commit is contained in:
2026-02-25 16:28:47 -05:00
parent fedfc21425
commit 3324c4f156
35 changed files with 3337 additions and 67 deletions

View File

@@ -198,3 +198,81 @@ test.describe('Capacity Planning - Phase 1 Tests (RED)', () => {
await expect(cell).toContainText('Full day');
});
});
test.describe('Expert Mode E2E Tests', () => {
let authToken: string;
let mainMemberId: string;
let createdMembers: string[] = [];
test.beforeEach(async ({ page }) => {
createdMembers = [];
await login(page);
authToken = await getAccessToken(page);
await setPeriod(page, '2026-02');
const member = await createTeamMember(page, authToken);
mainMemberId = member.id;
createdMembers.push(mainMemberId);
await goToCapacity(page);
});
test.afterEach(async ({ page }) => {
for (const memberId of createdMembers.splice(0)) {
await page.request.delete(`${API_BASE}/api/team-members/${memberId}`, {
headers: { Authorization: `Bearer ${authToken}` }
}).catch(() => null);
}
});
test.fixme('11.1 Expert Mode toggle appears on Capacity page and persists after reload', async ({ page }) => {
await expect(page.getByLabel('Toggle Expert Mode')).toBeVisible();
await page.getByLabel('Toggle Expert Mode').check();
await expect(page.getByLabel('Toggle Expert Mode')).toBeChecked();
await page.reload();
await expect(page.getByLabel('Toggle Expert Mode')).toBeChecked();
});
test.fixme('11.2 Grid renders all team members as rows for selected month', async ({ page }) => {
const extra = await createTeamMember(page, authToken, { name: 'Expert Mode Tester' });
createdMembers.push(extra.id);
await page.getByLabel('Toggle Expert Mode').check();
await expect(page.getByRole('row', { name: /Capacity Tester/ })).toBeVisible();
await expect(page.getByRole('row', { name: /Expert Mode Tester/ })).toBeVisible();
});
test.fixme('11.3 Typing invalid token shows red cell and disables Submit', async ({ page }) => {
await page.getByLabel('Toggle Expert Mode').check();
const cell = page.getByRole('textbox', { name: /2026-02-03/ }).first();
await cell.fill('invalid');
await cell.blur();
await expect(cell).toHaveClass(/border-error/);
await expect(page.getByRole('button', { name: /Submit/ })).toBeDisabled();
});
test.fixme('11.4 Typing valid tokens and clicking Submit saves and shows success toast', async ({ page }) => {
await page.getByLabel('Toggle Expert Mode').check();
const cell = page.getByRole('textbox', { name: /2026-02-03/ }).first();
await cell.fill('0.5');
await cell.blur();
await expect(page.getByRole('button', { name: /Submit/ })).toBeEnabled();
await page.getByRole('button', { name: /Submit/ }).click();
await expect(page.getByText(/saved/i)).toBeVisible();
});
test.fixme('11.5 KPI bar updates when cell value changes', async ({ page }) => {
await page.getByLabel('Toggle Expert Mode').check();
const cell = page.getByRole('textbox', { name: /2026-02-03/ }).first();
await cell.fill('0.5');
await cell.blur();
await expect(page.getByText(/Capacity:/)).toBeVisible();
await expect(page.getByText(/Revenue:/)).toBeVisible();
});
test.fixme('11.6 Switching off Expert Mode with dirty cells shows confirmation dialog', async ({ page }) => {
await page.getByLabel('Toggle Expert Mode').check();
const cell = page.getByRole('textbox', { name: /2026-02-03/ }).first();
await cell.fill('0.5');
await cell.blur();
await page.getByLabel('Toggle Expert Mode').uncheck();
await expect(page.getByRole('dialog')).toContainText('unsaved changes');
});
});