diff --git a/apps/backend/src/renovation/furniture-selection.ts b/apps/backend/src/renovation/furniture-selection.ts new file mode 100644 index 0000000..8b0882c --- /dev/null +++ b/apps/backend/src/renovation/furniture-selection.ts @@ -0,0 +1,52 @@ +import { + FurnitureOptionStatus, + FurnitureRequirementStatus, +} from './entities/furniture.entities'; + +export interface FurnitureSelectionState { + currentlySelected: boolean; + favorite: boolean; + status: FurnitureOptionStatus; +} + +export function deselectFurnitureOption( + option: FurnitureSelectionState, +): FurnitureSelectionState { + return { + ...option, + currentlySelected: false, + status: + option.status === FurnitureOptionStatus.Selected + ? option.favorite + ? FurnitureOptionStatus.Favorite + : FurnitureOptionStatus.Reviewing + : option.status, + }; +} + +export function toggleFurnitureSelection( + option: FurnitureSelectionState, + requirementStatus: FurnitureRequirementStatus, +): { + option: FurnitureSelectionState; + requirementStatus: FurnitureRequirementStatus; +} { + if (option.currentlySelected) { + return { + option: deselectFurnitureOption(option), + requirementStatus: + option.status === FurnitureOptionStatus.Selected + ? FurnitureRequirementStatus.DecisionOpen + : requirementStatus, + }; + } + + return { + option: { + ...option, + currentlySelected: true, + status: FurnitureOptionStatus.Selected, + }, + requirementStatus: FurnitureRequirementStatus.Selected, + }; +} diff --git a/apps/backend/src/renovation/furniture.service.ts b/apps/backend/src/renovation/furniture.service.ts index 19a13dd..69eff1d 100644 --- a/apps/backend/src/renovation/furniture.service.ts +++ b/apps/backend/src/renovation/furniture.service.ts @@ -40,6 +40,10 @@ import { sumMoney, } from './furniture-pricing'; import { FurnitureRepository } from './furniture.repository'; +import { + deselectFurnitureOption, + toggleFurnitureSelection, +} from './furniture-selection'; import { RenovationRepository } from './renovation.repository'; @Injectable() @@ -430,7 +434,7 @@ export class FurnitureService { const options = manager.getRepository(FurnitureOptionEntity); const candidate = await options.findOneBy({ id, projectId }); if (!candidate || candidate.deletedAt) this.notFound(); - await manager + const requirement = await manager .getRepository(FurnitureRequirementEntity) .createQueryBuilder('requirement') .setLock('pessimistic_write') @@ -456,39 +460,47 @@ export class FurnitureService { 'Die Alternative wurde zwischenzeitlich geändert. Laden Sie die Daten neu.', ); if ( - [ + !option.currentlySelected && + ([ FurnitureOptionStatus.Archived, FurnitureOptionStatus.Unavailable, FurnitureOptionStatus.Rejected, FurnitureOptionStatus.Returned, ].includes(option.status) || - [ - FurnitureAvailability.Unavailable, - FurnitureAvailability.Discontinued, - ].includes(option.availability) + [ + FurnitureAvailability.Unavailable, + FurnitureAvailability.Discontinued, + ].includes(option.availability)) ) this.conflict('Diese Alternative ist nicht auswählbar.'); - await options.update( - { - projectId, - requirementId: option.requirementId, - currentlySelected: true, - }, - { currentlySelected: false }, - ); - option.currentlySelected = true; - option.status = FurnitureOptionStatus.Selected; + if (!option.currentlySelected) { + const previousSelections = await options.find({ + where: { + projectId, + requirementId: option.requirementId, + currentlySelected: true, + }, + }); + for (const previous of previousSelections) { + Object.assign(previous, deselectFurnitureOption(previous)); + } + await options.save(previousSelections); + } + const transition = toggleFurnitureSelection(option, requirement.status); + Object.assign(option, transition.option); const saved = await options.save(option); await manager .getRepository(FurnitureRequirementEntity) .update( { id: option.requirementId, projectId }, - { status: FurnitureRequirementStatus.Selected }, + { status: transition.requirementStatus }, ); await this.activity( projectId, userId, - 'furniture.option-selected', + saved.currentlySelected + ? 'furniture.option-selected' + : 'furniture.option-deselected', { optionId: id, requirementId: option.requirementId }, manager, ); diff --git a/apps/backend/src/renovation/tests/furniture-selection.spec.ts b/apps/backend/src/renovation/tests/furniture-selection.spec.ts new file mode 100644 index 0000000..3f8aac1 --- /dev/null +++ b/apps/backend/src/renovation/tests/furniture-selection.spec.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest'; +import { + FurnitureOptionStatus, + FurnitureRequirementStatus, +} from '../entities/furniture.entities'; +import { toggleFurnitureSelection } from '../furniture-selection'; + +describe('furniture selection', () => { + it('removes a selected option from the cost selection', () => { + const result = toggleFurnitureSelection( + { + currentlySelected: true, + favorite: false, + status: FurnitureOptionStatus.Selected, + }, + FurnitureRequirementStatus.Selected, + ); + + expect(result).toEqual({ + option: { + currentlySelected: false, + favorite: false, + status: FurnitureOptionStatus.Reviewing, + }, + requirementStatus: FurnitureRequirementStatus.DecisionOpen, + }); + }); + + it('keeps the favorite marker when a selected favorite is deselected', () => { + const result = toggleFurnitureSelection( + { + currentlySelected: true, + favorite: true, + status: FurnitureOptionStatus.Selected, + }, + FurnitureRequirementStatus.Selected, + ); + + expect(result.option.status).toBe(FurnitureOptionStatus.Favorite); + }); +}); diff --git a/apps/frontend/src/app/features/projects/furniture-grid.component.ts b/apps/frontend/src/app/features/projects/furniture-grid.component.ts index 3ca0ec4..b43face 100644 --- a/apps/frontend/src/app/features/projects/furniture-grid.component.ts +++ b/apps/frontend/src/app/features/projects/furniture-grid.component.ts @@ -690,7 +690,7 @@ export class FurnitureGridComponent implements OnChanges { { field: 'currentlySelected', headerName: 'Auswahl', - valueFormatter: (p) => (p.value ? '◉ Ausgewählt' : '○ auswählen'), + valueFormatter: (p) => (p.value ? '◉ Auswahl aufheben' : '○ auswählen'), sortable: false, }, { diff --git a/apps/frontend/src/app/features/projects/furniture-planning.component.ts b/apps/frontend/src/app/features/projects/furniture-planning.component.ts index aff26a7..720a6ff 100644 --- a/apps/frontend/src/app/features/projects/furniture-planning.component.ts +++ b/apps/frontend/src/app/features/projects/furniture-planning.component.ts @@ -420,10 +420,11 @@ const statusLabels: Record = {