feat(checkout): add reward order confirmation feature with schema migrations

- Add new reward-order-confirmation feature library with components and store
- Implement checkout completion orchestrator service for order finalization
- Migrate checkout/oms/crm models to Zod schemas for better type safety
- Add order creation facade and display order schemas
- Update shopping cart facade with order completion flow
- Add comprehensive tests for shopping cart facade
- Update routing to include order confirmation page
This commit is contained in:
Lorenz Hilpert
2025-10-21 14:28:52 +02:00
parent b96d889da5
commit 2b5da00249
259 changed files with 61347 additions and 2652 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -3,4 +3,5 @@ export * from './lib/constants';
export * from './lib/models';
export * from './lib/resources';
export * from './lib/helpers';
export * from './lib/schemas';
export * from './lib/services';

View File

@@ -1,7 +1,6 @@
import { inject, Injectable } from '@angular/core';
import { CrmSearchService } from '../services/crm-search.service';
import { FetchCustomerInput } from '../schemas';
import { Customer } from '../models';
import { FetchCustomerInput, Customer } from '../schemas';
@Injectable({ providedIn: 'root' })
export class CustomerFacade {

View File

@@ -1,7 +0,0 @@
import { AssignedPayerDTO } from '@generated/swagger/crm-api';
import { Payer } from './payer';
import { EntityContainer } from '@isa/common/data-access';
export type AssignedPayer = AssignedPayerDTO & {
payer: EntityContainer<Payer>;
};

View File

@@ -1,8 +0,0 @@
import { CustomerDTO } from '@generated/swagger/crm-api';
import { CustomerType } from './customer-type';
import { AssignedPayer } from './assigned-payer';
export interface Customer extends CustomerDTO {
customerType: CustomerType;
payers: Array<AssignedPayer>;
}

View File

@@ -1,7 +1,4 @@
export * from './assigned-payer';
export * from './bonus-card-info.model';
export * from './country';
export * from './customer-type';
export * from './customer.model';
export * from './payer';
export * from './shipping-address.model';

View File

@@ -1,3 +0,0 @@
import { ShippingAddressDTO } from '@generated/swagger/crm-api';
export type ShippingAddress = ShippingAddressDTO;

View File

@@ -1,54 +1,57 @@
import { effect, inject, Injectable, resource, signal } from '@angular/core';
import { CrmTabMetadataService, ShippingAddressService } from '../services';
import { TabService } from '@isa/core/tabs';
import { ShippingAddress } from '../models';
@Injectable()
export class CustomerShippingAddressResource {
#shippingAddressService = inject(ShippingAddressService);
#params = signal<{
shippingAddressId: number | undefined;
}>({
shippingAddressId: undefined,
});
params(params: { shippingAddressId?: number }) {
this.#params.update((p) => ({ ...p, ...params }));
}
readonly resource = resource({
params: () => this.#params(),
loader: async ({ params, abortSignal }): Promise<ShippingAddress | undefined> => {
if (!params.shippingAddressId) {
return undefined;
}
const res = await this.#shippingAddressService.fetchShippingAddress(
{
shippingAddressId: params.shippingAddressId,
},
abortSignal,
);
return res.result as ShippingAddress;
},
});
}
@Injectable()
export class SelectedCustomerShippingAddressResource extends CustomerShippingAddressResource {
#tabId = inject(TabService).activatedTabId;
#customerMetadata = inject(CrmTabMetadataService);
constructor() {
super();
effect(() => {
const tabId = this.#tabId();
const shippingAddressId = tabId
? this.#customerMetadata.selectedShippingAddressId(tabId)
: undefined;
this.params({ shippingAddressId });
});
}
}
import { effect, inject, Injectable, resource, signal } from '@angular/core';
import { CrmTabMetadataService, ShippingAddressService } from '../services';
import { TabService } from '@isa/core/tabs';
import { ShippingAddress } from '../schemas';
@Injectable()
export class CustomerShippingAddressResource {
#shippingAddressService = inject(ShippingAddressService);
#params = signal<{
shippingAddressId: number | undefined;
}>({
shippingAddressId: undefined,
});
params(params: { shippingAddressId?: number }) {
this.#params.update((p) => ({ ...p, ...params }));
}
readonly resource = resource({
params: () => this.#params(),
loader: async ({
params,
abortSignal,
}): Promise<ShippingAddress | undefined> => {
if (!params.shippingAddressId) {
return undefined;
}
const res = await this.#shippingAddressService.fetchShippingAddress(
{
shippingAddressId: params.shippingAddressId,
},
abortSignal,
);
return res.result as ShippingAddress;
},
});
}
@Injectable()
export class SelectedCustomerShippingAddressResource extends CustomerShippingAddressResource {
#tabId = inject(TabService).activatedTabId;
#customerMetadata = inject(CrmTabMetadataService);
constructor() {
super();
effect(() => {
const tabId = this.#tabId();
const shippingAddressId = tabId
? this.#customerMetadata.selectedShippingAddressId(tabId)
: undefined;
this.params({ shippingAddressId });
});
}
}

