mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merged PR 2008: fix(reward-print, reward-popup, reward-destination): improve reward cart stab...
fix(reward-print, reward-popup, reward-destination): improve reward cart stability and UX - fix: remove console.log statement from calculate-price-value helper - fix: add loading/pending state to print button to prevent duplicate prints - fix: debounce reward selection resource reloading to prevent race conditions - fix: correct reward cart item destination-info alignment and flex behavior - fix: support OrderType in OrderDestinationComponent alongside OrderTypeFeature - fix: use unitPrice instead of total for price calculations in reward items - refactor: update calculatePriceValue test descriptions for clarity - fix: fallback to order.orderType when features don't contain orderType The reward selection popup now properly waits for all resources to reload before resolving, preventing timing issues with cart synchronization. Print button shows pending state during print operations. Destination info components now handle both legacy OrderType and new OrderTypeFeature enums for better compatibility. Ref: #5442, #5445
This commit is contained in:
committed by
Lorenz Hilpert
parent
af7bad03f5
commit
f04e36e710
@@ -55,8 +55,6 @@ import {
|
||||
RewardSelectionPopUpService,
|
||||
} from '@isa/checkout/shared/reward-selection-dialog';
|
||||
|
||||
import { injectConfirmationDialog, injectFeedbackDialog } from '@isa/ui/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-details',
|
||||
templateUrl: 'article-details.component.html',
|
||||
@@ -210,7 +208,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
).path;
|
||||
}
|
||||
|
||||
showMore: boolean = false;
|
||||
showMore = false;
|
||||
|
||||
@ViewChild('detailsContainer', { read: ElementRef, static: false })
|
||||
detailsContainer: ElementRef;
|
||||
@@ -610,7 +608,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
async navigateToResultList() {
|
||||
const processId = this.applicationService.activatedProcessId;
|
||||
let crumbs = await this.breadcrumb
|
||||
const crumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, [
|
||||
'catalog',
|
||||
'details',
|
||||
|
||||
@@ -3,10 +3,10 @@ import { calculatePriceValue } from './calculate-price-value.helper';
|
||||
import { RewardSelectionItem } from '@isa/checkout/data-access';
|
||||
|
||||
describe('calculatePriceValue', () => {
|
||||
it('should return item total price when available', () => {
|
||||
it('should return item unit price when available', () => {
|
||||
const item: RewardSelectionItem = {
|
||||
item: {
|
||||
total: { value: { value: 99.99 } },
|
||||
unitPrice: { value: { value: 99.99 } },
|
||||
},
|
||||
cartQuantity: 1,
|
||||
rewardCartQuantity: 0,
|
||||
@@ -20,7 +20,7 @@ describe('calculatePriceValue', () => {
|
||||
expect(result).toBe(99.99);
|
||||
});
|
||||
|
||||
it('should return availability price when total price not available', () => {
|
||||
it('should return availability price when unit price not available', () => {
|
||||
const item: RewardSelectionItem = {
|
||||
item: {
|
||||
availability: { price: { value: { value: 79.99 } } },
|
||||
|
||||
@@ -3,13 +3,14 @@ import { RewardSelectionItem } from '@isa/checkout/data-access';
|
||||
export const calculatePriceValue = (
|
||||
rewardSelectionItem: RewardSelectionItem,
|
||||
): number => {
|
||||
const itemTotalPrice = rewardSelectionItem.item?.total?.value?.value;
|
||||
console.log(rewardSelectionItem);
|
||||
const itemUnitPrice = rewardSelectionItem.item?.unitPrice?.value?.value;
|
||||
const availabilityPrice =
|
||||
rewardSelectionItem.item?.availability?.price?.value?.value;
|
||||
const catalogPrice = rewardSelectionItem.catalogPrice?.value?.value;
|
||||
|
||||
if (itemTotalPrice != null && itemTotalPrice !== 0) {
|
||||
return itemTotalPrice;
|
||||
if (itemUnitPrice != null && itemUnitPrice !== 0) {
|
||||
return itemUnitPrice;
|
||||
}
|
||||
|
||||
if (availabilityPrice != null && availabilityPrice !== 0) {
|
||||
|
||||
@@ -9,9 +9,7 @@ import {
|
||||
ProductInfoComponent,
|
||||
DisplayOrderDestinationInfoComponent,
|
||||
} from '@isa/checkout/shared/product-info';
|
||||
import {
|
||||
DisplayOrderItemDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { DisplayOrderItemDTO } from '@generated/swagger/oms-api';
|
||||
import { Product } from '@isa/common/data-access';
|
||||
import { type OrderItemGroup } from '@isa/checkout/data-access';
|
||||
|
||||
@@ -34,7 +32,7 @@ export class OrderConfirmationItemListItemComponent {
|
||||
* This now receives an OrderItemGroup which contains the necessary order information
|
||||
* (features, targetBranch, shippingAddress) required by the DisplayOrderDestinationInfoComponent.
|
||||
*/
|
||||
order = input.required<Pick<OrderItemGroup, 'features' | 'targetBranch' | 'shippingAddress'>>();
|
||||
order = input.required<OrderItemGroup>();
|
||||
|
||||
productItem = computed<Product | undefined>(() => {
|
||||
return this.item()?.product;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
@if (!isHorizontal()) {
|
||||
<checkout-destination-info
|
||||
[underline]="true"
|
||||
class="cursor-pointer mt-4 max-w-[14.25rem] grow-0 shrink-0"
|
||||
class="cursor-pointer mt-4 w-[14.25rem] grow items-end"
|
||||
(click)="updatePurchaseOption()"
|
||||
[shoppingCartItem]="itm"
|
||||
></checkout-destination-info>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
:host {
|
||||
@apply contents;
|
||||
@apply flex;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, computed, inject, input } from '@angular/core';
|
||||
import { Component, computed, input } from '@angular/core';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { getOrderTypeFeature } from '@isa/checkout/data-access';
|
||||
import { DisplayOrder, DisplayOrderItem } from '@isa/oms/data-access';
|
||||
import { OrderItemGroup } from '@isa/checkout/data-access';
|
||||
import { DisplayOrderItem } from '@isa/oms/data-access';
|
||||
import {
|
||||
OrderDestinationComponent,
|
||||
ShippingAddress,
|
||||
@@ -22,15 +22,13 @@ export class DisplayOrderDestinationInfoComponent {
|
||||
});
|
||||
|
||||
// Accept the parent DisplayOrder (required for branch info)
|
||||
order = input.required<DisplayOrder>();
|
||||
order = input.required<OrderItemGroup>();
|
||||
|
||||
// Optionally accept DisplayOrderItem (for potential future item-specific logic)
|
||||
item = input<DisplayOrderItem>();
|
||||
|
||||
orderType = computed(() => {
|
||||
const order = this.order();
|
||||
const features = order.features;
|
||||
return getOrderTypeFeature(features);
|
||||
return this.order().orderType;
|
||||
});
|
||||
|
||||
mappedBranch = computed(() => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { PrimaryCustomerCardResource } from '@isa/crm/data-access';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
import { filter, first } from 'rxjs/operators';
|
||||
import { debounceTime, filter } from 'rxjs/operators';
|
||||
import {
|
||||
PriceAndRedemptionPointsResource,
|
||||
ItemWithOrderType,
|
||||
@@ -177,15 +177,14 @@ export class RewardSelectionService {
|
||||
}
|
||||
|
||||
async reloadResources(): Promise<void> {
|
||||
// Start reloading all resources
|
||||
// Note: PrimaryCustomerCard, Price and redemption points will be loaded automatically by the effect
|
||||
// when selectionItemsWithOrderType changes after cart resources are reloaded
|
||||
this.#shoppingCartResource.reload();
|
||||
this.#rewardShoppingCartResource.reload();
|
||||
|
||||
// Wait until all resources are fully loaded (isLoading becomes false)
|
||||
await firstValueFrom(
|
||||
this.#isLoading$.pipe(filter((isLoading) => !isLoading)),
|
||||
this.#isLoading$.pipe(
|
||||
debounceTime(50),
|
||||
filter((isLoading) => !isLoading),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
data-which="print"
|
||||
class="self-start"
|
||||
(click)="print()"
|
||||
[pending]="printing()"
|
||||
[disabled]="printing()"
|
||||
uiInfoButton
|
||||
>
|
||||
<span uiInfoButtonLabel><ng-content></ng-content></span>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { OrderDestinationComponent } from './order-destination.component';
|
||||
import { OrderType } from '@isa/common/data-access';
|
||||
import { OrderTypeFeature } from '@isa/common/data-access';
|
||||
|
||||
describe('OrderDestinationComponent', () => {
|
||||
let component: OrderDestinationComponent;
|
||||
@@ -23,28 +23,28 @@ describe('OrderDestinationComponent', () => {
|
||||
});
|
||||
|
||||
it('should display delivery icon for delivery order type', () => {
|
||||
fixture.componentRef.setInput('orderType', OrderType.Delivery);
|
||||
fixture.componentRef.setInput('orderType', OrderTypeFeature.Delivery);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.destinationIcon()).toBe('isaDeliveryVersand');
|
||||
});
|
||||
|
||||
it('should display pickup icon for pickup order type', () => {
|
||||
fixture.componentRef.setInput('orderType', OrderType.Pickup);
|
||||
fixture.componentRef.setInput('orderType', OrderTypeFeature.Pickup);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.destinationIcon()).toBe('isaDeliveryRuecklage2');
|
||||
});
|
||||
|
||||
it('should display in-store icon for in-store order type', () => {
|
||||
fixture.componentRef.setInput('orderType', OrderType.InStore);
|
||||
fixture.componentRef.setInput('orderType', OrderTypeFeature.InStore);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.destinationIcon()).toBe('isaDeliveryRuecklage1');
|
||||
});
|
||||
|
||||
it('should display branch name when branch is provided', () => {
|
||||
fixture.componentRef.setInput('orderType', OrderType.Pickup);
|
||||
fixture.componentRef.setInput('orderType', OrderTypeFeature.Pickup);
|
||||
fixture.componentRef.setInput('branch', { name: 'Test Branch' });
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('OrderDestinationComponent', () => {
|
||||
});
|
||||
|
||||
it('should display shipping address name for delivery', () => {
|
||||
fixture.componentRef.setInput('orderType', OrderType.Delivery);
|
||||
fixture.componentRef.setInput('orderType', OrderTypeFeature.Delivery);
|
||||
fixture.componentRef.setInput('shippingAddress', {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
@@ -68,7 +68,7 @@ describe('OrderDestinationComponent', () => {
|
||||
city: 'Vienna',
|
||||
zipCode: '1010',
|
||||
};
|
||||
fixture.componentRef.setInput('orderType', OrderType.Delivery);
|
||||
fixture.componentRef.setInput('orderType', OrderTypeFeature.Delivery);
|
||||
fixture.componentRef.setInput('shippingAddress', {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
@@ -85,7 +85,7 @@ describe('OrderDestinationComponent', () => {
|
||||
city: 'Vienna',
|
||||
zipCode: '1020',
|
||||
};
|
||||
fixture.componentRef.setInput('orderType', OrderType.Pickup);
|
||||
fixture.componentRef.setInput('orderType', OrderTypeFeature.Pickup);
|
||||
fixture.componentRef.setInput('branch', {
|
||||
name: 'Test Branch',
|
||||
address: testAddress,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '@isa/icons';
|
||||
import { InlineAddressComponent } from '@isa/shared/address';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { OrderTypeFeature } from '@isa/common/data-access';
|
||||
import { OrderType, OrderTypeFeature } from '@isa/common/data-access';
|
||||
|
||||
export type Address = {
|
||||
apartment?: string;
|
||||
|
||||
Reference in New Issue
Block a user