feature(libs-feature-data-access): Using catchResponseArgsErrorPipe across all data-acesss services now and removed old patterns. Adjusted some tests and added Logging for every case wrapped in try catch patterns

Ref: #5340
This commit is contained in:
Nino
2025-12-15 17:48:43 +01:00
parent 3101e8e8e0
commit 3d82e7f0af
23 changed files with 923 additions and 861 deletions

View File

@@ -1,6 +1,5 @@
import { inject, Injectable } from '@angular/core';
import { AvailabilityService as GeneratedAvailabilityService } from '@generated/swagger/availability-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import { LogisticianService, Logistician } from '@isa/oms/data-access';
import { logger } from '@isa/core/logging';
// TODO: [Next Sprint - Architectural] Abstract cross-domain dependency

View File

@@ -1,6 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { StoreCheckoutBranchService } from '@generated/swagger/checkout-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
import { Cache, CacheTimeToLive, InFlight } from '@isa/common/decorators';
@@ -14,20 +17,20 @@ export class BranchService {
@Cache({ ttl: CacheTimeToLive.fiveMinutes })
@InFlight()
async fetchBranches(abortSignal?: AbortSignal): Promise<Branch[]> {
let req$ = this.#branchService.StoreCheckoutBranchGetBranches({});
let req$ = this.#branchService
.StoreCheckoutBranchGetBranches({})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
try {
const res = await firstValueFrom(req$);
return res.result as Branch[];
} catch (error) {
this.#logger.error('Failed to fetch branches', error);
throw error;
}
return res.result as Branch[];
}
}

View File

@@ -4,7 +4,7 @@ import {
PrintOrderConfirmation,
PrintOrderConfirmationSchema,
} from '../schemas';
import { ResponseArgsError } from '@isa/common/data-access';
import { catchResponseArgsErrorPipe } from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
import { logger } from '@isa/core/logging';
@@ -18,16 +18,19 @@ export class CheckoutPrintService {
): Promise<ResponseArgs> {
const parsed = PrintOrderConfirmationSchema.parse(params);
const req$ = this.#omsPrintService.OMSPrintAbholscheinById(parsed);
const req$ = this.#omsPrintService
.OMSPrintAbholscheinById(parsed)
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Failed to print order confirmation', err);
throw err;
try {
const res = await firstValueFrom(req$);
return res;
} catch (error) {
this.#logger.error('Failed to print order confirmation', error, () => ({
printer: parsed.printer,
orderIds: parsed.data,
}));
throw error;
}
return res;
}
}

View File

