Merged PR 2022: fix(checkout): prevent duplicate tasks in open reward carousel

Related work items: #5468
This commit is contained in:
Lorenz Hilpert
2025-11-11 14:15:05 +00:00
committed by Nino Righi
parent 6df02d9e86
commit cc186dbbe2
3 changed files with 50 additions and 10 deletions

View File

@@ -1,4 +1,10 @@
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
input,
} from '@angular/core';
import { RouterLink } from '@angular/router';
import { DBHOrderItemListItemDTO } from '@generated/swagger/oms-api';
import { TabService } from '@isa/core/tabs';
@@ -11,6 +17,12 @@ import { isaActionChevronRight } from '@isa/icons';
*
* Shows customer name and a chevron button for navigation to the order completion page.
*/
export type OpenTaskCardInput = Pick<
DBHOrderItemListItemDTO,
'orderId' | 'firstName' | 'lastName'
>;
@Component({
selector: 'reward-catalog-open-task-card',
standalone: true,
@@ -20,7 +32,7 @@ import { isaActionChevronRight } from '@isa/icons';
<a
class="bg-isa-white flex items-center justify-between px-[22px] py-[20px] rounded-2xl w-[334px] cursor-pointer no-underline"
data-what="open-task-card"
[attr.data-which]="task().orderItemId"
[attr.data-which]="task().orderId"
[routerLink]="routePath()"
>
<div class="flex flex-col gap-1">
@@ -47,7 +59,7 @@ export class OpenTaskCardComponent {
/**
* The open task data to display
*/
readonly task = input.required<DBHOrderItemListItemDTO>();
readonly task = input.required<OpenTaskCardInput>();
/**
* Computed customer name from first and last name
@@ -62,7 +74,9 @@ export class OpenTaskCardComponent {
/**
* Current tab ID for navigation
*/
readonly #tabId = computed(() => this.#tabService.activatedTab()?.id ?? Date.now());
readonly #tabId = computed(
() => this.#tabService.activatedTab()?.id ?? Date.now(),
);
/**
* Route path to the reward order confirmation page.
@@ -74,6 +88,12 @@ export class OpenTaskCardComponent {
console.warn('Missing orderId in task', this.task());
return [];
}
return ['/', this.#tabId(), 'reward', 'order-confirmation', orderId.toString()];
return [
'/',
this.#tabId(),
'reward',
'order-confirmation',
orderId.toString(),
];
});
}

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
import { OpenRewardTasksResource } from '@isa/oms/data-access';
import { CarouselComponent } from '@isa/ui/carousel';
import { OpenTaskCardComponent } from './open-task-card.component';
@@ -13,6 +13,7 @@ import { OpenTaskCardComponent } from './open-task-card.component';
* - Keyboard navigation (Arrow Left/Right)
* - Automatic visibility based on task availability
* - Shared global resource for consistent data across app
* - Deduplicates tasks to show only one card per orderId
*/
@Component({
selector: 'reward-catalog-open-tasks-carousel',
@@ -22,7 +23,7 @@ import { OpenTaskCardComponent } from './open-task-card.component';
@if (openTasksResource.hasOpenTasks()) {
<div class="mb-4" data-what="open-tasks-carousel">
<ui-carousel [gap]="'1rem'" [arrowAutoHide]="true">
@for (task of openTasksResource.tasks(); track task.orderItemId) {
@for (task of uniqueTasks(); track task.orderId) {
<reward-catalog-open-task-card [task]="task" />
}
</ui-carousel>
@@ -36,4 +37,23 @@ export class OpenTasksCarouselComponent {
* Global resource managing open reward tasks data
*/
readonly openTasksResource = inject(OpenRewardTasksResource);
/**
* Deduplicated tasks - shows only one task per orderId.
* When multiple order items exist for the same order, only the first one is displayed.
*
* @returns Array of unique tasks filtered by orderId
*/
readonly uniqueTasks = computed(() => {
const tasks = this.openTasksResource.tasks();
const seenOrderIds = new Set<number>();
return tasks.filter(task => {
if (!task.orderId || seenOrderIds.has(task.orderId)) {
return false;
}
seenOrderIds.add(task.orderId);
return true;
});
});
}

View File

@@ -12,7 +12,7 @@ import { firstValueFrom } from 'rxjs';
* Service for fetching open reward distribution tasks (Prämienausgabe) from the OMS API.
*
* Provides cached access to unfinished reward orders with automatic request deduplication.
* These are reward orders in processing statuses 16 (InPreparation) and 128 (ReadyForPickup)
* These are reward orders in processing statuses 16 (Bestellt) and 8192 (Nachbestellt)
* that need to be completed.
*/
@Injectable({ providedIn: 'root' })
@@ -24,7 +24,7 @@ export class OpenRewardTasksService {
* Fetches open reward distribution tasks (unfinished Prämienausgabe orders).
*
* Returns reward orders that are:
* - In status 16 (InPreparation) or 128 (ReadyForPickup)
* - In status 16 (Bestellt) or 8192 (Nachbestellt)
* - Flagged as reward items (praemie: "1-")
*
* Results are cached for 1 minute to balance freshness with performance.
@@ -42,7 +42,7 @@ export class OpenRewardTasksService {
const payload: QueryTokenDTO = {
input: {},
filter: {
orderitemprocessingstatus: '16', // InPreparation(16) and ReadyForPickup(128)
orderitemprocessingstatus: '16;8192', // Bestellt(16) and Nachbestellt(8192)
praemie: '1-', // Reward items only
supplier_id: '16',
},