View File

@@ -1,58 +1,66 @@
import { effect, inject, Injectable, resource, signal } from '@angular/core';
import { CrmTabMetadataService, ShippingAddressService } from '../services';
import { TabService } from '@isa/core/tabs';
import { ShippingAddress } from '../models';
@Injectable()
export class CustomerShippingAddressesResource {
#shippingAddressService = inject(ShippingAddressService);
#params = signal<{
customerId: number | undefined;
take?: number | null;
skip?: number | null;
}>({
customerId: undefined,
});
params(params: { customerId?: number; take?: number | null; skip?: number | null }) {
this.#params.update((p) => ({ ...p, ...params }));
}
readonly resource = resource({
params: () => this.#params(),
loader: async ({ params, abortSignal }): Promise<ShippingAddress[] | undefined> => {
if (!params.customerId) {
return undefined;
}
const res = await this.#shippingAddressService.fetchCustomerShippingAddresses(
{
customerId: params.customerId,
take: params.take,
skip: params.skip,
},
abortSignal,
);
return res.result as ShippingAddress[];
},
});
}
@Injectable()
export class SelectedCustomerShippingAddressesResource extends CustomerShippingAddressesResource {
#tabId = inject(TabService).activatedTabId;
#customerMetadata = inject(CrmTabMetadataService);
constructor() {
super();
effect(() => {
const tabId = this.#tabId();
const customerId = tabId
? this.#customerMetadata.selectedCustomerId(tabId)
: undefined;
this.params({ customerId });
});
}
}
import { effect, inject, Injectable, resource, signal } from '@angular/core';
import { CrmTabMetadataService, ShippingAddressService } from '../services';
import { TabService } from '@isa/core/tabs';
import { ShippingAddress } from '../schemas';
@Injectable()
export class CustomerShippingAddressesResource {
#shippingAddressService = inject(ShippingAddressService);
#params = signal<{
customerId: number | undefined;
take?: number | null;
skip?: number | null;
}>({
customerId: undefined,
});
params(params: {
customerId?: number;
take?: number | null;
skip?: number | null;
}) {
this.#params.update((p) => ({ ...p, ...params }));
}
readonly resource = resource({
params: () => this.#params(),
loader: async ({
params,
abortSignal,
}): Promise<ShippingAddress[] | undefined> => {
if (!params.customerId) {
return undefined;
}
const res =
await this.#shippingAddressService.fetchCustomerShippingAddresses(
{
customerId: params.customerId,
take: params.take,
skip: params.skip,
},
abortSignal,
);
return res.result as ShippingAddress[];
},
});
}
@Injectable()
export class SelectedCustomerShippingAddressesResource extends CustomerShippingAddressesResource {
#tabId = inject(TabService).activatedTabId;
#customerMetadata = inject(CrmTabMetadataService);
constructor() {
super();
effect(() => {
const tabId = this.#tabId();
const customerId = tabId
? this.#customerMetadata.selectedCustomerId(tabId)
: undefined;
this.params({ customerId });
});
}
}

