This commit is contained in:
Bastian Wagner
2026-07-20 21:47:51 +02:00
parent 4214c2d436
commit 2328081aa3
5 changed files with 127 additions and 21 deletions

View File

@@ -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,
};
}

View File

@@ -40,6 +40,10 @@ import {
sumMoney, sumMoney,
} from './furniture-pricing'; } from './furniture-pricing';
import { FurnitureRepository } from './furniture.repository'; import { FurnitureRepository } from './furniture.repository';
import {
deselectFurnitureOption,
toggleFurnitureSelection,
} from './furniture-selection';
import { RenovationRepository } from './renovation.repository'; import { RenovationRepository } from './renovation.repository';
@Injectable() @Injectable()
@@ -430,7 +434,7 @@ export class FurnitureService {
const options = manager.getRepository(FurnitureOptionEntity); const options = manager.getRepository(FurnitureOptionEntity);
const candidate = await options.findOneBy({ id, projectId }); const candidate = await options.findOneBy({ id, projectId });
if (!candidate || candidate.deletedAt) this.notFound(); if (!candidate || candidate.deletedAt) this.notFound();
await manager const requirement = await manager
.getRepository(FurnitureRequirementEntity) .getRepository(FurnitureRequirementEntity)
.createQueryBuilder('requirement') .createQueryBuilder('requirement')
.setLock('pessimistic_write') .setLock('pessimistic_write')
@@ -456,7 +460,8 @@ export class FurnitureService {
'Die Alternative wurde zwischenzeitlich geändert. Laden Sie die Daten neu.', 'Die Alternative wurde zwischenzeitlich geändert. Laden Sie die Daten neu.',
); );
if ( if (
[ !option.currentlySelected &&
([
FurnitureOptionStatus.Archived, FurnitureOptionStatus.Archived,
FurnitureOptionStatus.Unavailable, FurnitureOptionStatus.Unavailable,
FurnitureOptionStatus.Rejected, FurnitureOptionStatus.Rejected,
@@ -465,30 +470,37 @@ export class FurnitureService {
[ [
FurnitureAvailability.Unavailable, FurnitureAvailability.Unavailable,
FurnitureAvailability.Discontinued, FurnitureAvailability.Discontinued,
].includes(option.availability) ].includes(option.availability))
) )
this.conflict('Diese Alternative ist nicht auswählbar.'); this.conflict('Diese Alternative ist nicht auswählbar.');
await options.update( if (!option.currentlySelected) {
{ const previousSelections = await options.find({
where: {
projectId, projectId,
requirementId: option.requirementId, requirementId: option.requirementId,
currentlySelected: true, currentlySelected: true,
}, },
{ currentlySelected: false }, });
); for (const previous of previousSelections) {
option.currentlySelected = true; Object.assign(previous, deselectFurnitureOption(previous));
option.status = FurnitureOptionStatus.Selected; }
await options.save(previousSelections);
}
const transition = toggleFurnitureSelection(option, requirement.status);
Object.assign(option, transition.option);
const saved = await options.save(option); const saved = await options.save(option);
await manager await manager
.getRepository(FurnitureRequirementEntity) .getRepository(FurnitureRequirementEntity)
.update( .update(
{ id: option.requirementId, projectId }, { id: option.requirementId, projectId },
{ status: FurnitureRequirementStatus.Selected }, { status: transition.requirementStatus },
); );
await this.activity( await this.activity(
projectId, projectId,
userId, userId,
'furniture.option-selected', saved.currentlySelected
? 'furniture.option-selected'
: 'furniture.option-deselected',
{ optionId: id, requirementId: option.requirementId }, { optionId: id, requirementId: option.requirementId },
manager, manager,
); );

View File

@@ -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);
});
});

View File

@@ -690,7 +690,7 @@ export class FurnitureGridComponent implements OnChanges {
{ {
field: 'currentlySelected', field: 'currentlySelected',
headerName: 'Auswahl', headerName: 'Auswahl',
valueFormatter: (p) => (p.value ? '◉ Ausgewählt' : '○ auswählen'), valueFormatter: (p) => (p.value ? '◉ Auswahl aufheben' : '○ auswählen'),
sortable: false, sortable: false,
}, },
{ {

View File

@@ -420,10 +420,11 @@ const statusLabels: Record<string, string> = {
<button <button
type="button" type="button"
class="ui-button" class="ui-button"
[class.ui-button--secondary]="option.currentlySelected"
(click)="select(option)" (click)="select(option)"
[disabled]="option.currentlySelected" [attr.aria-pressed]="option.currentlySelected"
> >
Auswählen {{ option.currentlySelected ? 'Auswahl aufheben' : 'Auswählen' }}
</button> </button>
<button <button
type="button" type="button"