mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
2 Commits
7e7721b222
...
fix/5411-R
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
888e505f1a | ||
|
|
b28f79cd1d |
@@ -36,13 +36,21 @@ import { CrmCustomerService } from '@domain/crm';
|
||||
import { MessageModalComponent, MessageModalData } from '@modal/message';
|
||||
import { GenderSettingsService } from '@shared/services/gender';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { CrmTabMetadataService, Customer } from '@isa/crm/data-access';
|
||||
import { CustomerAdapter } from '@isa/checkout/data-access';
|
||||
import {
|
||||
CrmTabMetadataService,
|
||||
Customer,
|
||||
AssignedPayer,
|
||||
} from '@isa/crm/data-access';
|
||||
import {
|
||||
CustomerAdapter,
|
||||
ShippingAddressAdapter,
|
||||
} from '@isa/checkout/data-access';
|
||||
import {
|
||||
NavigateAfterRewardSelection,
|
||||
RewardSelectionPopUpService,
|
||||
} from '@isa/checkout/shared/reward-selection-dialog';
|
||||
import { NavigationStateService } from '@isa/core/navigation';
|
||||
import { ShippingAddressDTO as CrmShippingAddressDTO } from '@generated/swagger/crm-api';
|
||||
|
||||
export interface CustomerDetailsViewMainState {
|
||||
isBusy: boolean;
|
||||
@@ -407,9 +415,9 @@ export class CustomerDetailsViewMainComponent
|
||||
|
||||
await this._updateNotifcationChannelsAsync(currentBuyer);
|
||||
|
||||
this._setPayer();
|
||||
await this._setPayer();
|
||||
|
||||
this._setShippingAddress();
|
||||
await this._setShippingAddress();
|
||||
|
||||
// #5262 Check for reward selection flow before navigation
|
||||
if (this.hasReturnUrl()) {
|
||||
@@ -631,8 +639,46 @@ export class CustomerDetailsViewMainComponent
|
||||
}
|
||||
}
|
||||
|
||||
@log
|
||||
_setPayer() {
|
||||
@logAsync
|
||||
async _setPayer() {
|
||||
// Check if there's a selected payer in metadata (from previous address selection)
|
||||
const selectedPayerId = this.crmTabMetadataService.selectedPayerId(
|
||||
this.processId,
|
||||
);
|
||||
|
||||
if (selectedPayerId) {
|
||||
// Load the selected payer from metadata
|
||||
try {
|
||||
const payerResponse = await this.customerService
|
||||
.getPayer(selectedPayerId)
|
||||
.toPromise();
|
||||
|
||||
if (payerResponse?.result) {
|
||||
// Create AssignedPayer structure expected by adapter
|
||||
// Type cast needed due to incompatible enum types between CRM and Checkout APIs
|
||||
const assignedPayer = {
|
||||
payer: {
|
||||
id: selectedPayerId,
|
||||
data: payerResponse.result,
|
||||
},
|
||||
} as AssignedPayer;
|
||||
|
||||
const payer = CustomerAdapter.toPayerFromAssignedPayer(assignedPayer);
|
||||
|
||||
if (payer) {
|
||||
this._checkoutService.setPayer({
|
||||
processId: this.processId,
|
||||
payer,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load selected payer from metadata', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to current payer from component state
|
||||
if (this.payer) {
|
||||
this._checkoutService.setPayer({
|
||||
processId: this.processId,
|
||||
@@ -641,8 +687,41 @@ export class CustomerDetailsViewMainComponent
|
||||
}
|
||||
}
|
||||
|
||||
@log
|
||||
_setShippingAddress() {
|
||||
@logAsync
|
||||
async _setShippingAddress() {
|
||||
// Check if there's a selected shipping address in metadata (from previous address selection)
|
||||
const selectedShippingAddressId =
|
||||
this.crmTabMetadataService.selectedShippingAddressId(this.processId);
|
||||
|
||||
if (selectedShippingAddressId) {
|
||||
// Load the selected shipping address from metadata
|
||||
try {
|
||||
const addressResponse = await this.customerService
|
||||
.getShippingAddress(selectedShippingAddressId)
|
||||
.toPromise();
|
||||
|
||||
if (addressResponse?.result) {
|
||||
const shippingAddress = ShippingAddressAdapter.fromCrmShippingAddress(
|
||||
addressResponse.result as CrmShippingAddressDTO,
|
||||
);
|
||||
|
||||
if (shippingAddress) {
|
||||
this._checkoutService.setShippingAddress({
|
||||
processId: this.processId,
|
||||
shippingAddress,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Failed to load selected shipping address from metadata',
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to current shipping address from component state
|
||||
if (this.shippingAddress) {
|
||||
this._checkoutService.setShippingAddress({
|
||||
processId: this.processId,
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
import {
|
||||
SelectedCustomerResource,
|
||||
getCustomerName,
|
||||
SelectedCustomerShippingAddressResource,
|
||||
SelectedCustomerPayerAddressResource,
|
||||
} from '@isa/crm/data-access';
|
||||
import { isaActionEdit } from '@isa/icons';
|
||||
import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
@@ -25,10 +27,19 @@ import { NavigationStateService } from '@isa/core/navigation';
|
||||
})
|
||||
export class BillingAndShippingAddressCardComponent {
|
||||
#navigationState = inject(NavigationStateService);
|
||||
#shippingAddressResource = inject(SelectedCustomerShippingAddressResource);
|
||||
#payerAddressResource = inject(SelectedCustomerPayerAddressResource);
|
||||
|
||||
tabId = injectTabId();
|
||||
#customerResource = inject(SelectedCustomerResource).resource;
|
||||
|
||||
isLoading = this.#customerResource.isLoading;
|
||||
isLoading = computed(() => {
|
||||
return (
|
||||
this.#customerResource.isLoading() ||
|
||||
this.#shippingAddressResource.resource.isLoading() ||
|
||||
this.#payerAddressResource.resource.isLoading()
|
||||
);
|
||||
});
|
||||
|
||||
customer = computed(() => {
|
||||
return this.#customerResource.value();
|
||||
@@ -49,26 +60,44 @@ export class BillingAndShippingAddressCardComponent {
|
||||
}
|
||||
|
||||
payer = computed(() => {
|
||||
// Prefer selected payer from metadata over customer as payer
|
||||
const selectedPayer = this.#payerAddressResource.resource.value();
|
||||
if (selectedPayer) {
|
||||
return selectedPayer;
|
||||
}
|
||||
// Fallback to customer as payer
|
||||
return this.customer();
|
||||
});
|
||||
|
||||
payerName = computed(() => {
|
||||
return getCustomerName(this.payer());
|
||||
const payer = this.payer();
|
||||
return getCustomerName(payer);
|
||||
});
|
||||
|
||||
payerAddress = computed(() => {
|
||||
return this.customer()?.address;
|
||||
const payer = this.payer();
|
||||
if (!payer) return undefined;
|
||||
return payer.address;
|
||||
});
|
||||
|
||||
shippingAddress = computed(() => {
|
||||
// Prefer selected shipping address from metadata over customer default
|
||||
const selectedAddress = this.#shippingAddressResource.resource.value();
|
||||
if (selectedAddress) {
|
||||
return selectedAddress;
|
||||
}
|
||||
// Fallback to customer
|
||||
return this.customer();
|
||||
});
|
||||
|
||||
shippingName = computed(() => {
|
||||
return getCustomerName(this.shippingAddress());
|
||||
const shipping = this.shippingAddress();
|
||||
return getCustomerName(shipping);
|
||||
});
|
||||
|
||||
shippingAddressAddress = computed(() => {
|
||||
return this.shippingAddress()?.address;
|
||||
const shipping = this.shippingAddress();
|
||||
if (!shipping) return undefined;
|
||||
return shipping.address;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { effect, inject, Injectable, resource, signal } from '@angular/core';
|
||||
import { CrmTabMetadataService, PayerService } from '../services';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { CrmPayer } from '../schemas';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerPayerAddressResource {
|
||||
#payerService = inject(PayerService);
|
||||
|
||||
#params = signal<{
|
||||
payerId: number | undefined;
|
||||
}>({
|
||||
payerId: undefined,
|
||||
});
|
||||
|
||||
params(params: { payerId?: number }) {
|
||||
this.#params.update((p) => ({ ...p, ...params }));
|
||||
}
|
||||
|
||||
readonly resource = resource({
|
||||
params: () => this.#params(),
|
||||
loader: async ({ params, abortSignal }): Promise<CrmPayer | undefined> => {
|
||||
if (!params.payerId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const res = await this.#payerService.fetchPayer(
|
||||
{
|
||||
payerId: params.payerId,
|
||||
},
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
return res.result as CrmPayer;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SelectedCustomerPayerAddressResource extends CustomerPayerAddressResource {
|
||||
#tabId = inject(TabService).activatedTabId;
|
||||
#customerMetadata = inject(CrmTabMetadataService);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
effect(() => {
|
||||
const tabId = this.#tabId();
|
||||
const payerId = tabId
|
||||
? this.#customerMetadata.selectedPayerId(tabId)
|
||||
: undefined;
|
||||
this.params({ payerId });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './country.resource';
|
||||
export * from './customer-payer-address.resource';
|
||||
export * from './primary-customer-card.resource';
|
||||
export * from './customer-shipping-address.resource';
|
||||
export * from './customer-shipping-addresses.resource';
|
||||
export * from './customer.resource';
|
||||
export * from './payer.resource';
|
||||
|
||||
52
libs/crm/data-access/src/lib/resources/payer.resource.ts
Normal file
52
libs/crm/data-access/src/lib/resources/payer.resource.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { effect, inject, Injectable, resource, signal } from '@angular/core';
|
||||
import { CrmTabMetadataService } from '../services';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { PayerDTO } from '@generated/swagger/crm-api';
|
||||
|
||||
@Injectable()
|
||||
export class PayerResource {
|
||||
#customerService = inject(CrmCustomerService);
|
||||
|
||||
#params = signal<{
|
||||
payerId: number | undefined;
|
||||
}>({
|
||||
payerId: undefined,
|
||||
});
|
||||
|
||||
params(params: { payerId?: number }) {
|
||||
this.#params.update((p) => ({ ...p, ...params }));
|
||||
}
|
||||
|
||||
readonly resource = resource({
|
||||
params: () => this.#params(),
|
||||
loader: async ({ params, abortSignal }): Promise<PayerDTO | undefined> => {
|
||||
if (!params.payerId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const res = await this.#customerService
|
||||
.getPayer(params.payerId)
|
||||
.toPromise();
|
||||
|
||||
return res?.result;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SelectedPayerResource extends PayerResource {
|
||||
#tabId = inject(TabService).activatedTabId;
|
||||
#customerMetadata = inject(CrmTabMetadataService);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
effect(() => {
|
||||
const tabId = this.#tabId();
|
||||
const payerId = tabId
|
||||
? this.#customerMetadata.selectedPayerId(tabId)
|
||||
: undefined;
|
||||
this.params({ payerId });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FetchPayerSchema = z.object({
|
||||
payerId: z.number().int().describe('Payer identifier'),
|
||||
});
|
||||
|
||||
export type FetchPayer = z.infer<typeof FetchPayerSchema>;
|
||||
export type FetchPayerInput = z.input<typeof FetchPayerSchema>;
|
||||
@@ -8,6 +8,7 @@ export * from './customer-feature-groups.schema';
|
||||
export * from './fetch-customer-cards.schema';
|
||||
export * from './fetch-customer-shipping-addresses.schema';
|
||||
export * from './fetch-customer.schema';
|
||||
export * from './fetch-payer.schema';
|
||||
export * from './fetch-shipping-address.schema';
|
||||
export * from './linked-record.schema';
|
||||
export * from './notification-channel.schema';
|
||||
|
||||
@@ -16,23 +16,43 @@ export const PayerSchema = z
|
||||
.object({
|
||||
address: AddressSchema.describe('Address').optional(),
|
||||
agentComment: z.string().describe('Agent comment').optional(),
|
||||
communicationDetails: CommunicationDetailsSchema.describe('Communication details').optional(),
|
||||
communicationDetails: CommunicationDetailsSchema.describe(
|
||||
'Communication details',
|
||||
).optional(),
|
||||
deactivationComment: z.string().describe('Deactivation comment').optional(),
|
||||
defaultPaymentPeriod: z.number().describe('Default payment period').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(),
|
||||
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(),
|
||||
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(),
|
||||
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);
|
||||
|
||||
export type CrmPayer = z.infer<typeof PayerSchema>;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './country.service';
|
||||
export * from './crm-search.service';
|
||||
export * from './crm-tab-metadata.service';
|
||||
export * from './payer.service';
|
||||
export * from './shipping-address.service';
|
||||
|
||||
43
libs/crm/data-access/src/lib/services/payer.service.ts
Normal file
43
libs/crm/data-access/src/lib/services/payer.service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { PayerService as GeneratedPayerService } from '@generated/swagger/crm-api';
|
||||
import {
|
||||
catchResponseArgsErrorPipe,
|
||||
ResponseArgs,
|
||||
takeUntilAborted,
|
||||
} from '@isa/common/data-access';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { FetchPayerInput, FetchPayerSchema, CrmPayer } from '../schemas';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PayerService {
|
||||
#payerService = inject(GeneratedPayerService);
|
||||
#logger = logger(() => ({
|
||||
service: 'PayerService',
|
||||
}));
|
||||
|
||||
async fetchPayer(
|
||||
params: FetchPayerInput,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ResponseArgs<CrmPayer>> {
|
||||
this.#logger.info('Fetching payer from API');
|
||||
const { payerId } = FetchPayerSchema.parse(params);
|
||||
|
||||
let req$ = this.#payerService
|
||||
.PayerGetPayer(payerId)
|
||||
.pipe(catchResponseArgsErrorPipe());
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await firstValueFrom(req$);
|
||||
this.#logger.debug('Successfully fetched payer');
|
||||
return res as ResponseArgs<CrmPayer>;
|
||||
} catch (error) {
|
||||
this.#logger.error('Error fetching payer', error);
|
||||
return undefined as unknown as ResponseArgs<CrmPayer>;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user