import {
ChangeDetectionStrategy,
Component,
input,
inject,
computed,
effect,
output,
} from '@angular/core';
import {
injectFeedbackDialog,
injectFeedbackErrorDialog,
} from '@isa/ui/dialog';
import { logger } from '@isa/core/logging';
import {
CustomerBonRedemptionFacade,
CustomerBonCheckResource,
BonRedemptionStore,
} from '@isa/crm/data-access';
import { ResponseArgsError } from '@isa/common/data-access';
import { BonInputFieldComponent } from './components/bon-input-field/bon-input-field.component';
import { BonDetailsDisplayComponent } from './components/bon-details-display/bon-details-display.component';
import { BonRedemptionButtonComponent } from './components/bon-redemption-button/bon-redemption-button.component';
/**
* Component for redeeming customer receipts (Bon) for loyalty points.
*
* Allows users to:
* 1. Enter a Bon number
* 2. Validate the Bon exists and is valid
* 3. View Bon summary (date, total)
* 4. Redeem the Bon for customer points
*
* @example
*
*/
@Component({
selector: 'crm-customer-bon-redemption',
imports: [
BonInputFieldComponent,
BonDetailsDisplayComponent,
BonRedemptionButtonComponent,
],
providers: [BonRedemptionStore, CustomerBonCheckResource],
templateUrl: './crm-feature-customer-bon-redemption.component.html',
styleUrl: './crm-feature-customer-bon-redemption.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CrmFeatureCustomerBonRedemptionComponent {
#logger = logger(() => ({
component: 'CrmFeatureCustomerBonRedemptionComponent',
cardCode: this.cardCode(),
}));
#bonCheckResource = inject(CustomerBonCheckResource);
#bonFacade = inject(CustomerBonRedemptionFacade);
#errorFeedbackDialog = injectFeedbackErrorDialog();
#feedbackDialog = injectFeedbackDialog();
/**
* Store for managing Bon redemption state
*/
readonly store = inject(BonRedemptionStore);
/**
* Active loyalty card code for the customer
*/
readonly cardCode = input(undefined);
/**
* Computed: Disable redemption if no card code
*/
readonly disableRedemption = computed(
() => this.store.disableRedemption() || !this.cardCode(),
);
readonly redeemed = output();
/**
* Constructor - sets up effects to sync resource state with store
*/
constructor() {
// Sync resource loading state with store
effect(() => {
const isLoading = this.#bonCheckResource.resource.isLoading();
this.store.setValidating(isLoading);
});
// Sync resource results with store
effect(() => {
const result = this.#bonCheckResource.resource.hasValue()
? this.#bonCheckResource.resource.value()
: undefined;
const error = this.#bonCheckResource.resource.error();
const isLoading = this.#bonCheckResource.resource.isLoading();
const validationAttempted = this.store.validationAttempted();
// Only process results if validation was attempted
if (!validationAttempted || isLoading) {
return;
}
// Handle validation result
if (result) {
this.store.setValidatedBon({
bonNumber: this.store.bonNumber(),
date: result.date ?? '',
total: result.total ?? 0,
});
}
// Check if validation returned no result
else if (!error && !result) {
this.store.setError('Keine verpunktung möglich');
}
// Handle API errors
else if (error) {
const errorMsg = this.#extractErrorMessage(error);
this.store.setError(errorMsg);
}
});
}
/**
* Validate the entered Bon number
*/
validateBon(): void {
const cardCode = this.cardCode();
const bonNr = this.store.bonNumber().trim();
if (!cardCode || !bonNr) {
this.#logger.warn(
'Cannot validate Bon: missing required parameters',
() => ({
hasCardCode: !!cardCode,
hasBonNr: !!bonNr,
}),
);
return;
}
this.#logger.debug('Triggering Bon validation', () => ({
cardCode,
bonNr,
}));
this.store.setValidationAttempted(true);
this.#bonCheckResource.params({ cardCode, bonNr });
}
/**
* Redeem the validated Bon for customer points
*/
async redeemBon() {
this.store.setRedeeming(true);
try {
const cardCode = this.cardCode();
const bonNr = this.store.bonNumber().trim();
const validatedBon = this.store.validatedBon();
if (!cardCode) {
throw new Error('Kein Karten-Code vorhanden');
}
if (!bonNr || !validatedBon) {
throw new Error('Bon muss zuerst validiert werden');
}
this.#logger.debug('Redeeming Bon', () => ({ cardCode, bonNr }));
const success = await this.#bonFacade.addBon({ cardCode, bonNr });
if (!success) {
throw new Error('Bon-Einlösung fehlgeschlagen');
}
this.#logger.info('Bon redeemed successfully', () => ({
bonNr,
total: validatedBon.total,
}));
this.#feedbackDialog({
data: {
message: 'Bon wurde erfolgreich gebucht',
autoClose: true,
autoCloseDelay: 10000,
},
});
// Reset form
this.resetForm();
this.redeemed.emit();
} catch (error: unknown) {
this.#logger.error('Bon redemption failed', error as Error, () => ({
bonNr: this.store.bonNumber(),
}));
let errorMsg = 'Bon-Einlösung fehlgeschlagen';
if (error instanceof ResponseArgsError) {
errorMsg = error.message || errorMsg;
} else if (error instanceof Error) {
errorMsg = error.message;
}
this.#errorFeedbackDialog({
data: {
errorMessage: errorMsg,
},
});
} finally {
this.store.setRedeeming(false);
}
}
/**
* Reset the form to initial state
*/
resetForm(): void {
this.store.reset();
this.#bonCheckResource.reset();
}
/**
* Extract error message from various error types.
* ResponseArgsError already formats invalidProperties into a readable message.
*/
#extractErrorMessage(error: unknown): string {
const defaultMsg = 'Bon-Validierung fehlgeschlagen';
const actualError = (error as { cause?: unknown })?.cause ?? error;
if (actualError instanceof ResponseArgsError) {
return actualError.message || defaultMsg;
}
if (actualError instanceof Error) {
return actualError.message || defaultMsg;
}
return defaultMsg;
}
}