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 { inject, Injectable } from '@angular/core';
import { AvailabilityService as GeneratedAvailabilityService } from '@generated/swagger/availability-api'; 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 { LogisticianService, Logistician } from '@isa/oms/data-access';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
// TODO: [Next Sprint - Architectural] Abstract cross-domain dependency // TODO: [Next Sprint - Architectural] Abstract cross-domain dependency

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { StoreCheckoutSupplierService } from '@generated/swagger/checkout-api'; 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 { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { Cache, CacheTimeToLive, InFlight } from '@isa/common/decorators'; import { Cache, CacheTimeToLive, InFlight } from '@isa/common/decorators';
@@ -32,37 +35,42 @@ export class SupplierService {
async getTakeAwaySupplier(abortSignal?: AbortSignal): Promise<Supplier> { async getTakeAwaySupplier(abortSignal?: AbortSignal): Promise<Supplier> {
this.#logger.debug('Fetching take away supplier'); this.#logger.debug('Fetching take away supplier');
let req$ = this.#supplierService.StoreCheckoutSupplierGetSuppliers({}); let req$ = this.#supplierService
.StoreCheckoutSupplierGetSuppliers({})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) { if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { const takeAwaySupplier = res.result?.find(
const error = new ResponseArgsError(res); (supplier) => supplier.supplierNumber === 'F',
this.#logger.error('Failed to fetch suppliers', error); );
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; 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 { 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 { inject, Injectable } from '@angular/core';
import { Country } from '../models'; import { Country } from '../models';
import { import {
catchResponseArgsErrorPipe, catchResponseArgsErrorPipe,
ResponseArgs,
takeUntilAborted, takeUntilAborted,
} from '@isa/common/data-access'; } from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { logger } from '@isa/core/logging';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class CountryService { export class CountryService {
#apiCountryService = inject(ApiCountryService); #apiCountryService = inject(ApiCountryService);
#logger = logger(() => ({ service: 'CountryService' }));
@Cache() @Cache()
async getCountries(abortSignal?: AbortSignal): Promise<Country[]> { async getCountries(abortSignal?: AbortSignal): Promise<Country[]> {
@@ -23,8 +24,12 @@ export class CountryService {
req$ = req$.pipe(catchResponseArgsErrorPipe()); req$ = req$.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
return res.result as Country[]; 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 { import {
catchResponseArgsErrorPipe, catchResponseArgsErrorPipe,
ResponseArgs, ResponseArgs,
ResponseArgsError,
takeUntilAborted, takeUntilAborted,
} from '@isa/common/data-access'; } from '@isa/common/data-access';
import { Cache, CacheTimeToLive } from '@isa/common/decorators'; import { Cache, CacheTimeToLive } from '@isa/common/decorators';
@@ -161,8 +160,15 @@ export class CrmSearchService {
}) })
.pipe(catchResponseArgsErrorPipe()); .pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$); try {
return res?.result; 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> { async lockCard(params: LockCardInput): Promise<boolean | undefined> {
@@ -176,15 +182,15 @@ export class CrmSearchService {
}) })
.pipe(catchResponseArgsErrorPipe()); .pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { return res?.result;
const err = new ResponseArgsError(res); } catch (error) {
this.#logger.error('Lock card Failed', err); this.#logger.error('Error locking customer card', error, () => ({
throw err; cardCode: parsed.cardCode,
}));
throw error;
} }
return res?.result;
} }
async unlockCard(params: UnlockCardInput): Promise<boolean | undefined> { async unlockCard(params: UnlockCardInput): Promise<boolean | undefined> {
@@ -199,15 +205,16 @@ export class CrmSearchService {
}) })
.pipe(catchResponseArgsErrorPipe()); .pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { return res?.result;
const err = new ResponseArgsError(res); } catch (error) {
this.#logger.error('Unlock card Failed', err); this.#logger.error('Error unlocking customer card', error, () => ({
throw err; customerId: parsed.customerId,
cardCode: parsed.cardCode,
}));
throw error;
} }
return res?.result;
} }
@Cache({ ttl: CacheTimeToLive.oneHour }) @Cache({ ttl: CacheTimeToLive.oneHour })
@@ -224,10 +231,14 @@ export class CrmSearchService {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
this.#logger.debug('Successfully fetched current booking partner store'); const res = await firstValueFrom(req$);
this.#logger.debug('Successfully fetched current booking partner store');
return res?.result; return res?.result;
} catch (error) {
this.#logger.error('Error fetching current booking partner store', error);
throw error;
}
} }
async addBooking( async addBooking(
@@ -245,8 +256,16 @@ export class CrmSearchService {
}, },
}) })
.pipe(catchResponseArgsErrorPipe()); .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 { try {
const res = await firstValueFrom(req$); const res = await firstValueFrom(req$);
this.#logger.debug('Successfully checked Bon'); 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>; return res as ResponseArgs<LoyaltyBonResponse>;
} catch (error) { } catch (error) {
this.#logger.error('Error checking Bon', error); this.#logger.error('Error checking Bon', error, () => ({
cardCode,
bonNr,
}));
throw error; throw error;
} }
} }
@@ -301,8 +316,16 @@ export class CrmSearchService {
}) })
.pipe(catchResponseArgsErrorPipe()); .pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$); try {
this.#logger.debug('Successfully redeemed Bon'); const res = await firstValueFrom(req$);
return res?.result ?? false; 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 { logger } from '@isa/core/logging';
import { ReceiptService } from '@generated/swagger/oms-api'; import { ReceiptService } from '@generated/swagger/oms-api';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access'; import {
catchResponseArgsErrorPipe,
takeUntilAborted,
} from '@isa/common/data-access';
import { Receipt } from '../models'; import { Receipt } from '../models';
@Injectable() @Injectable()
@@ -74,29 +77,31 @@ export class HandleCommandService {
parsed, parsed,
})); }));
let req$ = this.#receiptService.ReceiptGetReceiptsByOrderItemSubset({ let req$ = this.#receiptService
payload: parsed, // Payload Default from old Implementation, eagerLoading: 1 and receiptType: (1 + 64 + 128) set as Schema default .ReceiptGetReceiptsByOrderItemSubset({
}); payload: parsed, // Payload Default from old Implementation, eagerLoading: 1 and receiptType: (1 + 64 + 128) set as Schema default
})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) { if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { // Mapping Logic from old implementation
const err = new ResponseArgsError(res); const mappedReceipts =
res?.result?.map((r) => r.item3?.data).filter((f) => !!f) ?? [];
return mappedReceipts as Receipt[];
} catch (error) {
this.#logger.error( this.#logger.error(
'Failed to fetch receipts by order item subset IDs', '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 { inject, Injectable } from '@angular/core';
import { LogisticianService as GeneratedLogisticianService } from '@generated/swagger/oms-api'; 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 { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { Cache, CacheTimeToLive, InFlight } from '@isa/common/decorators'; import { Cache, CacheTimeToLive, InFlight } from '@isa/common/decorators';
@@ -32,37 +35,38 @@ export class LogisticianService {
async getLogistician2470(abortSignal?: AbortSignal): Promise<Logistician> { async getLogistician2470(abortSignal?: AbortSignal): Promise<Logistician> {
this.#logger.debug('Fetching logistician 2470'); this.#logger.debug('Fetching logistician 2470');
let req$ = this.#logisticianService.LogisticianGetLogisticians({}); let req$ = this.#logisticianService
.LogisticianGetLogisticians({})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) { if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error || !res.result) { const logistician = res.result?.find(
const error = new ResponseArgsError(res); (l) => l.logisticianNumber === '2470',
this.#logger.error('Failed to fetch logisticians', error); );
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; 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, DBHOrderItemListItemDTO,
QueryTokenDTO, QueryTokenDTO,
} from '@generated/swagger/oms-api'; } 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 { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
@@ -49,26 +52,26 @@ export class OpenRewardTasksService {
orderBy: [], orderBy: [],
}; };
let req$ = this.#abholfachService.AbholfachWarenausgabe(payload); let req$ = this.#abholfachService
.AbholfachWarenausgabe(payload)
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) { if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
const tasks = res.result ?? [];
if (res.error) { this.#logger.debug('Open reward tasks fetched', () => ({
const error = new ResponseArgsError(res); taskCount: tasks.length,
}));
return tasks;
} catch (error) {
this.#logger.error('Failed to fetch open reward tasks', error); this.#logger.error('Failed to fetch open reward tasks', error);
throw 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, LogisticianService,
LogisticianDTO, LogisticianDTO,
} from '@generated/swagger/oms-api'; } 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 { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { DisplayOrder } from '../models'; import { DisplayOrder } from '../models';
@@ -36,19 +39,23 @@ export class OrderCreationService {
throw new Error(`Invalid checkoutId: ${checkoutId}`); throw new Error(`Invalid checkoutId: ${checkoutId}`);
} }
const req$ = this.#orderCheckoutService.OrderCheckoutCreateOrderPOST({ const req$ = this.#orderCheckoutService
checkoutId, .OrderCheckoutCreateOrderPOST({
}); checkoutId,
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { return res.result as DisplayOrder[];
const error = new ResponseArgsError(res); } catch (error) {
this.#logger.error('Failed to create orders', error); this.#logger.error(
'Failed to create orders from checkout',
error,
() => ({ checkoutId }),
);
throw error; throw error;
} }
return res.result as DisplayOrder[];
} }
/** /**
@@ -63,25 +70,29 @@ export class OrderCreationService {
logisticianNumber = '2470', logisticianNumber = '2470',
abortSignal?: AbortSignal, abortSignal?: AbortSignal,
): Promise<LogisticianDTO> { ): Promise<LogisticianDTO> {
let req$ = this.#logisticianService.LogisticianGetLogisticians({}); let req$ = this.#logisticianService
.LogisticianGetLogisticians({})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) req$ = req$.pipe(takeUntilAborted(abortSignal)); if (abortSignal) req$ = req$.pipe(takeUntilAborted(abortSignal));
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { const logistician = res.result?.find(
const error = new ResponseArgsError(res); (l) => l.logisticianNumber === logisticianNumber,
this.#logger.error('Failed to get logistician', error); );
if (!logistician) {
throw new Error(`Logistician ${logisticianNumber} not found`);
}
return logistician;
} catch (error) {
this.#logger.error('Failed to get logistician', error, () => ({
logisticianNumber,
}));
throw error; 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 { inject, Injectable } from '@angular/core';
import { OrderService } from '@generated/swagger/oms-api'; 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 { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { import {
@@ -25,25 +28,28 @@ export class OrderRewardCollectService {
throw error; throw error;
} }
const req$ = this.#orderService.OrderLoyaltyCollect({ const req$ = this.#orderService
orderId: params.orderId, .OrderLoyaltyCollect({
orderItemId: params.orderItemId, orderId: params.orderId,
orderItemSubsetId: params.orderItemSubsetId, orderItemId: params.orderItemId,
data: { orderItemSubsetId: params.orderItemSubsetId,
collectType: params.collectType, data: {
quantity: params.quantity, collectType: params.collectType,
}, quantity: params.quantity,
}); },
})
.pipe(catchResponseArgsErrorPipe());
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { return res.result as DBHOrderItemListItem[];
const error = new ResponseArgsError(res); } catch (error) {
this.#logger.error('Failed to collect reward item', error); this.#logger.error('Failed to collect order reward', error, () => ({
orderId: params.orderId,
orderItemSubsetId: params.orderItemSubsetId,
}));
throw error; throw error;
} }
return res.result as DBHOrderItemListItem[];
} }
async fetchOrderItemSubset( async fetchOrderItemSubset(
@@ -57,22 +63,22 @@ export class OrderRewardCollectService {
throw error; throw error;
} }
let req$ = this.#orderService.OrderGetOrderItemSubset( let req$ = this.#orderService
params.orderItemSubsetId, .OrderGetOrderItemSubset(params.orderItemSubsetId)
); .pipe(catchResponseArgsErrorPipe());
if (abortSignal) { if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { return res.result as DisplayOrderItemSubset;
const error = new ResponseArgsError(res); } catch (error) {
this.#logger.error('Failed to fetch order item subset', error); this.#logger.error('Failed to fetch order item subset', error, () => ({
orderItemSubsetId: params.orderItemSubsetId,
}));
throw error; throw error;
} }
return res.result as DisplayOrderItemSubset;
} }
} }

View File

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

View File

@@ -1,6 +1,9 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { StockService, BranchDTO } from '@generated/swagger/inventory-api'; 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 { logger } from '@isa/core/logging';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
@@ -27,41 +30,42 @@ export class BranchService {
* @throws {Error} If branch retrieval fails * @throws {Error} If branch retrieval fails
*/ */
async getDefaultBranch(abortSignal?: AbortSignal): Promise<BranchDTO> { 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)); 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) { if (!branch) {
const error = new ResponseArgsError(res); 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); this.#logger.error('Failed to get default branch', error);
throw 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 { firstValueFrom } from 'rxjs';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
import { RemissionStockService } from './remission-stock.service'; 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'; import { InFlight, Cache, CacheTimeToLive } from '@isa/common/decorators';
/** /**
@@ -65,26 +68,29 @@ export class RemissionProductGroupService {
stockId: assignedStock.id, stockId: assignedStock.id,
})); }));
let req$ = this.#remiService.RemiProductgroups({ let req$ = this.#remiService
stockId: assignedStock.id, .RemiProductgroups({
}); stockId: assignedStock.id,
})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) { if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error || !res.result) { this.#logger.debug('Successfully fetched product groups', () => ({
const error = new ResponseArgsError(res); groupCount: res.result?.length || 0,
this.#logger.error('Failed to fetch product groups', error); }));
return res.result as KeyValueStringAndString[];
} catch (error) {
this.#logger.error('Failed to fetch product groups', error, () => ({
stockId: assignedStock.id,
}));
throw error; 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 { inject, Injectable } from '@angular/core';
import { ReturnService } from '@generated/swagger/inventory-api'; 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 { firstValueFrom } from 'rxjs';
import { KeyValueStringAndString } from '../models'; import { KeyValueStringAndString } from '../models';
import { logger } from '@isa/core/logging'; import { logger } from '@isa/core/logging';
@@ -74,29 +77,30 @@ export class RemissionReasonService {
stockId: assignedStock?.id, stockId: assignedStock?.id,
})); }));
let req$ = this.#returnService.ReturnGetReturnReasons({ let req$ = this.#returnService
stockId: assignedStock?.id, .ReturnGetReturnReasons({
}); stockId: assignedStock?.id,
})
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) { if (abortSignal) {
this.#logger.debug('Request configured with abort signal'); this.#logger.debug('Request configured with abort signal');
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$);
if (res.error) { this.#logger.debug('Successfully fetched return reasons', () => ({
this.#logger.error( reasonCount: res.result?.length || 0,
'Failed to fetch return reasons', }));
new Error(res.message || 'Unknown error'),
); return res.result as KeyValueStringAndString[];
throw new ResponseArgsError(res); } 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( mockReturnService.ReturnCreateAndAssignPackage.mockReturnValue(
of({ result: mockReceipt, error: null }), of({ result: mockReceipt, error: null }),
); );
const abortController = new AbortController(); await service.assignPackage({
await service.assignPackage( returnId: 123,
{ returnId: 123, receiptId: 456, packageNumber: 'PKG-789' }, receiptId: 456,
abortController.signal, packageNumber: 'PKG-789',
); });
expect(mockReturnService.ReturnCreateAndAssignPackage).toHaveBeenCalled(); 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 // Arrange
mockReturnService.ReturnAddReturnItem.mockReturnValue( mockReturnService.ReturnAddReturnItem.mockReturnValue(
of({ result: mockTuple, error: null }), of({ result: mockTuple, error: null }),
); );
const abortController = new AbortController();
// Act // Act
await service.addReturnItem( await service.addReturnItem({
{ returnId: 1,
returnId: 1, receiptId: 2,
receiptId: 2, returnItemId: 3,
returnItemId: 3, quantity: 4,
quantity: 4, inStock: 5,
inStock: 5, });
},
abortController.signal,
);
// Assert // Assert
expect(mockReturnService.ReturnAddReturnItem).toHaveBeenCalled(); 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 // Arrange
mockReturnService.ReturnAddReturnSuggestion.mockReturnValue( mockReturnService.ReturnAddReturnSuggestion.mockReturnValue(
of({ result: mockTuple, error: null }), of({ result: mockTuple, error: null }),
); );
const abortController = new AbortController();
// Act // Act
await service.addReturnSuggestionItem( await service.addReturnSuggestionItem({
{ returnId: 1,
returnId: 1, receiptId: 2,
receiptId: 2, returnSuggestionId: 3,
returnSuggestionId: 3, quantity: 4,
quantity: 4, inStock: 5,
inStock: 5, });
},
abortController.signal,
);
// Assert // Assert
expect(mockReturnService.ReturnAddReturnSuggestion).toHaveBeenCalled(); expect(mockReturnService.ReturnAddReturnSuggestion).toHaveBeenCalled();

View File

@@ -1,8 +1,8 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { ReturnService } from '@generated/swagger/inventory-api'; import { ReturnService } from '@generated/swagger/inventory-api';
import { import {
catchResponseArgsErrorPipe,
ResponseArgs, ResponseArgs,
ResponseArgsError,
takeUntilAborted, takeUntilAborted,
} from '@isa/common/data-access'; } from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
@@ -102,86 +102,21 @@ export class RemissionReturnReceiptService {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) { const returns = (res?.result as Return[]) || [];
this.#logger.error( this.#logger.debug('Successfully fetched completed returns', () => ({
'Failed to fetch completed returns', returnCount: returns.length,
new Error(res.message || 'Unknown error'), }));
);
throw new ResponseArgsError(res); 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. * Fetches a remission return by its ID.
* Validates parameters using FetchReturnSchema before making the request. * Validates parameters using FetchReturnSchema before making the request.
@@ -218,22 +153,19 @@ export class RemissionReturnReceiptService {
req$ = req$.pipe(takeUntilAborted(abortSignal)); req$ = req$.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req$); try {
const res = await firstValueFrom(req$.pipe(catchResponseArgsErrorPipe()));
if (res?.error) { const returnData = res?.result as Return | undefined;
this.#logger.error( this.#logger.debug('Successfully fetched return', () => ({
'Failed to fetch return', found: !!returnData,
new Error(res.message || 'Unknown error'), }));
);
throw new ResponseArgsError(res); 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) { const returnResponse = res as ResponseArgs<Return> | undefined;
this.#logger.error( this.#logger.debug('Successfully created return', () => ({
'Failed to create return', found: !!returnResponse,
new Error(res.message || 'Unknown error'), }));
);
throw new ResponseArgsError(res); 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) { const receiptResponse = res as ResponseArgs<Receipt> | undefined;
this.#logger.error( this.#logger.debug('Successfully created return receipt', () => ({
'Failed to create return receipt', found: !!receiptResponse,
new Error(res.message || 'Unknown error'), }));
);
throw new ResponseArgsError(res); 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) { const receiptWithAssignedPackageResponse = res as
this.#logger.error( | ResponseArgs<Receipt>
'Failed to assign package', | undefined;
new Error(res.message || 'Unknown error'),
); this.#logger.debug('Successfully assigned package', () => ({
throw new ResponseArgsError(res); 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: { async removeReturnItemFromReturnReceipt(params: {
@@ -436,16 +359,15 @@ export class RemissionReturnReceiptService {
receiptId: number; receiptId: number;
receiptItemId: number; receiptItemId: number;
}) { }) {
const res = await firstValueFrom( try {
this.#returnService.ReturnRemoveReturnItem(params), await firstValueFrom(
); this.#returnService
.ReturnRemoveReturnItem(params)
if (res?.error) { .pipe(catchResponseArgsErrorPipe()),
this.#logger.error(
'Failed to remove item from return receipt',
new Error(res.message || 'Unknown error'),
); );
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; returnId: number;
receiptId: number; receiptId: number;
}): Promise<void> { }): Promise<void> {
const res = await firstValueFrom( try {
this.#returnService.ReturnCancelReturnReceipt(params), await firstValueFrom(
); this.#returnService
.ReturnCancelReturnReceipt(params)
if (res?.error) { .pipe(catchResponseArgsErrorPipe()),
this.#logger.error(
'Failed to cancel return receipt',
new Error(res.message || 'Unknown error'),
); );
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 * @throws {ResponseArgsError} When the API request fails
*/ */
async cancelReturn(params: { returnId: number }): Promise<void> { async cancelReturn(params: { returnId: number }): Promise<void> {
const res = await firstValueFrom( try {
this.#returnService.ReturnCancelReturn(params), await firstValueFrom(
); this.#returnService
.ReturnCancelReturn(params)
if (res?.error) { .pipe(catchResponseArgsErrorPipe()),
this.#logger.error(
'Failed to cancel return',
new Error(res.message || 'Unknown error'),
); );
throw new ResponseArgsError(res); } catch (error) {
this.#logger.error('Failed to cancel return', error, () => ({
params,
}));
throw error;
} }
} }
async deleteReturnItem(params: { itemId: number }) { async deleteReturnItem(params: { itemId: number }) {
this.#logger.debug('Deleting return item', () => ({ params })); this.#logger.debug('Deleting return item', () => ({ params }));
const res = await firstValueFrom( try {
this.#returnService.ReturnDeleteReturnItem(params), const res = await firstValueFrom(
); this.#returnService
.ReturnDeleteReturnItem(params)
if (res?.error) { .pipe(catchResponseArgsErrorPipe()),
this.#logger.error(
'Failed to delete return item',
new Error(res.message || 'Unknown error'),
); );
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) { async updateReturnItemImpediment(params: UpdateItemImpediment) {
@@ -521,24 +446,27 @@ export class RemissionReturnReceiptService {
const { itemId, comment } = UpdateItemImpedimentSchema.parse(params); const { itemId, comment } = UpdateItemImpedimentSchema.parse(params);
const res = await firstValueFrom( try {
this.#returnService.ReturnReturnItemImpediment({ const res = await firstValueFrom(
itemId, this.#returnService
data: { .ReturnReturnItemImpediment({
comment, itemId,
}, data: {
}), comment,
); },
})
.pipe(catchResponseArgsErrorPipe()),
);
if (res?.error) { return res?.result as ReturnItem;
} catch (error) {
this.#logger.error( this.#logger.error(
'Failed to update return item impediment', '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) { async updateReturnSuggestionImpediment(params: UpdateItemImpediment) {
@@ -546,22 +474,26 @@ export class RemissionReturnReceiptService {
params, params,
})); }));
const { itemId, comment } = UpdateItemImpedimentSchema.parse(params); const { itemId, comment } = UpdateItemImpedimentSchema.parse(params);
const res = await firstValueFrom( try {
this.#returnService.ReturnReturnSuggestionImpediment({ const res = await firstValueFrom(
itemId, this.#returnService
data: { .ReturnReturnSuggestionImpediment({
comment, itemId,
}, data: {
}), comment,
); },
if (res?.error) { })
.pipe(catchResponseArgsErrorPipe()),
);
return res?.result as ReturnSuggestion;
} catch (error) {
this.#logger.error( this.#logger.error(
'Failed to update return suggestion impediment', '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({ async completeReturnReceipt({
@@ -572,23 +504,25 @@ export class RemissionReturnReceiptService {
receiptId: number; receiptId: number;
}): Promise<Receipt> { }): Promise<Receipt> {
this.#logger.debug('Completing return receipt', () => ({ returnId })); this.#logger.debug('Completing return receipt', () => ({ returnId }));
const res = await firstValueFrom( try {
this.#returnService.ReturnFinalizeReceipt({ 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, returnId,
receiptId, receiptId,
data: {}, }));
}), throw error;
);
if (res?.error) {
this.#logger.error(
'Failed to complete return receipt',
new Error(res.message || 'Unknown error'),
);
throw new ResponseArgsError(res);
} }
return res?.result as Receipt;
} }
async completeReturn(params: { returnId: number }): Promise<Return> { async completeReturn(params: { returnId: number }): Promise<Return> {
@@ -596,23 +530,24 @@ export class RemissionReturnReceiptService {
returnId: params.returnId, returnId: params.returnId,
})); }));
const res = await firstValueFrom( try {
this.#returnService.ReturnFinalizeReturn(params), const res = await firstValueFrom(
); this.#returnService
.ReturnFinalizeReturn(params)
if (res?.error) { .pipe(catchResponseArgsErrorPipe()),
this.#logger.error(
'Failed to complete return',
new Error(res.message || 'Unknown error'),
); );
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 }) { async completeReturnGroup(params: { returnGroup: string }) {
@@ -620,23 +555,24 @@ export class RemissionReturnReceiptService {
returnId: params.returnGroup, returnId: params.returnGroup,
})); }));
const res = await firstValueFrom( try {
this.#returnService.ReturnFinalizeReturnGroup(params), const res = await firstValueFrom(
); this.#returnService
.ReturnFinalizeReturnGroup(params)
if (res?.error) { .pipe(catchResponseArgsErrorPipe()),
this.#logger.error(
'Failed to complete return group',
new Error(res.message || 'Unknown error'),
); );
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: { 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) { const updatedReturn = res?.result as ReceiptReturnTuple | undefined;
this.#logger.error( this.#logger.debug('Successfully added return item', () => ({
'Failed to add return item', found: !!updatedReturn,
new Error(res.message || 'Unknown error'), }));
);
throw new ResponseArgsError(res); 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) { const updatedReturnSuggestion = res?.result as
this.#logger.error( | ReceiptReturnSuggestionTuple
'Failed to add return suggestion item', | undefined;
new Error(res.message || 'Unknown error'), this.#logger.debug('Successfully added return suggestion item', () => ({
); found: !!updatedReturnSuggestion,
throw new ResponseArgsError(res); }));
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 { firstValueFrom } from 'rxjs';
import { import {
BatchResponseArgs, BatchResponseArgs,
catchResponseArgsErrorPipe,
ListResponseArgs, ListResponseArgs,
ResponseArgsError,
takeUntilAborted, takeUntilAborted,
} from '@isa/common/data-access'; } from '@isa/common/data-access';
import { logger } from '@isa/core/logging'; 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) { this.#logger.debug('Successfully fetched required capacity');
const error = new ResponseArgsError(res); return (res?.result ?? []) as ValueTupleOfStringAndInteger[];
this.#logger.error('Failed to fetch required capacity', error); } catch (error) {
this.#logger.error('Failed to fetch required capacity', error, () => ({
stockId: parsed.stockId,
supplierId: parsed.supplierId,
}));
throw error; 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)); req = req.pipe(takeUntilAborted(abortSignal));
} }
const res = await firstValueFrom(req); try {
if (res.error) { const res = await firstValueFrom(req.pipe(catchResponseArgsErrorPipe()));
const error = new ResponseArgsError(res);
this.#logger.error('Failed to check item addition', error); 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; throw error;
} }
return res as BatchResponseArgs<ReturnItem>;
} }
async addToList( 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) { return res.successful?.map((r) => r.value) as ReturnItem[];
const error = new ResponseArgsError(res); } catch (error) {
this.#logger.error('Failed to add item to remission list', error); this.#logger.error(
'Failed to add items to remission list',
error,
() => ({ stockId: stock.id, itemCount: items.length }),
);
throw error; 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( mockStockService.StockCurrentStock.mockReturnValue(
of({ error: false, result: undefined }), of({ error: false, result: undefined }),
); );
await expect(service.fetchAssignedStock()).rejects.toThrow( await expect(service.fetchAssignedStock()).rejects.toThrow(
ResponseArgsError, 'Assigned stock has no ID',
); );
}); });
@@ -196,16 +196,17 @@ describe('RemissionStockService', () => {
expect(mockStockService.StockInStock).toHaveBeenCalled(); 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 // Arrange
mockStockService.StockInStock.mockReturnValue( mockStockService.StockInStock.mockReturnValue(
of({ error: false, result: undefined }), of({ error: false, result: undefined }),
); );
// Act & Assert // Act
await expect(service.fetchStockInfos(validParams)).rejects.toThrow( const result = await service.fetchStockInfos(validParams);
ResponseArgsError,
); // Assert
expect(result).toBe(undefined);
expect(mockStockService.StockInStock).toHaveBeenCalled(); expect(mockStockService.StockInStock).toHaveBeenCalled();
}); });

View File

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