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

@@ -0,0 +1,116 @@
import { fireEvent, render, screen } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import CapacityCalendar from '$lib/components/capacity/CapacityCalendar.svelte';
import CapacitySummary from '$lib/components/capacity/CapacitySummary.svelte';
import type { Capacity, Revenue, TeamCapacity } from '$lib/types/capacity';
describe('capacity components', () => {
it('4.1.25 CapacityCalendar displays selected month', () => {
const capacity: Capacity = {
team_member_id: 'member-1',
month: '2026-02',
working_days: 20,
person_days: 20,
hours: 160,
details: [
{
date: '2026-02-02',
day_of_week: 1,
is_weekend: false,
is_holiday: false,
is_pto: false,
availability: 1,
effective_hours: 8
}
]
};
render(CapacityCalendar, {
props: {
month: '2026-02',
capacity,
holidays: [],
ptos: []
}
});
expect(screen.getByTestId('capacity-calendar')).toBeTruthy();
expect(screen.getByText('2026-02')).toBeTruthy();
expect(screen.getByText('Working days: 20')).toBeTruthy();
});
it('4.1.26 Availability editor toggles values', async () => {
const capacity: Capacity = {
team_member_id: 'member-1',
month: '2026-02',
working_days: 20,
person_days: 20,
hours: 160,
details: [
{
date: '2026-02-10',
day_of_week: 2,
is_weekend: false,
is_holiday: false,
is_pto: false,
availability: 1,
effective_hours: 8
}
]
};
render(CapacityCalendar, {
props: {
month: '2026-02',
capacity,
holidays: [],
ptos: []
}
});
const select = screen.getByLabelText('Availability for 2026-02-10') as HTMLSelectElement;
expect(select.value).toBe('1');
await fireEvent.change(select, { target: { value: '0.5' } });
expect(select.value).toBe('0.5');
});
it('4.1.27 CapacitySummary shows totals', () => {
const teamCapacity: TeamCapacity = {
month: '2026-02',
total_person_days: 57,
total_hours: 456,
member_capacities: [
{
team_member_id: 'm1',
team_member_name: 'VJ',
role: 'Frontend Dev',
person_days: 19,
hours: 152,
hourly_rate: 80
}
]
};
const revenue: Revenue = {
month: '2026-02',
total_revenue: 45600,
member_revenues: []
};
render(CapacitySummary, {
props: {
teamCapacity,
revenue,
teamMembers: []
}
});
expect(screen.getByTestId('team-capacity-card')).toBeTruthy();
expect(screen.getByTestId('possible-revenue-card')).toBeTruthy();
expect(screen.getByText('57.0d')).toBeTruthy();
expect(screen.getByText('456 hrs')).toBeTruthy();
expect(screen.getByText('$45,600.00')).toBeTruthy();
});
});