@@ -9,7 +9,10 @@ import {
DestinationDTO,
} from '@generated/swagger/checkout-api';
import { EntityContainer, ResponseArgsError } from '@isa/common/data-access';
import {
EntityContainer,
catchResponseArgsErrorPipe,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
import { ShoppingCartService } from './shopping-cart.service';
@@ -315,19 +318,21 @@ export class CheckoutService {
shoppingCartId: number,
customerFeatures: Record<string, string>,
): Promise<void> {
const req$ =
this.#shoppingCartService.StoreCheckoutShoppingCartSetLogisticianOnDestinationsByBuyer(
{
shoppingCartId,
payload: { customerFeatures },
},
const req$ = this.#shoppingCartService
.StoreCheckoutShoppingCartSetLogisticianOnDestinationsByBuyer({
shoppingCartId,
payload: { customerFeatures },
})
.pipe(catchResponseArgsErrorPipe());
try {
await firstValueFrom(req$);
} catch (error) {
this.#logger.error(
'Failed to update destinations for customer',
error,
() => ({ shoppingCartId }),
);
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to update destinations for customer', error);
throw error;
}
}
@@ -345,20 +350,24 @@ export class CheckoutService {
items.map(async (item) => {
if (!item.id) return;
const req$ =
this.#shoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItem(
{
const req$ = this.#shoppingCartService
.StoreCheckoutShoppingCartUpdateShoppingCartItem({
shoppingCartId,
shoppingCartItemId: item.id,
values: { specialComment: comment },
})
.pipe(catchResponseArgsErrorPipe());
try {
await firstValueFrom(req$);
} catch (error) {
this.#logger.error(
'Failed to set special comment on item',
error,
() => ({
shoppingCartId,
shoppingCartItemId: item.id,
values: { specialComment: comment },
},
}),
);
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to set special comment on item');
throw error;
}
}),
@@ -369,20 +378,21 @@ export class CheckoutService {
* Refreshes checkout to get latest state.
*/
private async refreshCheckout(shoppingCartId: number): Promise<Checkout> {
const req$ =
this.#storeCheckoutService.StoreCheckoutCreateOrRefreshCheckout({
const req$ = this.#storeCheckoutService
.StoreCheckoutCreateOrRefreshCheckout({
shoppingCartId,
});
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to refresh checkout', error);
try {
const res = await firstValueFrom(req$);
return res.result as Checkout;
} catch (error) {
this.#logger.error('Failed to refresh checkout', error, () => ({
shoppingCartId,
}));
throw error;
}
return res.result as Checkout;
}
/**
@@ -579,20 +589,22 @@ export class CheckoutService {
checkoutId: number,
buyerDTO: Buyer,
): Promise<Checkout> {
const req$ = this.#buyerService.StoreCheckoutBuyerSetBuyerPOST({
checkoutId,
buyerDTO,
});
const req$ = this.#buyerService
.StoreCheckoutBuyerSetBuyerPOST({
checkoutId,
buyerDTO,
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to set buyer', error);
try {
const res = await firstValueFrom(req$);
return res.result as Checkout;
} catch (error) {
this.#logger.error('Failed to set buyer on checkout', error, () => ({
checkoutId,
}));
throw error;
}
return res.result as Checkout;
}
/**
@@ -602,20 +614,22 @@ export class CheckoutService {
checkoutId: number,
payer: Payer,
): Promise<Checkout> {
const req$ = this.#payerService.StoreCheckoutPayerSetPayerPOST({
checkoutId,
payerDTO: payer,
});
const req$ = this.#payerService
.StoreCheckoutPayerSetPayerPOST({
checkoutId,
payerDTO: payer,
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to set payer', error);
try {
const res = await firstValueFrom(req$);
return res.result as Checkout;
} catch (error) {
this.#logger.error('Failed to set payer on checkout', error, () => ({
checkoutId,
}));
throw error;
}
return res.result as Checkout;
}
/**
@@ -625,21 +639,24 @@ export class CheckoutService {
checkoutId: number,
channels: NotificationChannel,
): Promise<Checkout> {
const req$ =
this.#storeCheckoutService.StoreCheckoutSetNotificationChannels({
const req$ = this.#storeCheckoutService
.StoreCheckoutSetNotificationChannels({
checkoutId,
notificationChannel: channels,
});
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to set notification channels', error);
try {
const res = await firstValueFrom(req$);
return res.result as Checkout;
} catch (error) {
this.#logger.error(
'Failed to set notification channels on checkout',
error,
() => ({ checkoutId }),
);
throw error;
}
return res.result as Checkout;
}
/**
@@ -649,20 +666,24 @@ export class CheckoutService {
checkoutId: number,
paymentType: PaymentType,
): Promise<Checkout> {
const req$ = this.#paymentService.StoreCheckoutPaymentSetPaymentType({
checkoutId,
paymentType: paymentType,
});
const req$ = this.#paymentService
.StoreCheckoutPaymentSetPaymentType({
checkoutId,
paymentType: paymentType,
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to set payment type', error);
try {
const res = await firstValueFrom(req$);
return res.result as Checkout;
} catch (error) {
this.#logger.error(
'Failed to set payment type on checkout',
error,
() => ({ checkoutId, paymentType }),
);
throw error;
}
return res.result as Checkout;
}
/**
@@ -691,17 +712,21 @@ export class CheckoutService {
shippingAddress: { ...shippingAddress },
};
const req$ = this.#storeCheckoutService.StoreCheckoutUpdateDestination({
checkoutId,
destinationId: dest.id,
destinationDTO: updatedDestination,
});
const req$ = this.#storeCheckoutService
.StoreCheckoutUpdateDestination({
checkoutId,
destinationId: dest.id,
destinationDTO: updatedDestination,
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to update destination');
try {
await firstValueFrom(req$);
} catch (error) {
this.#logger.error('Failed to update destination', error, () => ({
checkoutId,
destinationId: dest.id,
}));
throw error;
}
}),

View File

@@ -18,7 +18,7 @@ import {
} from '../schemas';
import { RewardSelectionItem, ShoppingCart, ShoppingCartItem } from '../models';
import {
ResponseArgsError,
catchResponseArgsErrorPipe,
takeUntilAborted,
ensureCurrencyDefaults,
} from '@isa/common/data-access';
@@ -36,107 +36,105 @@ export class ShoppingCartService {
#checkoutMetadataService = inject(CheckoutMetadataService);
async createShoppingCart(): Promise<ShoppingCart> {
const req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartCreateShoppingCart();
const req$ = this.#storeCheckoutShoppingCartService
.StoreCheckoutShoppingCartCreateShoppingCart()
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Failed to create shopping cart', err);
throw err;
this.#shoppingCartStream.pub(
ShoppingCartEvent.Created,
res.result as ShoppingCart,
'ShoppingCartService',
);
return res.result as ShoppingCart;
} catch (error) {
this.#logger.error('Failed to create shopping cart', error);
throw error;
}
this.#shoppingCartStream.pub(
ShoppingCartEvent.Created,
res.result as ShoppingCart,
'ShoppingCartService',
);
return res.result as ShoppingCart;
}
async getShoppingCart(
shoppingCartId: number,
abortSignal?: AbortSignal,
): Promise<ShoppingCart | undefined> {
let req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartGetShoppingCart(
{
shoppingCartId,
},
);
let req$ = this.#storeCheckoutShoppingCartService
.StoreCheckoutShoppingCartGetShoppingCart({
shoppingCartId,
})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Failed to fetch shopping cart', err);
throw err;
this.#shoppingCartStream.pub(
ShoppingCartEvent.ItemUpdated,
res.result as ShoppingCart,
'ShoppingCartService',
);
return res.result as ShoppingCart;
} catch (error) {
this.#logger.error('Failed to get shopping cart', error, () => ({
shoppingCartId,
}));
throw error;
}
this.#shoppingCartStream.pub(
ShoppingCartEvent.ItemUpdated,
res.result as ShoppingCart,
'ShoppingCartService',
);
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 req$ = this.#storeCheckoutShoppingCartService
.StoreCheckoutShoppingCartCanAddItems({
shoppingCartId: parsed.shoppingCartId,
payload: parsed.payload as ItemPayload[],
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
try {
const res = await firstValueFrom(req$);
return res.result as unknown as ItemsResult[];
} catch (error) {
this.#logger.error(
'Failed to check if items can be added to shopping cart',
err,
'Failed to check if items can be added',
error,
() => ({ shoppingCartId: parsed.shoppingCartId }),
);
throw err;
throw error;
}
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 req$ = this.#storeCheckoutShoppingCartService
.StoreCheckoutShoppingCartAddItemToShoppingCart({
shoppingCartId: parsed.shoppingCartId,
items: parsed.items as AddToShoppingCartDTO[],
})
.pipe(catchResponseArgsErrorPipe());
try {
const res = await firstValueFrom(req$);
this.#shoppingCartStream.pub(
ShoppingCartEvent.ItemAdded,
res.result as ShoppingCart,
'ShoppingCartService',
);
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;
} catch (error) {
this.#logger.error('Failed to add item to shopping cart', error, () => ({
shoppingCartId: parsed.shoppingCartId,
}));
throw error;
}
this.#shoppingCartStream.pub(
ShoppingCartEvent.ItemAdded,
res.result as ShoppingCart,
'ShoppingCartService',
);
return res.result as ShoppingCart;
}
async updateItem(
@@ -144,60 +142,62 @@ export class ShoppingCartService {
): Promise<ShoppingCart> {
const parsed = UpdateShoppingCartItemParamsSchema.parse(params);
const req$ =
this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItem(
{
shoppingCartId: parsed.shoppingCartId,
shoppingCartItemId: parsed.shoppingCartItemId,
values: parsed.values as UpdateShoppingCartItemDTO,
},
const req$ = this.#storeCheckoutShoppingCartService
.StoreCheckoutShoppingCartUpdateShoppingCartItem({
shoppingCartId: parsed.shoppingCartId,
shoppingCartItemId: parsed.shoppingCartItemId,
values: parsed.values as UpdateShoppingCartItemDTO,
})
.pipe(catchResponseArgsErrorPipe());
try {
const res = await firstValueFrom(req$);
this.#shoppingCartStream.pub(
ShoppingCartEvent.ItemUpdated,
res.result as ShoppingCart,
'ShoppingCartService',
);
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;
} catch (error) {
this.#logger.error('Failed to update shopping cart item', error, () => ({
shoppingCartId: parsed.shoppingCartId,
shoppingCartItemId: parsed.shoppingCartItemId,
}));
throw error;
}
this.#shoppingCartStream.pub(
ShoppingCartEvent.ItemUpdated,
res.result as ShoppingCart,
'ShoppingCartService',
);
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 req$ = this.#storeCheckoutShoppingCartService
.StoreCheckoutShoppingCartUpdateShoppingCartItem({
shoppingCartId: parsed.shoppingCartId,
shoppingCartItemId: parsed.shoppingCartItemId,
values: {
quantity: 0,
},
})
.pipe(catchResponseArgsErrorPipe());
try {
const res = await firstValueFrom(req$);
this.#shoppingCartStream.pub(
ShoppingCartEvent.ItemRemoved,
res.result as ShoppingCart,
'ShoppingCartService',
);
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;
} catch (error) {
this.#logger.error('Failed to remove shopping cart item', error, () => ({
shoppingCartId: parsed.shoppingCartId,
shoppingCartItemId: parsed.shoppingCartItemId,
}));
throw error;
}
this.#shoppingCartStream.pub(
ShoppingCartEvent.ItemRemoved,
res.result as ShoppingCart,
'ShoppingCartService',
);
return res.result as ShoppingCart;
}
// TODO: Code Kommentieren + Beschreiben

View File

@@ -87,7 +87,8 @@ describe('SupplierService', () => {
// Arrange
const errorResponse = {
result: null,
error: { message: 'API Error', code: 500 },
error: true,
message: 'API Error',
};
mockSupplierService.StoreCheckoutSupplierGetSuppliers.mockReturnValue(

View File

@@ -1,6 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { StoreCheckoutSupplierService } from '@generated/swagger/checkout-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
import { Cache, CacheTimeToLive, InFlight } from '@isa/common/decorators';
@@ -32,37 +35,42 @@ export class SupplierService {
async getTakeAwaySupplier(abortSignal?: AbortSignal): Promise<Supplier> {
this.#logger.debug('Fetching take away supplier');
let req$ = this.#supplierService.StoreCheckoutSupplierGetSuppliers({});
let req$ = this.#supplierService
.StoreCheckoutSupplierGetSuppliers({})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to fetch suppliers', error);
const takeAwaySupplier = res.result?.find(
(supplier) => supplier.supplierNumber === 'F',
);
if (!takeAwaySupplier) {
const notFoundError = new Error('Take away supplier (F) not found');
this.#logger.error(
'Take away supplier not found',
notFoundError,
() => ({
availableSuppliers: res.result?.map((s) => s.supplierNumber),
}),
);
throw notFoundError;
}
this.#logger.debug('Take away supplier fetched', () => ({
supplierId: takeAwaySupplier.id,
supplierNumber: takeAwaySupplier.supplierNumber,
}));
return takeAwaySupplier;
} catch (error) {
this.#logger.error('Failed to fetch take away supplier', error);
throw error;
}
const takeAwaySupplier = res.result?.find(
(supplier) => supplier.supplierNumber === 'F',
);
if (!takeAwaySupplier) {
const notFoundError = new Error('Take away supplier (F) not found');
this.#logger.error('Take away supplier not found', notFoundError, () => ({
availableSuppliers: res.result?.map((s) => s.supplierNumber),
}));
throw notFoundError;
}
this.#logger.debug('Take away supplier fetched', () => ({
supplierId: takeAwaySupplier.id,
supplierNumber: takeAwaySupplier.supplierNumber,
}));
return takeAwaySupplier;
}
}

View File

@@ -1,17 +1,18 @@
import { CountryService as ApiCountryService } from '@generated/swagger/crm-api';
import { Cache, CacheTimeToLive } from '@isa/common/decorators';
import { Cache } from '@isa/common/decorators';
import { inject, Injectable } from '@angular/core';
import { Country } from '../models';
import {
catchResponseArgsErrorPipe,
ResponseArgs,
takeUntilAborted,
} from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
import { logger } from '@isa/core/logging';
@Injectable({ providedIn: 'root' })
export class CountryService {
#apiCountryService = inject(ApiCountryService);
#logger = logger(() => ({ service: 'CountryService' }));
@Cache()
async getCountries(abortSignal?: AbortSignal): Promise<Country[]> {
@@ -23,8 +24,12 @@ export class CountryService {
req$ = req$.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
return res.result as Country[];
try {
const res = await firstValueFrom(req$);
return res.result as Country[];
} catch (error) {
this.#logger.error('Failed to get countries', error);
throw error;
}
}
}

View File

@@ -30,7 +30,6 @@ import {
import {
catchResponseArgsErrorPipe,
ResponseArgs,
ResponseArgsError,
takeUntilAborted,
} from '@isa/common/data-access';
import { Cache, CacheTimeToLive } from '@isa/common/decorators';
@@ -161,8 +160,15 @@ export class CrmSearchService {
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
return res?.result;
try {
const res = await firstValueFrom(req$);
return res?.result;
} catch (error) {
this.#logger.error('Error adding customer card', error, () => ({
customerId: parsed.customerId,
}));
throw error;
}
}
async lockCard(params: LockCardInput): Promise<boolean | undefined> {
@@ -176,15 +182,15 @@ export class CrmSearchService {
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Lock card Failed', err);
throw err;
try {
const res = await firstValueFrom(req$);
return res?.result;
} catch (error) {
this.#logger.error('Error locking customer card', error, () => ({
cardCode: parsed.cardCode,
}));
throw error;
}
return res?.result;
}
async unlockCard(params: UnlockCardInput): Promise<boolean | undefined> {
@@ -199,15 +205,16 @@ export class CrmSearchService {
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Unlock card Failed', err);
throw err;
try {
const res = await firstValueFrom(req$);
return res?.result;
} catch (error) {
this.#logger.error('Error unlocking customer card', error, () => ({
customerId: parsed.customerId,
cardCode: parsed.cardCode,
}));
throw error;
}
return res?.result;
}
@Cache({ ttl: CacheTimeToLive.oneHour })
@@ -224,10 +231,14 @@ export class CrmSearchService {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully fetched current booking partner store');
return res?.result;
try {
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully fetched current booking partner store');
return res?.result;
} catch (error) {
this.#logger.error('Error fetching current booking partner store', error);
throw error;
}
}
async addBooking(
@@ -245,8 +256,16 @@ export class CrmSearchService {
},
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
return res?.result;
try {
const res = await firstValueFrom(req$);
return res?.result;
} catch (error) {
this.#logger.error('Error adding booking', error, () => ({
cardCode: parsed.cardCode,
}));
throw error;
}
}
/**
@@ -273,16 +292,12 @@ export class CrmSearchService {
try {
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully checked Bon');
if (res.error) {
const err = new ResponseArgsError(res);
this.#logger.error('Bon check failed', err);
throw err;
}
return res as ResponseArgs<LoyaltyBonResponse>;
} catch (error) {
this.#logger.error('Error checking Bon', error);
this.#logger.error('Error checking Bon', error, () => ({
cardCode,
bonNr,
}));
throw error;
}
}
@@ -301,8 +316,16 @@ export class CrmSearchService {
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully redeemed Bon');
return res?.result ?? false;
try {
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully redeemed Bon');
return res?.result ?? false;
} catch (error) {
this.#logger.error('Error redeeming Bon', error, () => ({
cardCode,
bonNr,
}));
throw error;
}
}
}

View File

@@ -9,7 +9,10 @@ import {
import { logger } from '@isa/core/logging';
import { ReceiptService } from '@generated/swagger/oms-api';
import { firstValueFrom } from 'rxjs';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { Receipt } from '../models';
@Injectable()
@@ -74,29 +77,31 @@ export class HandleCommandService {
parsed,
}));
let req$ = this.#receiptService.ReceiptGetReceiptsByOrderItemSubset({
payload: parsed, // Payload Default from old Implementation, eagerLoading: 1 and receiptType: (1 + 64 + 128) set as Schema default
});
let req$ = this.#receiptService
.ReceiptGetReceiptsByOrderItemSubset({
payload: parsed, // Payload Default from old Implementation, eagerLoading: 1 and receiptType: (1 + 64 + 128) set as Schema default
})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error) {
const err = new ResponseArgsError(res);
// Mapping Logic from old implementation
const mappedReceipts =
res?.result?.map((r) => r.item3?.data).filter((f) => !!f) ?? [];
return mappedReceipts as Receipt[];
} catch (error) {
this.#logger.error(
'Failed to fetch receipts by order item subset IDs',
err,
error,
() => ({ ids: parsed.ids }),
);
throw err;
throw error;
}
// Mapping Logic from old implementation
const mappedReceipts =
res?.result?.map((r) => r.item3?.data).filter((f) => !!f) ?? [];
return mappedReceipts as Receipt[];
}
}

View File

@@ -1,6 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { LogisticianService as GeneratedLogisticianService } from '@generated/swagger/oms-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
import { Cache, CacheTimeToLive, InFlight } from '@isa/common/decorators';
@@ -32,37 +35,38 @@ export class LogisticianService {
async getLogistician2470(abortSignal?: AbortSignal): Promise<Logistician> {
this.#logger.debug('Fetching logistician 2470');
let req$ = this.#logisticianService.LogisticianGetLogisticians({});
let req$ = this.#logisticianService
.LogisticianGetLogisticians({})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error || !res.result) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to fetch logisticians', error);
const logistician = res.result?.find(
(l) => l.logisticianNumber === '2470',
);
if (!logistician) {
const notFoundError = new Error('Logistician 2470 not found');
this.#logger.error('Logistician 2470 not found', notFoundError, () => ({
availableLogisticians: res.result?.map((l) => l.logisticianNumber),
}));
throw notFoundError;
}
this.#logger.debug('Logistician 2470 fetched', () => ({
logisticianId: logistician.id,
logisticianNumber: logistician.logisticianNumber,
}));
return logistician;
} catch (error) {
this.#logger.error('Failed to fetch logistician 2470', error);
throw error;
}
const logistician = res.result.find(
(l) => l.logisticianNumber === '2470',
);
if (!logistician) {
const notFoundError = new Error('Logistician 2470 not found');
this.#logger.error('Logistician 2470 not found', notFoundError, () => ({
availableLogisticians: res.result?.map((l) => l.logisticianNumber),
}));
throw notFoundError;
}
this.#logger.debug('Logistician 2470 fetched', () => ({
logisticianId: logistician.id,
logisticianNumber: logistician.logisticianNumber,
}));
return logistician;
}
}

View File

@@ -4,7 +4,10 @@ import {
DBHOrderItemListItemDTO,
QueryTokenDTO,
} from '@generated/swagger/oms-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
@@ -49,26 +52,26 @@ export class OpenRewardTasksService {
orderBy: [],
};
let req$ = this.#abholfachService.AbholfachWarenausgabe(payload);
let req$ = this.#abholfachService
.AbholfachWarenausgabe(payload)
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
const tasks = res.result ?? [];
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.debug('Open reward tasks fetched', () => ({
taskCount: tasks.length,
}));
return tasks;
} catch (error) {
this.#logger.error('Failed to fetch open reward tasks', error);
throw error;
}
const tasks = res.result ?? [];
this.#logger.debug('Open reward tasks fetched', () => ({
taskCount: tasks.length,
}));
return tasks;
}
}

View File

@@ -4,7 +4,10 @@ import {
LogisticianService,
LogisticianDTO,
} from '@generated/swagger/oms-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
import { DisplayOrder } from '../models';
@@ -36,19 +39,23 @@ export class OrderCreationService {
throw new Error(`Invalid checkoutId: ${checkoutId}`);
}
const req$ = this.#orderCheckoutService.OrderCheckoutCreateOrderPOST({
checkoutId,
});
const req$ = this.#orderCheckoutService
.OrderCheckoutCreateOrderPOST({
checkoutId,
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to create orders', error);
try {
const res = await firstValueFrom(req$);
return res.result as DisplayOrder[];
} catch (error) {
this.#logger.error(
'Failed to create orders from checkout',
error,
() => ({ checkoutId }),
);
throw error;
}
return res.result as DisplayOrder[];
}
/**
@@ -63,25 +70,29 @@ export class OrderCreationService {
logisticianNumber = '2470',
abortSignal?: AbortSignal,
): Promise<LogisticianDTO> {
let req$ = this.#logisticianService.LogisticianGetLogisticians({});
let req$ = this.#logisticianService
.LogisticianGetLogisticians({})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) req$ = req$.pipe(takeUntilAborted(abortSignal));
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to get logistician', error);
const logistician = res.result?.find(
(l) => l.logisticianNumber === logisticianNumber,
);
if (!logistician) {
throw new Error(`Logistician ${logisticianNumber} not found`);
}
return logistician;
} catch (error) {
this.#logger.error('Failed to get logistician', error, () => ({
logisticianNumber,
}));
throw error;
}
const logistician = res.result?.find(
(l) => l.logisticianNumber === logisticianNumber,
);
if (!logistician) {
throw new Error(`Logistician ${logisticianNumber} not found`);
}
return logistician;
}
}

View File

@@ -1,6 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { OrderService } from '@generated/swagger/oms-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
import {
@@ -25,25 +28,28 @@ export class OrderRewardCollectService {
throw error;
}
const req$ = this.#orderService.OrderLoyaltyCollect({
orderId: params.orderId,
orderItemId: params.orderItemId,
orderItemSubsetId: params.orderItemSubsetId,
data: {
collectType: params.collectType,
quantity: params.quantity,
},
});
const req$ = this.#orderService
.OrderLoyaltyCollect({
orderId: params.orderId,
orderItemId: params.orderItemId,
orderItemSubsetId: params.orderItemSubsetId,
data: {
collectType: params.collectType,
quantity: params.quantity,
},
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to collect reward item', error);
try {
const res = await firstValueFrom(req$);
return res.result as DBHOrderItemListItem[];
} catch (error) {
this.#logger.error('Failed to collect order reward', error, () => ({
orderId: params.orderId,
orderItemSubsetId: params.orderItemSubsetId,
}));
throw error;
}
return res.result as DBHOrderItemListItem[];
}
async fetchOrderItemSubset(
@@ -57,22 +63,22 @@ export class OrderRewardCollectService {
throw error;
}
let req$ = this.#orderService.OrderGetOrderItemSubset(
params.orderItemSubsetId,
);
let req$ = this.#orderService
.OrderGetOrderItemSubset(params.orderItemSubsetId)
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to fetch order item subset', error);
try {
const res = await firstValueFrom(req$);
return res.result as DisplayOrderItemSubset;
} catch (error) {
this.#logger.error('Failed to fetch order item subset', error, () => ({
orderItemSubsetId: params.orderItemSubsetId,
}));
throw error;
}
return res.result as DisplayOrderItemSubset;
}
}

View File

@@ -4,7 +4,10 @@ import {
OrderDTO,
DisplayOrderDTO,
} from '@generated/swagger/oms-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
@@ -20,21 +23,23 @@ export class OrdersService {
orderId: number,
abortSignal?: AbortSignal,
): Promise<OrderDTO | null> {
let req$ = this.#orderService.OrderGetOrder(orderId);
let req$ = this.#orderService
.OrderGetOrder(orderId)
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to fetch order', { orderId, error });
try {
const res = await firstValueFrom(req$);
return res.result ?? null;
} catch (error) {
this.#logger.error('Failed to get order', error, () => ({
orderId,
}));
throw error;
}
return res.result ?? null;
}
/**
@@ -66,21 +71,23 @@ export class OrdersService {
orderId: number,
abortSignal?: AbortSignal,
): Promise<DisplayOrderDTO | null> {
let req$ = this.#orderService.OrderGetDisplayOrder(orderId);
let req$ = this.#orderService
.OrderGetDisplayOrder(orderId)
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to fetch display order', { orderId, error });
try {
const res = await firstValueFrom(req$);
return res.result ?? null;
} catch (error) {
this.#logger.error('Failed to get display order', error, () => ({
orderId,
}));
throw error;
}
return res.result ?? null;
}
/**

View File

@@ -1,6 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { StockService, BranchDTO } from '@generated/swagger/inventory-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs';
@@ -27,41 +30,42 @@ export class BranchService {
* @throws {Error} If branch retrieval fails
*/
async getDefaultBranch(abortSignal?: AbortSignal): Promise<BranchDTO> {
let req$ = this.#stockService.StockCurrentBranch();
let req$ = this.#stockService
.StockCurrentBranch()
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) req$ = req$.pipe(takeUntilAborted(abortSignal));
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
const branch = res.result;
if (res.error) {
const error = new ResponseArgsError(res);
if (!branch) {
const error = new Error('No branch data returned');
this.#logger.error('Failed to get default branch', error);
throw error;
}
return {
id: branch.id,
name: branch.name,
address: branch.address,
branchType: branch.branchType,
branchNumber: branch.branchNumber,
changed: branch.changed,
created: branch.created,
isDefault: branch.isDefault,
isOnline: branch.isOnline,
key: branch.key,
label: branch.label,
pId: branch.pId,
shortName: branch.shortName,
status: branch.status,
version: branch.version,
};
} catch (error) {
this.#logger.error('Failed to get default branch', error);
throw error;
}
const branch = res.result;
if (!branch) {
const error = new Error('No branch data returned');
this.#logger.error('Failed to get default branch', error);
throw error;
}
return {
id: branch.id,
name: branch.name,
address: branch.address,
branchType: branch.branchType,
branchNumber: branch.branchNumber,
changed: branch.changed,
created: branch.created,
isDefault: branch.isDefault,
isOnline: branch.isOnline,
key: branch.key,
label: branch.label,
pId: branch.pId,
shortName: branch.shortName,
status: branch.status,
version: branch.version,
};
}
}

