Files
ISA-Frontend/libs/checkout/data-access/src/lib/services/shopping-cart.service.ts
Lorenz Hilpert 2d654aa63a Merged PR 1986: fix(checkout): add complete price structure for reward delivery orders
Commits Summary

  Commit 1: fix(checkout): add complete price structure for reward delivery orders

  File: libs/checkout/data-access/src/lib/services/shopping-cart.service.ts
  - Fixed incomplete price object when adding reward items to cart
  - Added currency (EUR) and currencySymbol (€) to price value
  - This resolves the API error: "Error converting value 0 to type PriceDTO"

  Commit 2: feat(checkout): enable delivery options for reward shop items

  Files:
  - libs/checkout/feature/reward-catalog/src/lib/reward-action/reward-action.component.ts
  - libs/checkout/feature/reward-shopping-cart/src/lib/reward-shopping-cart-item/reward-shopping-cart-item.component.ts
  - libs/checkout/feature/reward-order-confirmation/src/lib/reward-order-confirmation.component.ts

  Changes:
  - Re-enabled 'delivery' and 'dig-delivery' purchase options for reward items
  - Removed unused import
  - Applied code formatting

Related work items: #5405
2025-10-27 11:14:14 +00:00

398 lines
13 KiB
TypeScript

import { inject, Injectable } from '@angular/core';
import {
ItemsResult,
StoreCheckoutShoppingCartService,
ItemPayload,
AddToShoppingCartDTO,
UpdateShoppingCartItemDTO,
} from '@generated/swagger/checkout-api';
import {
AddItemToShoppingCartParams,
AddItemToShoppingCartParamsSchema,
CanAddItemsToShoppingCartParams,
CanAddItemsToShoppingCartParamsSchema,
RemoveShoppingCartItemParams,
RemoveShoppingCartItemParamsSchema,
UpdateShoppingCartItemParams,
UpdateShoppingCartItemParamsSchema,
} from '../schemas';
import { RewardSelectionItem, ShoppingCart, ShoppingCartItem } from '../models';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
import { logger } from '@isa/core/logging';
import { CheckoutMetadataService } from './checkout-metadata.service';
import { getItemKey } from '../helpers';
@Injectable({ providedIn: 'root' })
export class ShoppingCartService {
#logger = logger(() => ({ service: 'ShoppingCartService' }));
#storeCheckoutShoppingCartService = inject(StoreCheckoutShoppingCartService);
#checkoutMetadataService = inject(CheckoutMetadataService);
async createShoppingCart(): Promise<ShoppingCart> {
const req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartCreateShoppingCart();
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Failed to create shopping cart', err);
throw err;
}
return res.result as ShoppingCart;
}
async getShoppingCart(
shoppingCartId: number,
abortSignal?: AbortSignal,
): Promise<ShoppingCart | undefined> {
let req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartGetShoppingCart(
{
shoppingCartId,
},
);
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Failed to fetch shopping cart', err);
throw err;
}
return res.result as ShoppingCart;
}
async canAddItems(
params: CanAddItemsToShoppingCartParams,
): Promise<ItemsResult[]> {
const parsed = CanAddItemsToShoppingCartParamsSchema.parse(params);
const req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartCanAddItems(
{
shoppingCartId: parsed.shoppingCartId,
payload: parsed.payload as ItemPayload[],
},
);
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error(
'Failed to check if items can be added to shopping cart',
err,
);
throw err;
}
return res.result as unknown as ItemsResult[];
}
async addItem(params: AddItemToShoppingCartParams): Promise<ShoppingCart> {
const parsed = AddItemToShoppingCartParamsSchema.parse(params);
const req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartAddItemToShoppingCart(
{
shoppingCartId: parsed.shoppingCartId,
items: parsed.items as AddToShoppingCartDTO[],
},
);
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Failed to add item to shopping cart', err);
throw err;
}
return res.result as ShoppingCart;
}
async updateItem(
params: UpdateShoppingCartItemParams,
): Promise<ShoppingCart> {
console.log('UpdateShoppingCartItemParams', params);
const parsed = UpdateShoppingCartItemParamsSchema.parse(params);
const req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItem(
{
shoppingCartId: parsed.shoppingCartId,
shoppingCartItemId: parsed.shoppingCartItemId,
values: parsed.values as UpdateShoppingCartItemDTO,
},
);
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Failed to update shopping cart item', err);
throw err;
}
return res.result as ShoppingCart;
}
async removeItem(
params: RemoveShoppingCartItemParams,
): Promise<ShoppingCart> {
const parsed = RemoveShoppingCartItemParamsSchema.parse(params);
const req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItem(
{
shoppingCartId: parsed.shoppingCartId,
shoppingCartItemId: parsed.shoppingCartItemId,
values: {
quantity: 0,
},
},
);
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Failed to remove item from shopping cart', err);
throw err;
}
return res.result as ShoppingCart;
}
async completeRewardSelection({
tabId,
rewardSelectionItems,
}: {
tabId: number;
rewardSelectionItems: RewardSelectionItem[];
}) {
// Fetch or create both shopping cart IDs
const shoppingCartId = await this.#getOrCreateShoppingCartId(tabId);
const rewardShoppingCartId =
await this.#getOrCreateRewardShoppingCartId(tabId);
// Get Current Carts
const currentCart = await this.getShoppingCart(shoppingCartId);
const currentRewardCart = await this.getShoppingCart(rewardShoppingCartId);
// Get Current Items from Cart
const currentCartItems = currentCart?.items?.map((item) => item.data) ?? [];
const currentRewardCartItems =
currentRewardCart?.items?.map((item) => item.data) ?? [];
for (const selectionItem of rewardSelectionItems) {
// Search by Key needed because of different ShoppingCartItem IDs the items have in the different carts
const selectionItemKey = getItemKey(selectionItem.item);
const currentInCart = currentCartItems.find(
(item) => item && getItemKey(item) === selectionItemKey,
);
const currentInRewardCart = currentRewardCartItems.find(
(item) => item && getItemKey(item) === selectionItemKey,
);
// Handle regular cart
await this.#handleCart({
shoppingCartId,
itemId: currentInCart?.id,
currentCartItem: currentInCart,
rewardSelectionItem: selectionItem,
});
// Handle reward cart
await this.#handleRewardCart({
rewardShoppingCartId,
itemId: currentInRewardCart?.id,
currentRewardCartItem: currentInRewardCart,
rewardSelectionItem: selectionItem,
});
}
}
async #handleCart({
shoppingCartId,
itemId,
currentCartItem,
rewardSelectionItem,
}: {
shoppingCartId: number;
itemId: number | undefined;
currentCartItem: ShoppingCartItem | undefined;
rewardSelectionItem: RewardSelectionItem;
}) {
const desiredCartQuantity = rewardSelectionItem.cartQuantity;
if (currentCartItem && itemId) {
const currentQuantity = currentCartItem.quantity;
if (desiredCartQuantity !== currentQuantity) {
if (desiredCartQuantity === 0) {
this.#logger.info('Removing item from regular cart', () => ({
shoppingCartId,
itemId,
currentQuantity,
}));
await this.removeItem({
shoppingCartId,
shoppingCartItemId: itemId,
});
} else {
this.#logger.info('Updating item quantity in regular cart', () => ({
shoppingCartId,
itemId,
currentQuantity,
desiredCartQuantity,
}));
await this.updateItem({
shoppingCartId,
shoppingCartItemId: itemId,
values: { quantity: desiredCartQuantity },
});
}
}
} else if (desiredCartQuantity > 0) {
this.#logger.info('Adding item to regular cart', () => ({
shoppingCartId,
itemId,
desiredCartQuantity,
productNumber: rewardSelectionItem?.item?.product?.catalogProductNumber,
}));
await this.addItem({
shoppingCartId,
items: [
{
destination: rewardSelectionItem.item.destination,
product: {
...rewardSelectionItem.item.product,
catalogProductNumber:
rewardSelectionItem?.item?.product?.catalogProductNumber ??
String(rewardSelectionItem?.item?.id),
},
availability: {
...rewardSelectionItem.item.availability,
price:
rewardSelectionItem?.availabilityPrice ??
rewardSelectionItem?.catalogPrice,
},
promotion: rewardSelectionItem?.item?.promotion,
quantity: desiredCartQuantity,
loyalty: undefined,
},
],
});
}
}
async #handleRewardCart({
rewardShoppingCartId,
itemId,
currentRewardCartItem,
rewardSelectionItem,
}: {
rewardShoppingCartId: number;
itemId: number | undefined;
currentRewardCartItem: ShoppingCartItem | undefined;
rewardSelectionItem: RewardSelectionItem;
}) {
const desiredRewardCartQuantity = rewardSelectionItem.rewardCartQuantity;
if (currentRewardCartItem && itemId) {
const currentQuantity = currentRewardCartItem.quantity;
if (desiredRewardCartQuantity !== currentQuantity) {
if (desiredRewardCartQuantity === 0) {
this.#logger.info('Removing item from reward cart', () => ({
rewardShoppingCartId,
itemId,
currentQuantity,
}));
await this.removeItem({
shoppingCartId: rewardShoppingCartId,
shoppingCartItemId: itemId,
});
} else {
this.#logger.info('Updating item quantity in reward cart', () => ({
rewardShoppingCartId,
itemId,
currentQuantity,
desiredRewardCartQuantity,
}));
await this.updateItem({
shoppingCartId: rewardShoppingCartId,
shoppingCartItemId: itemId,
values: { quantity: desiredRewardCartQuantity },
});
}
}
} else if (desiredRewardCartQuantity > 0) {
this.#logger.info('Adding item to reward cart', () => ({
rewardShoppingCartId,
itemId,
desiredRewardCartQuantity,
rewardPoints: rewardSelectionItem.catalogRewardPoints,
productNumber: rewardSelectionItem?.item?.product?.catalogProductNumber,
}));
await this.addItem({
shoppingCartId: rewardShoppingCartId,
items: [
{
destination: rewardSelectionItem.item.destination,
product: {
...rewardSelectionItem.item.product,
catalogProductNumber:
rewardSelectionItem?.item?.product?.catalogProductNumber ??
String(rewardSelectionItem?.item?.id),
},
quantity: desiredRewardCartQuantity,
promotion: undefined, // If loyalty is set, we need to remove promotion
loyalty: { value: rewardSelectionItem.catalogRewardPoints }, // Set loyalty points from item
availability: {
...rewardSelectionItem.item.availability,
price: {
value: {
value: 0,
currency: 'EUR',
currencySymbol: '€',
},
vat: undefined,
},
},
},
],
});
}
}
async #getOrCreateShoppingCartId(tabId: number): Promise<number> {
const shoppingCartId =
this.#checkoutMetadataService.getShoppingCartId(tabId);
if (shoppingCartId) {
return shoppingCartId;
}
const shoppingCart = await this.createShoppingCart();
this.#checkoutMetadataService.setShoppingCartId(tabId!, shoppingCart.id);
return shoppingCart.id!;
}
async #getOrCreateRewardShoppingCartId(tabId: number): Promise<number> {
const rewardShoppingCartId =
this.#checkoutMetadataService.getRewardShoppingCartId(tabId);
if (rewardShoppingCartId) {
return rewardShoppingCartId;
}
const shoppingCart = await this.createShoppingCart();
this.#checkoutMetadataService.setRewardShoppingCartId(
tabId!,
shoppingCart.id,
);
return shoppingCart.id!;
}
}