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,
} 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,7 +460,8 @@ export class FurnitureService {
'Die Alternative wurde zwischenzeitlich geändert. Laden Sie die Daten neu.',
);
if (
[
!option.currentlySelected &&
([
FurnitureOptionStatus.Archived,
FurnitureOptionStatus.Unavailable,
FurnitureOptionStatus.Rejected,
@@ -465,30 +470,37 @@ export class FurnitureService {
[
FurnitureAvailability.Unavailable,
FurnitureAvailability.Discontinued,
].includes(option.availability)
].includes(option.availability))
)
this.conflict('Diese Alternative ist nicht auswählbar.');
await options.update(
{
if (!option.currentlySelected) {
const previousSelections = await options.find({
where: {
projectId,
requirementId: option.requirementId,
currentlySelected: true,
},
{ currentlySelected: false },
);
option.currentlySelected = true;
option.status = FurnitureOptionStatus.Selected;
});
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,
);

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',
headerName: 'Auswahl',
valueFormatter: (p) => (p.value ? '◉ Ausgewählt' : '○ auswählen'),
valueFormatter: (p) => (p.value ? '◉ Auswahl aufheben' : '○ auswählen'),
sortable: false,
},
{

View File

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