View File

@@ -1,7 +1,7 @@
import { effect, inject, Injectable, resource, signal } from '@angular/core';
import { CrmSearchService, CrmTabMetadataService } from '../services';
import { TabService } from '@isa/core/tabs';
import { Customer } from '../models';
import { Customer } from '../schemas';
@Injectable()
export class CustomerResource {

View File

@@ -0,0 +1,11 @@
import { EntityContainerSchema } from '@isa/common/data-access';
import { z } from 'zod';
import { PayerSchema } from './payer.schema';
export const AssignedPayerSchema = z.object({
assignedToCustomer: z.string().describe('Assigned to customer').optional(),
isDefault: z.string().describe('Whether this is the default').optional(),
payer: EntityContainerSchema(PayerSchema).describe('Payer information').optional(),
});
export type AssignedPayer = z.infer<typeof AssignedPayerSchema>;

View File

@@ -0,0 +1,19 @@
import { z } from 'zod';
/**
* Schema for AttributeDTO
* Represents attribute data with date fields as strings (ISO format)
*
* Note: The API returns start/stop as string (not Date objects).
* If date parsing is needed, use z.coerce.date() or parse in application logic.
*/
export const AttributeSchema = z.object({
dataType: z.number().describe('Data type'),
formatValidator: z.string().describe('Format validator').optional(),
group: z.string().describe('Group').optional(),
key: z.string().describe('Key'),
name: z.string().describe('Name').optional(),
start: z.string().describe('Start').optional(),
stop: z.string().describe('Stop').optional(),
value: z.string().describe('Value').optional(),
});

View File

@@ -0,0 +1,22 @@
import { EntitySchema } from '@isa/common/data-access';
import { z } from 'zod';
/**
* Schema for BonusCardDTO
* Represents bonus card information with proper type matching
*
* Note: cardProvider is a number in the API, and dates are returned as strings
*/
export const BonusCardSchema = z
.object({
bonusValue: z.number().describe('Bonus value').optional(),
cardNumber: z.string().describe('Card number').optional(),
cardProvider: z.number().describe('Card provider').optional(),
isLocked: z.boolean().describe('Whether locked').optional(),
isPaymentEnabled: z.boolean().describe('Whether paymentEnabled').optional(),
markedAsLost: z.string().describe('Marked as lost').optional(),
suspensionComment: z.string().describe('Suspension comment').optional(),
validFrom: z.string().describe('Validity start date').optional(),
validThrough: z.string().describe('Valid through').optional(),
})
.extend(EntitySchema.shape);

View File

@@ -0,0 +1,21 @@
import {
AddressSchema,
EntityContainerSchema,
EntitySchema,
LabelSchema,
} from '@isa/common/data-access';
import { number, z } from 'zod';
export const BranchSchema = z
.object({
address: AddressSchema.describe('Address').optional(),
banchNumber: z.string().describe('Banch number').optional(),
branchType: z.number().describe('Branch type'),
isOnline: z.boolean().describe('Whether online').optional(),
key: z.string().describe('Key').optional(),
label: EntityContainerSchema(LabelSchema).describe('Label').optional(),
name: z.string().describe('Name').optional(),
parent: z.number().describe('Parent').optional(),
shortName: z.string().describe('Short name').optional(),
})
.extend(EntitySchema.shape);

View File

@@ -0,0 +1,96 @@
import {
AddressSchema,
CommunicationDetailsSchema,
EntityContainerSchema,
EntitySchema,
GenderSchema,
KeyValueOfStringAndStringSchema,
LabelSchema,
NotificationChannelSchema,
OrganisationSchema,
} from '@isa/common/data-access';
import { z } from 'zod';
import { CustomerType } from '../models';
import { AssignedPayerSchema } from './assigned-payer.schema';
import { AttributeSchema } from './attribute.schema';
import { BonusCardSchema } from './bonus-card.schema';
import { BranchSchema } from './branch.schema';
import { LinkedRecordSchema } from './linked-record.schema';
import { ShippingAddressSchema } from './shipping-address.schema';
import { UserSchema } from './user.schema';
export const CustomerSchema = z
.object({
address: AddressSchema.describe('Address').optional(),
agentComment: z.string().describe('Agent comment').optional(),
attributes: z
.array(EntityContainerSchema(AttributeSchema))
.describe('Attribute list')
.optional(),
bonusCard: EntityContainerSchema(BonusCardSchema)
.describe('Bonus card information')
.optional(),
campaignCode: z.string().describe('Campaign code').optional(),
clientChannel: z.number().describe('Client channel').optional(),
communicationDetails: CommunicationDetailsSchema.describe(
'Communication details',
).optional(),
createdInBranch: EntityContainerSchema(BranchSchema)
.describe('Created in branch')
.optional(),
customerGroup: z.string().describe('Customer group').optional(),
customerNumber: z.string().describe('Customer number').optional(),
customerStatus: z.number().describe('Customer status').optional(),
customerType: z.nativeEnum(CustomerType).describe('Customer type'),
dateOfBirth: z.string().describe('Date of birth').optional(),
deactivationComment: z.string().describe('Deactivation comment').optional(),
features: z
.array(KeyValueOfStringAndStringSchema)
.describe('Features')
.optional(),
fetchOnDeliveryNote: z
.boolean()
.describe('Fetch on delivery note')
.optional(),
firstName: z.string().describe('First name').optional(),
gender: GenderSchema.describe('Gender').optional(),
hasOnlineAccount: z
.boolean()
.describe('Whether has onlineAccount')
.optional(),
isGuestAccount: z.boolean().describe('Whether guestAccount').optional(),
label: EntityContainerSchema(LabelSchema).describe('Label').optional(),
lastName: z.string().describe('Last name').optional(),
linkedRecords: z
.array(LinkedRecordSchema)
.describe('List of linked records')
.optional(),
notificationChannels: NotificationChannelSchema.describe(
'Notification channels',
).optional(),
orderCount: z.number().describe('Number of orders').optional(),
organisation: OrganisationSchema.describe(
'Organisation information',
).optional(),
payers: z.array(AssignedPayerSchema).describe('Payers').optional(),
preferredPaymentType: z
.number()
.describe('PreferredPayment type')
.optional(),
shippingAddresses: z
.array(EntityContainerSchema(ShippingAddressSchema))
.describe('Shipping addresses')
.optional(),
statusChangeComment: z
.string()
.describe('Status change comment')
.optional(),
statusComment: z.string().describe('Status comment').optional(),
title: z.string().describe('Title').optional(),
user: EntityContainerSchema(UserSchema)
.describe('User information')
.optional(),
})
.extend(EntitySchema.shape);
export type Customer = z.infer<typeof CustomerSchema>;

View File

@@ -1,7 +1,7 @@
import { z } from 'zod';
export const FetchCustomerCardsSchema = z.object({
customerId: z.number().int(),
customerId: z.number().int().describe('Unique customer identifier'),
});
export type FetchCustomerCards = z.infer<typeof FetchCustomerCardsSchema>;

View File

@@ -1,9 +1,9 @@
import { z } from 'zod';
export const FetchCustomerShippingAddressesSchema = z.object({
customerId: z.number().int(),
take: z.number().int().optional().nullable(),
skip: z.number().int().optional().nullable(),
customerId: z.number().int().describe('Unique customer identifier'),
take: z.number().int().optional().describe('Number of items to return per page').nullable(),
skip: z.number().int().optional().describe('Number of items to skip for pagination').nullable(),
});
export type FetchCustomerShippingAddresses = z.infer<typeof FetchCustomerShippingAddressesSchema>;

View File

@@ -1,8 +1,8 @@
import { z } from 'zod';
export const FetchCustomerSchema = z.object({
customerId: z.number().int(),
eagerLoading: z.number().optional(),
customerId: z.number().int().describe('Unique customer identifier'),
eagerLoading: z.number().describe('Eager loading').optional(),
});
export type FetchCustomer = z.infer<typeof FetchCustomerSchema>;

View File

@@ -1,7 +1,7 @@
import { z } from 'zod';
export const FetchShippingAddressSchema = z.object({
shippingAddressId: z.number().int(),
shippingAddressId: z.number().int().describe('ShippingAddress identifier'),
});
export type FetchShippingAddress = z.infer<typeof FetchShippingAddressSchema>;

View File

@@ -1,4 +1,16 @@
export * from './fetch-customer-cards.schema';
export * from './fetch-customer-shipping-addresses.schema';
export * from './fetch-customer.schema';
export * from './fetch-shipping-address.schema';
export * from './assigned-payer.schema';
export * from './attribute.schema';
export * from './bonus-card.schema';
export * from './branch.schema';
export * from './customer.schema';
export * from './fetch-customer-cards.schema';
export * from './fetch-customer-shipping-addresses.schema';
export * from './fetch-customer.schema';
export * from './fetch-shipping-address.schema';
export * from './linked-record.schema';
export * from './notification-channel.schema';
export * from './payer-status.schema';
export * from './payer.schema';
export * from './payment-settings.schema';
export * from './shipping-address.schema';
export * from './user.schema';

View File

@@ -0,0 +1,8 @@
import { z } from 'zod';
export const LinkedRecordSchema = z.object({
isSource: z.boolean().describe('Whether source').optional(),
number: z.string().describe('Number').optional(),
pk: z.string().describe('Pk').optional(),
repository: z.string().describe('Repository').optional(),
});

View File

@@ -0,0 +1,11 @@
/**
* @deprecated This schema has been moved to @isa/common/data-access.
* Please update imports to use:
* import { NotificationChannelSchema, NotificationChannel } from '@isa/common/data-access';
*
* This re-export will be removed in a future version.
*/
export {
NotificationChannelSchema,
NotificationChannel,
} from '@isa/common/data-access';

View File

@@ -0,0 +1,14 @@
import { z } from 'zod';
export const PayerStatus = {
NotSet: 0,
Blocked: 1,
Check: 2,
LowDegreeOfCreditworthiness: 4,
Dunning1: 8,
Dunning2: 16,
} as const;
export const PayerStatusSchema = z.nativeEnum(PayerStatus).describe('Payer status');
export type PayerStatus = z.infer<typeof PayerStatusSchema>;

View File

@@ -0,0 +1,38 @@
import {
AddressSchema,
CommunicationDetailsSchema,
EntityContainerSchema,
EntitySchema,
GenderSchema,
LabelSchema,
OrganisationSchema,
PayerType,
} from '@isa/common/data-access';
import { z } from 'zod';
import { PayerStatusSchema } from './payer-status.schema';
import { PaymentSettingsSchema } from './payment-settings.schema';
export const PayerSchema = z
.object({
address: AddressSchema.describe('Address').optional(),
agentComment: z.string().describe('Agent comment').optional(),
communicationDetails: CommunicationDetailsSchema.describe('Communication details').optional(),
deactivationComment: z.string().describe('Deactivation comment').optional(),
defaultPaymentPeriod: z.number().describe('Default payment period').optional(),
firstName: z.string().describe('First name').optional(),
gender: GenderSchema.describe('Gender').optional(),
isGuestAccount: z.boolean().describe('Whether guestAccount').optional(),
label: EntityContainerSchema(LabelSchema).describe('Label').optional(),
lastName: z.string().describe('Last name').optional(),
organisation: OrganisationSchema.describe('Organisation information').optional(),
payerGroup: z.string().describe('Payer group').optional(),
payerNumber: z.string().describe('Unique payer account number').optional(),
payerStatus: PayerStatusSchema.describe('Current status of the payer account').optional(),
payerType: z.nativeEnum(PayerType).describe('Payer type').optional(),
paymentTypes: z.array(PaymentSettingsSchema).describe('Payment types').optional(),
standardInvoiceText: z.string().describe('Standard invoice text').optional(),
statusChangeComment: z.string().describe('Status change comment').optional(),
statusComment: z.string().describe('Status comment').optional(),
title: z.string().describe('Title').optional(),
})
.extend(EntitySchema.shape);

View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
export const PaymentSettingsSchema = z.object({
allow: z.string().describe('Allow').optional(),
channel: z.string().describe('Communication channel').optional(),
denaiedReason: z.string().describe('Denaied reason').optional(),
deny: z.string().describe('Deny').optional(),
paymentType: z.number().int().positive().describe('Payment type').optional(),
});

View File

@@ -0,0 +1,26 @@
import {
AddressSchema,
CommunicationDetailsSchema,
EntitySchema,
GenderSchema,
OrganisationSchema,
} from '@isa/common/data-access';
import z from 'zod';
export const ShippingAddressSchema = z
.object({
address: AddressSchema.describe('Address').optional(),
agentomment: z.string().describe('Agentomment').optional(),
communicationDetails: CommunicationDetailsSchema.describe('Communication details').optional(),
firstName: z.string().describe('First name').optional(),
gender: GenderSchema.describe('Gender').optional(),
lastName: z.string().describe('Last name').optional(),
organisation: OrganisationSchema.describe('Organisation information').optional(),
title: z.string().describe('Title').optional(),
type: z.number().describe('Type').optional(),
validated: z.string().describe('Validated').optional(),
validationResult: z.number().describe('Validation result').optional(),
})
.extend(EntitySchema.shape);
export type ShippingAddress = z.infer<typeof ShippingAddressSchema>;

View File

@@ -0,0 +1,13 @@
import { EntitySchema, GenderSchema } from '@isa/common/data-access';
import { z } from 'zod';
export const UserSchema = z
.object({
email: z.string().email().describe('Email address').optional(),
firstName: z.string().describe('First name').optional(),
gender: GenderSchema.describe('Gender').optional(),
lastName: z.string().describe('Last name').optional(),
name: z.string().describe('Name').optional(),
title: z.string().describe('Title').optional(),
})
.extend(EntitySchema.shape);

View File

@@ -1,6 +1,7 @@
import { inject, Injectable } from '@angular/core';
import { CustomerService } from '@generated/swagger/crm-api';
import {
Customer,
FetchCustomerCardsInput,
FetchCustomerCardsSchema,
FetchCustomerInput,
@@ -12,7 +13,7 @@ import {
takeUntilAborted,
} from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
import { BonusCardInfo, Customer } from '../models';
import { BonusCardInfo } from '../models';
import { logger } from '@isa/core/logging';
@Injectable({ providedIn: 'root' })

View File

@@ -1,79 +1,79 @@
import { inject, Injectable } from '@angular/core';
import { ShippingAddressService as GeneratedShippingAddressService } from '@generated/swagger/crm-api';
import {
FetchCustomerShippingAddressesInput,
FetchCustomerShippingAddressesSchema,
FetchShippingAddressInput,
FetchShippingAddressSchema,
} from '../schemas';
import {
catchResponseArgsErrorPipe,
ListResponseArgs,
ResponseArgs,
takeUntilAborted,
} from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
import { ShippingAddress } from '../models';
import { logger } from '@isa/core/logging';
@Injectable({ providedIn: 'root' })
export class ShippingAddressService {
#shippingAddressService = inject(GeneratedShippingAddressService);
#logger = logger(() => ({
service: 'ShippingAddressService',
}));
async fetchCustomerShippingAddresses(
params: FetchCustomerShippingAddressesInput,
abortSignal?: AbortSignal,
): Promise<ListResponseArgs<ShippingAddress>> {
this.#logger.info('Fetching customer shipping addresses from API');
const { customerId, take, skip } =
FetchCustomerShippingAddressesSchema.parse(params);
let req$ = this.#shippingAddressService
.ShippingAddressGetShippingAddresses({ customerId, take, skip })
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
try {
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully fetched customer shipping addresses');
return res as ListResponseArgs<ShippingAddress>;
} catch (error) {
this.#logger.error('Error fetching customer shipping addresses', error);
return {
result: [],
totalCount: 0,
} as unknown as ListResponseArgs<ShippingAddress>;
}
}
async fetchShippingAddress(
params: FetchShippingAddressInput,
abortSignal?: AbortSignal,
): Promise<ResponseArgs<ShippingAddress>> {
this.#logger.info('Fetching shipping address from API');
const { shippingAddressId } = FetchShippingAddressSchema.parse(params);
let req$ = this.#shippingAddressService
.ShippingAddressGetShippingaddress(shippingAddressId)
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
try {
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully fetched shipping address');
return res as ResponseArgs<ShippingAddress>;
} catch (error) {
this.#logger.error('Error fetching shipping address', error);
return undefined as unknown as ResponseArgs<ShippingAddress>;
}
}
}
import { inject, Injectable } from '@angular/core';
import { ShippingAddressService as GeneratedShippingAddressService } from '@generated/swagger/crm-api';
import {
FetchCustomerShippingAddressesInput,
FetchCustomerShippingAddressesSchema,
FetchShippingAddressInput,
FetchShippingAddressSchema,
ShippingAddress,
} from '../schemas';
import {
catchResponseArgsErrorPipe,
ListResponseArgs,
ResponseArgs,
takeUntilAborted,
} from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
import { logger } from '@isa/core/logging';
@Injectable({ providedIn: 'root' })
export class ShippingAddressService {
#shippingAddressService = inject(GeneratedShippingAddressService);
#logger = logger(() => ({
service: 'ShippingAddressService',
}));
async fetchCustomerShippingAddresses(
params: FetchCustomerShippingAddressesInput,
abortSignal?: AbortSignal,
): Promise<ListResponseArgs<ShippingAddress>> {
this.#logger.info('Fetching customer shipping addresses from API');
const { customerId, take, skip } =
FetchCustomerShippingAddressesSchema.parse(params);
let req$ = this.#shippingAddressService
.ShippingAddressGetShippingAddresses({ customerId, take, skip })
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
try {
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully fetched customer shipping addresses');
return res as ListResponseArgs<ShippingAddress>;
} catch (error) {
this.#logger.error('Error fetching customer shipping addresses', error);
return {
result: [],
totalCount: 0,
} as unknown as ListResponseArgs<ShippingAddress>;
}
}
async fetchShippingAddress(
params: FetchShippingAddressInput,
abortSignal?: AbortSignal,
): Promise<ResponseArgs<ShippingAddress>> {
this.#logger.info('Fetching shipping address from API');
const { shippingAddressId } = FetchShippingAddressSchema.parse(params);
let req$ = this.#shippingAddressService
.ShippingAddressGetShippingaddress(shippingAddressId)
.pipe(catchResponseArgsErrorPipe());
if (abortSignal) {
req$ = req$.pipe(takeUntilAborted(abortSignal));
}
try {
const res = await firstValueFrom(req$);
this.#logger.debug('Successfully fetched shipping address');
return res as ResponseArgs<ShippingAddress>;
} catch (error) {
this.#logger.error('Error fetching shipping address', error);
return undefined as unknown as ResponseArgs<ShippingAddress>;
}
}
}