View File

@@ -4,7 +4,10 @@ import { KeyValueStringAndString } from '../models';
import { firstValueFrom } from 'rxjs';
import { logger } from '@isa/core/logging';
import { RemissionStockService } from './remission-stock.service';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { InFlight, Cache, CacheTimeToLive } from '@isa/common/decorators';
/**
@@ -65,26 +68,29 @@ export class RemissionProductGroupService {
stockId: assignedStock.id,
}));
let req$ = this.#remiService.RemiProductgroups({
stockId: assignedStock.id,
});
let req$ = this.#remiService
.RemiProductgroups({
stockId: assignedStock.id,
})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error || !res.result) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to fetch product groups', error);
this.#logger.debug('Successfully fetched product groups', () => ({
groupCount: res.result?.length || 0,
}));
return res.result as KeyValueStringAndString[];
} catch (error) {
this.#logger.error('Failed to fetch product groups', error, () => ({
stockId: assignedStock.id,
}));
throw error;
}
this.#logger.debug('Successfully fetched product groups', () => ({
groupCount: res.result?.length || 0,
}));
return res.result as KeyValueStringAndString[];
}
}

View File

@@ -1,6 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { ReturnService } from '@generated/swagger/inventory-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
import { KeyValueStringAndString } from '../models';
import { logger } from '@isa/core/logging';
@@ -74,29 +77,30 @@ export class RemissionReasonService {
stockId: assignedStock?.id,
}));
let req$ = this.#returnService.ReturnGetReturnReasons({
stockId: assignedStock?.id,
});
let req$ = this.#returnService
.ReturnGetReturnReasons({
stockId: assignedStock?.id,
})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
this.#logger.debug('Request configured with abort signal');
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error) {
this.#logger.error(
'Failed to fetch return reasons',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
this.#logger.debug('Successfully fetched return reasons', () => ({
reasonCount: res.result?.length || 0,
}));
return res.result as KeyValueStringAndString[];
} catch (error) {
this.#logger.error('Failed to fetch return reasons', error, () => ({
stockId: assignedStock?.id,
}));
throw error;
}
this.#logger.debug('Successfully fetched return reasons', () => ({
reasonCount: res.result?.length || 0,
}));
return res.result as KeyValueStringAndString[];
}
}

View File

@@ -540,15 +540,15 @@ describe('RemissionReturnReceiptService', () => {
});
});
it('should handle abort signal', async () => {
it('should call API with correct parameters', async () => {
mockReturnService.ReturnCreateAndAssignPackage.mockReturnValue(
of({ result: mockReceipt, error: null }),
);
const abortController = new AbortController();
await service.assignPackage(
{ returnId: 123, receiptId: 456, packageNumber: 'PKG-789' },
abortController.signal,
);
await service.assignPackage({
returnId: 123,
receiptId: 456,
packageNumber: 'PKG-789',
});
expect(mockReturnService.ReturnCreateAndAssignPackage).toHaveBeenCalled();
});
@@ -1052,24 +1052,20 @@ describe('RemissionReturnReceiptService', () => {
});
});
it('should handle abort signal', async () => {
it('should call API with correct parameters', async () => {
// Arrange
mockReturnService.ReturnAddReturnItem.mockReturnValue(
of({ result: mockTuple, error: null }),
);
const abortController = new AbortController();
// Act
await service.addReturnItem(
{
returnId: 1,
receiptId: 2,
returnItemId: 3,
quantity: 4,
inStock: 5,
},
abortController.signal,
);
await service.addReturnItem({
returnId: 1,
receiptId: 2,
returnItemId: 3,
quantity: 4,
inStock: 5,
});
// Assert
expect(mockReturnService.ReturnAddReturnItem).toHaveBeenCalled();
@@ -1163,24 +1159,20 @@ describe('RemissionReturnReceiptService', () => {
});
});
it('should handle abort signal', async () => {
it('should call API with correct parameters', async () => {
// Arrange
mockReturnService.ReturnAddReturnSuggestion.mockReturnValue(
of({ result: mockTuple, error: null }),
);
const abortController = new AbortController();
// Act
await service.addReturnSuggestionItem(
{
returnId: 1,
receiptId: 2,
returnSuggestionId: 3,
quantity: 4,
inStock: 5,
},
abortController.signal,
);
await service.addReturnSuggestionItem({
returnId: 1,
receiptId: 2,
returnSuggestionId: 3,
quantity: 4,
inStock: 5,
});
// Assert
expect(mockReturnService.ReturnAddReturnSuggestion).toHaveBeenCalled();

View File

@@ -1,8 +1,8 @@
import { inject, Injectable } from '@angular/core';
import { ReturnService } from '@generated/swagger/inventory-api';
import {
catchResponseArgsErrorPipe,
ResponseArgs,
ResponseArgsError,
takeUntilAborted,
} from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
@@ -102,86 +102,21 @@ export class RemissionReturnReceiptService {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) {
this.#logger.error(
'Failed to fetch completed returns',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
const returns = (res?.result as Return[]) || [];
this.#logger.debug('Successfully fetched completed returns', () => ({
returnCount: returns.length,
}));
return returns;
} catch (error) {
this.#logger.error('Failed to fetch completed returns', error);
throw error;
}
const returns = (res?.result as Return[]) || [];
this.#logger.debug('Successfully fetched completed returns', () => ({
returnCount: returns.length,
}));
return returns;
}
// /**
// * Fetches a specific remission return receipt by receipt and return IDs.
// * Validates parameters using FetchRemissionReturnReceiptSchema before making the request.
// *
// * @async
// * @param {FetchRemissionReturnParams} params - The receipt and return identifiers
// * @param {FetchRemissionReturnParams} params.receiptId - ID of the receipt to fetch
// * @param {FetchRemissionReturnParams} params.returnId - ID of the return containing the receipt
// * @param {AbortSignal} [abortSignal] - Optional signal to abort the request
// * @returns {Promise<Receipt | undefined>} The receipt object if found, undefined otherwise
// * @throws {ResponseArgsError} When the API request fails
// * @throws {z.ZodError} When parameter validation fails
// *
// * @example
// * const receipt = await service.fetchRemissionReturnReceipt({
// * receiptId: '123',
// * returnId: '456'
// * });
// */
// async fetchRemissionReturnReceipt(
// params: FetchRemissionReturnParams,
// abortSignal?: AbortSignal,
// ): Promise<Receipt | undefined> {
// this.#logger.debug('Fetching remission return receipt', () => ({ params }));
// const { receiptId, returnId } =
// FetchRemissionReturnReceiptSchema.parse(params);
// this.#logger.info('Fetching return receipt from API', () => ({
// receiptId,
// returnId,
// }));
// let req$ = this.#returnService.ReturnGetReturnReceipt({
// receiptId,
// returnId,
// eagerLoading: 2,
// });
// if (abortSignal) {
// this.#logger.debug('Request configured with abort signal');
// req$ = req$.pipe(takeUntilAborted(abortSignal));
// }
// const res = await firstValueFrom(req$);
// if (res?.error) {
// this.#logger.error(
// 'Failed to fetch return receipt',
// new Error(res.message || 'Unknown error'),
// );
// throw new ResponseArgsError(res);
// }
// const receipt = res?.result as Receipt | undefined;
// this.#logger.debug('Successfully fetched return receipt', () => ({
// found: !!receipt,
// }));
// return receipt;
// }
/**
* Fetches a remission return by its ID.
* Validates parameters using FetchReturnSchema before making the request.
@@ -218,22 +153,19 @@ export class RemissionReturnReceiptService {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) {
this.#logger.error(
'Failed to fetch return',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
const returnData = res?.result as Return | undefined;
this.#logger.debug('Successfully fetched return', () => ({
found: !!returnData,
}));
return returnData;
} catch (error) {
this.#logger.error('Failed to fetch return', error);
throw error;
}
const returnData = res?.result as Return | undefined;
this.#logger.debug('Successfully fetched return', () => ({
found: !!returnData,
}));
return returnData;
}
/**
@@ -286,22 +218,19 @@ export class RemissionReturnReceiptService {
},
});
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) {
this.#logger.error(
'Failed to create return',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
const returnResponse = res as ResponseArgs<Return> | undefined;
this.#logger.debug('Successfully created return', () => ({
found: !!returnResponse,
}));
return returnResponse;
} catch (error) {
this.#logger.error('Failed to create return', error);
throw error;
}
const returnResponse = res as ResponseArgs<Return> | undefined;
this.#logger.debug('Successfully created return', () => ({
found: !!returnResponse,
}));
return returnResponse;
}
/**
@@ -354,22 +283,19 @@ export class RemissionReturnReceiptService {
},
});
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) {
this.#logger.error(
'Failed to create return receipt',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
const receiptResponse = res as ResponseArgs<Receipt> | undefined;
this.#logger.debug('Successfully created return receipt', () => ({
found: !!receiptResponse,
}));
return receiptResponse;
} catch (error) {
this.#logger.error('Failed to create return receipt', error);
throw error;
}
const receiptResponse = res as ResponseArgs<Receipt> | undefined;
this.#logger.debug('Successfully created return receipt', () => ({
found: !!receiptResponse,
}));
return receiptResponse;
}
/**
@@ -411,24 +337,21 @@ export class RemissionReturnReceiptService {
},
});
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) {
this.#logger.error(
'Failed to assign package',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
const receiptWithAssignedPackageResponse = res as
| ResponseArgs<Receipt>
| undefined;
this.#logger.debug('Successfully assigned package', () => ({
found: !!receiptWithAssignedPackageResponse,
}));
return receiptWithAssignedPackageResponse;
} catch (error) {
this.#logger.error('Failed to assign package', error);
throw error;
}
const receiptWithAssignedPackageResponse = res as
| ResponseArgs<Receipt>
| undefined;
this.#logger.debug('Successfully assigned package', () => ({
found: !!receiptWithAssignedPackageResponse,
}));
return receiptWithAssignedPackageResponse;
}
async removeReturnItemFromReturnReceipt(params: {
@@ -436,16 +359,15 @@ export class RemissionReturnReceiptService {
receiptId: number;
receiptItemId: number;
}) {
const res = await firstValueFrom(
this.#returnService.ReturnRemoveReturnItem(params),
);
if (res?.error) {
this.#logger.error(
'Failed to remove item from return receipt',
new Error(res.message || 'Unknown error'),
try {
await firstValueFrom(
this.#returnService
.ReturnRemoveReturnItem(params)
.pipe(catchResponseArgsErrorPipe()),
);
throw new ResponseArgsError(res);
} catch (error) {
this.#logger.error('Failed to remove item from return receipt', error);
throw error;
}
}
@@ -464,16 +386,17 @@ export class RemissionReturnReceiptService {
returnId: number;
receiptId: number;
}): Promise<void> {
const res = await firstValueFrom(
this.#returnService.ReturnCancelReturnReceipt(params),
);
if (res?.error) {
this.#logger.error(
'Failed to cancel return receipt',
new Error(res.message || 'Unknown error'),
try {
await firstValueFrom(
this.#returnService
.ReturnCancelReturnReceipt(params)
.pipe(catchResponseArgsErrorPipe()),
);
throw new ResponseArgsError(res);
} catch (error) {
this.#logger.error('Failed to cancel return receipt', error, () => ({
params,
}));
throw error;
}
}
@@ -486,34 +409,36 @@ export class RemissionReturnReceiptService {
* @throws {ResponseArgsError} When the API request fails
*/
async cancelReturn(params: { returnId: number }): Promise<void> {
const res = await firstValueFrom(
this.#returnService.ReturnCancelReturn(params),
);
if (res?.error) {
this.#logger.error(
'Failed to cancel return',
new Error(res.message || 'Unknown error'),
try {
await firstValueFrom(
this.#returnService
.ReturnCancelReturn(params)
.pipe(catchResponseArgsErrorPipe()),
);
throw new ResponseArgsError(res);
} catch (error) {
this.#logger.error('Failed to cancel return', error, () => ({
params,
}));
throw error;
}
}
async deleteReturnItem(params: { itemId: number }) {
this.#logger.debug('Deleting return item', () => ({ params }));
const res = await firstValueFrom(
this.#returnService.ReturnDeleteReturnItem(params),
);
if (res?.error) {
this.#logger.error(
'Failed to delete return item',
new Error(res.message || 'Unknown error'),
try {
const res = await firstValueFrom(
this.#returnService
.ReturnDeleteReturnItem(params)
.pipe(catchResponseArgsErrorPipe()),
);
throw new ResponseArgsError(res);
}
return res?.result as ReturnItem;
return res?.result as ReturnItem;
} catch (error) {
this.#logger.error('Failed to delete return item', error, () => ({
params,
}));
throw error;
}
}
async updateReturnItemImpediment(params: UpdateItemImpediment) {
@@ -521,24 +446,27 @@ export class RemissionReturnReceiptService {
const { itemId, comment } = UpdateItemImpedimentSchema.parse(params);
const res = await firstValueFrom(
this.#returnService.ReturnReturnItemImpediment({
itemId,
data: {
comment,
},
}),
);
try {
const res = await firstValueFrom(
this.#returnService
.ReturnReturnItemImpediment({
itemId,
data: {
comment,
},
})
.pipe(catchResponseArgsErrorPipe()),
);
if (res?.error) {
return res?.result as ReturnItem;
} catch (error) {
this.#logger.error(
'Failed to update return item impediment',
new Error(res.message || 'Unknown error'),
error,
() => ({ itemId, comment }),
);
throw new ResponseArgsError(res);
throw error;
}
return res?.result as ReturnItem;
}
async updateReturnSuggestionImpediment(params: UpdateItemImpediment) {
@@ -546,22 +474,26 @@ export class RemissionReturnReceiptService {
params,
}));
const { itemId, comment } = UpdateItemImpedimentSchema.parse(params);
const res = await firstValueFrom(
this.#returnService.ReturnReturnSuggestionImpediment({
itemId,
data: {
comment,
},
}),
);
if (res?.error) {
try {
const res = await firstValueFrom(
this.#returnService
.ReturnReturnSuggestionImpediment({
itemId,
data: {
comment,
},
})
.pipe(catchResponseArgsErrorPipe()),
);
return res?.result as ReturnSuggestion;
} catch (error) {
this.#logger.error(
'Failed to update return suggestion impediment',
new Error(res.message || 'Unknown error'),
error,
() => ({ itemId, comment }),
);
throw new ResponseArgsError(res);
throw error;
}
return res?.result as ReturnSuggestion;
}
async completeReturnReceipt({
@@ -572,23 +504,25 @@ export class RemissionReturnReceiptService {
receiptId: number;
}): Promise<Receipt> {
this.#logger.debug('Completing return receipt', () => ({ returnId }));
const res = await firstValueFrom(
this.#returnService.ReturnFinalizeReceipt({
try {
const res = await firstValueFrom(
this.#returnService
.ReturnFinalizeReceipt({
returnId,
receiptId,
data: {},
})
.pipe(catchResponseArgsErrorPipe()),
);
return res?.result as Receipt;
} catch (error) {
this.#logger.error('Failed to complete return receipt', error, () => ({
returnId,
receiptId,
data: {},
}),
);
if (res?.error) {
this.#logger.error(
'Failed to complete return receipt',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
}));
throw error;
}
return res?.result as Receipt;
}
async completeReturn(params: { returnId: number }): Promise<Return> {
@@ -596,23 +530,24 @@ export class RemissionReturnReceiptService {
returnId: params.returnId,
}));
const res = await firstValueFrom(
this.#returnService.ReturnFinalizeReturn(params),
);
if (res?.error) {
this.#logger.error(
'Failed to complete return',
new Error(res.message || 'Unknown error'),
try {
const res = await firstValueFrom(
this.#returnService
.ReturnFinalizeReturn(params)
.pipe(catchResponseArgsErrorPipe()),
);
throw new ResponseArgsError(res);
this.#logger.info('Successfully completed return', () => ({
returnId: params.returnId,
}));
return res?.result as Return;
} catch (error) {
this.#logger.error('Failed to complete return', error, () => ({
returnId: params.returnId,
}));
throw error;
}
this.#logger.info('Successfully completed return', () => ({
returnId: params.returnId,
}));
return res?.result as Return;
}
async completeReturnGroup(params: { returnGroup: string }) {
@@ -620,23 +555,24 @@ export class RemissionReturnReceiptService {
returnId: params.returnGroup,
}));
const res = await firstValueFrom(
this.#returnService.ReturnFinalizeReturnGroup(params),
);
if (res?.error) {
this.#logger.error(
'Failed to complete return group',
new Error(res.message || 'Unknown error'),
try {
const res = await firstValueFrom(
this.#returnService
.ReturnFinalizeReturnGroup(params)
.pipe(catchResponseArgsErrorPipe()),
);
throw new ResponseArgsError(res);
this.#logger.info('Successfully completed return group', () => ({
returnId: params.returnGroup,
}));
return res?.result as Return[];
} catch (error) {
this.#logger.error('Failed to complete return group', error, () => ({
returnGroup: params.returnGroup,
}));
throw error;
}
this.#logger.info('Successfully completed return group', () => ({
returnId: params.returnGroup,
}));
return res?.result as Return[];
}
async completeReturnReceiptAndReturn(params: {
@@ -711,22 +647,23 @@ export class RemissionReturnReceiptService {
},
});
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) {
this.#logger.error(
'Failed to add return item',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
const updatedReturn = res?.result as ReceiptReturnTuple | undefined;
this.#logger.debug('Successfully added return item', () => ({
found: !!updatedReturn,
}));
return updatedReturn;
} catch (error) {
this.#logger.error('Failed to add return item', error, () => ({
returnId,
receiptId,
returnItemId,
}));
throw error;
}
const updatedReturn = res?.result as ReceiptReturnTuple | undefined;
this.#logger.debug('Successfully added return item', () => ({
found: !!updatedReturn,
}));
return updatedReturn;
}
/**
@@ -788,24 +725,25 @@ export class RemissionReturnReceiptService {
},
});
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) {
this.#logger.error(
'Failed to add return suggestion item',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
const updatedReturnSuggestion = res?.result as
| ReceiptReturnSuggestionTuple
| undefined;
this.#logger.debug('Successfully added return suggestion item', () => ({
found: !!updatedReturnSuggestion,
}));
return updatedReturnSuggestion;
} catch (error) {
this.#logger.error('Failed to add return suggestion item', error, () => ({
returnId,
receiptId,
returnSuggestionId,
}));
throw error;
}
const updatedReturnSuggestion = res?.result as
| ReceiptReturnSuggestionTuple
| undefined;
this.#logger.debug('Successfully added return suggestion item', () => ({
found: !!updatedReturnSuggestion,
}));
return updatedReturnSuggestion;
}
/**

View File

@@ -19,8 +19,8 @@ import {
import { firstValueFrom } from 'rxjs';
import {
BatchResponseArgs,
catchResponseArgsErrorPipe,
ListResponseArgs,
ResponseArgsError,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
@@ -122,16 +122,18 @@ export class RemissionSearchService {
},
});
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to fetch required capacity', error);
this.#logger.debug('Successfully fetched required capacity');
return (res?.result ?? []) as ValueTupleOfStringAndInteger[];
} catch (error) {
this.#logger.error('Failed to fetch required capacity', error, () => ({
stockId: parsed.stockId,
supplierId: parsed.supplierId,
}));
throw error;
}
this.#logger.debug('Successfully fetched required capacity');
return (res?.result ?? []) as ValueTupleOfStringAndInteger[];
}
/**
@@ -401,14 +403,18 @@ export class RemissionSearchService {
req = req.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req);
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to check item addition', error);
try {
const res = await firstValueFrom(req.pipe(catchResponseArgsErrorPipe()));
return res as BatchResponseArgs<ReturnItem>;
} catch (error) {
this.#logger.error(
'Failed to check if items can be added to remission list',
error,
() => ({ stockId: stock.id, itemCount: items.length }),
);
throw error;
}
return res as BatchResponseArgs<ReturnItem>;
}
async addToList(
@@ -437,14 +443,17 @@ export class RemissionSearchService {
})),
});
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res.error) {
const error = new ResponseArgsError(res);
this.#logger.error('Failed to add item to remission list', error);
return res.successful?.map((r) => r.value) as ReturnItem[];
} catch (error) {
this.#logger.error(
'Failed to add items to remission list',
error,
() => ({ stockId: stock.id, itemCount: items.length }),
);
throw error;
}
return res.successful?.map((r) => r.value) as ReturnItem[];
}
}

View File

@@ -87,13 +87,13 @@ describe('RemissionStockService', () => {
);
});
it('should throw ResponseArgsError when API returns no result', async () => {
it('should throw Error when API returns no result', async () => {
mockStockService.StockCurrentStock.mockReturnValue(
of({ error: false, result: undefined }),
);
await expect(service.fetchAssignedStock()).rejects.toThrow(
ResponseArgsError,
'Assigned stock has no ID',
);
});
@@ -196,16 +196,17 @@ describe('RemissionStockService', () => {
expect(mockStockService.StockInStock).toHaveBeenCalled();
});
it('should throw ResponseArgsError when API returns no result', async () => {
it('should return empty array when API returns no result', async () => {
// Arrange
mockStockService.StockInStock.mockReturnValue(
of({ error: false, result: undefined }),
);
// Act & Assert
await expect(service.fetchStockInfos(validParams)).rejects.toThrow(
ResponseArgsError,
);
// Act
const result = await service.fetchStockInfos(validParams);
// Assert
expect(result).toBe(undefined);
expect(mockStockService.StockInStock).toHaveBeenCalled();
});

View File

@@ -3,7 +3,10 @@ import { StockService } from '@generated/swagger/inventory-api';
import { firstValueFrom } from 'rxjs';
import { Stock, StockInfo } from '../models';
import { FetchStockInStock, FetchStockInStockSchema } from '../schemas';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { InFlight, Cache, CacheTimeToLive } from '@isa/common/decorators';
@@ -56,73 +59,74 @@ export class RemissionStockService {
@InFlight()
async fetchAssignedStock(abortSignal?: AbortSignal): Promise<Stock> {
this.#logger.info('Fetching assigned stock from API');
let req$ = this.#stockService.StockCurrentStock();
let req$ = this.#stockService
.StockCurrentStock()
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
this.#logger.debug('Request configured with abort signal');
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
const result = res.result;
if (result?.id === undefined) {
const error = new Error('Assigned stock has no ID');
this.#logger.error('Invalid stock response', error);
throw error;
}
if (res.error || !res.result) {
this.#logger.error(
'Failed to fetch assigned stock',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
}
this.#logger.debug('Successfully fetched assigned stock', () => ({
stockId: result.id,
}));
const result = res.result;
if (result.id === undefined) {
const error = new Error('Assigned stock has no ID');
this.#logger.error('Invalid stock response', error);
// TypeScript cannot narrow StockDTO to Stock based on the id check above,
// so we use a minimal type assertion after runtime validation
return result as Stock;
} catch (error) {
this.#logger.error('Failed to fetch assigned stock', error);
throw error;
}
this.#logger.debug('Successfully fetched assigned stock', () => ({
stockId: result.id,
}));
// TypeScript cannot narrow StockDTO to Stock based on the id check above,
// so we use a minimal type assertion after runtime validation
return result as Stock;
}
async fetchStock(
branchId: number,
abortSignal?: AbortSignal,
): Promise<Stock | undefined> {
let req$ = this.#stockService.StockGetStocks();
let req$ = this.#stockService
.StockGetStocks()
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
this.#logger.debug('Request configured with abort signal');
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error || !res.result) {
this.#logger.error(
'Failed to fetch stocks',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
const stock = res.result?.find((s) => s.branch?.id === branchId);
if (!stock) {
return undefined;
}
if (stock.id === undefined) {
this.#logger.warn('Found stock without ID for branch', () => ({
branchId,
}));
return undefined;
}
// TypeScript cannot narrow StockDTO to Stock based on the id check above,
// so we use a minimal type assertion after runtime validation
return stock as Stock;
} catch (error) {
this.#logger.error('Failed to fetch stock', error, () => ({
branchId,
}));
throw error;
}
const stock = res.result.find((s) => s.branch?.id === branchId);
if (!stock) {
return undefined;
}
if (stock.id === undefined) {
this.#logger.warn('Found stock without ID for branch', () => ({ branchId }));
return undefined;
}
// TypeScript cannot narrow StockDTO to Stock based on the id check above,
// so we use a minimal type assertion after runtime validation
return stock as Stock;
}
/**
@@ -179,30 +183,31 @@ export class RemissionStockService {
itemCount: parsed.itemIds.length,
}));
let req$ = this.#stockService.StockInStock({
stockId: assignedStockId,
articleIds: parsed.itemIds,
});
let req$ = this.#stockService
.StockInStock({
stockId: assignedStockId,
articleIds: parsed.itemIds,
})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
this.#logger.debug('Request configured with abort signal');
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
try {
const res = await firstValueFrom(req$);
if (res.error || !res.result) {
this.#logger.error(
'Failed to fetch stock info',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
this.#logger.debug('Successfully fetched stock info', () => ({
itemCount: res.result?.length || 0,
}));
return res.result as StockInfo[];
} catch (error) {
this.#logger.error('Failed to fetch stock info', error, () => ({
stockId: assignedStockId,
}));
throw error;
}
this.#logger.debug('Successfully fetched stock info', () => ({
itemCount: res.result?.length || 0,
}));
return res.result as StockInfo[];
}
}