Merged PR 2031: feat(crm): add customer bon redemption feature

feat(crm): add customer bon redemption feature

- New library @isa/crm/feature/customer-bon-redemption
- Implement bon validation and redemption flow
- Add SignalStore for state management
- Add resource pattern for reactive data loading
- Add facade for business logic abstraction
- Add Zod schemas for runtime validation
- Integrate with loyalty card API endpoints
- Add accessibility and E2E test attributes
- Remove mock provider (use real facade)
- Exclude generated swagger files from linting

Components:
- BonInputFieldComponent - input with validation
- BonDetailsDisplayComponent - shows validated bon
- BonRedemptionButtonComponent - redemption action

Data Access:
- CustomerBonRedemptionFacade - business logic
- CustomerBonCheckResource - reactive validation
- BonRedemptionStore - component state
- CrmSearchService - API integration (checkBon, addBon)

Issue: 5314

Related work items: #5314
This commit is contained in:
Lorenz Hilpert
2025-11-19 12:51:58 +00:00
committed by Nino Righi
parent 8c0de558a4
commit fc6d29d62f
43 changed files with 1358 additions and 49 deletions

View File

@@ -11,10 +11,17 @@
class="mt-4"
/>
@let cardCode = firstActiveCardCode();
@if (cardCode) {
<crm-customer-bon-redemption
[cardCode]="cardCode"
class="mt-4"
(redeemed)="reloadCardTransactions()"
/>
<crm-customer-booking [cardCode]="cardCode" class="mt-4" />
<crm-customer-card-transactions [cardCode]="cardCode" class="mt-8" />
}
<crm-customer-card-transactions [cardCode]="cardCode" class="mt-8" />
<utils-scroll-top-button
[target]="hostElement"
class="flex flex-col justify-self-end fixed bottom-6 right-6"

View File

@@ -5,6 +5,7 @@ import {
computed,
effect,
ElementRef,
OnDestroy,
} from '@angular/core';
import { CustomerSearchStore } from '../store';
import { ActivatedRoute } from '@angular/router';
@@ -14,8 +15,12 @@ import { CustomerMenuComponent } from '../../components/customer-menu';
import { CustomerLoyaltyCardsComponent } from '@isa/crm/feature/customer-loyalty-cards';
import { CrmFeatureCustomerCardTransactionsComponent } from '@isa/crm/feature/customer-card-transactions';
import { toSignal } from '@angular/core/rxjs-interop';
import { CustomerBonusCardsResource } from '@isa/crm/data-access';
import {
CustomerBonusCardsResource,
CustomerCardTransactionsResource,
} from '@isa/crm/data-access';
import { CrmFeatureCustomerBookingComponent } from '@isa/crm/feature/customer-booking';
import { CrmFeatureCustomerBonRedemptionComponent } from '@isa/crm/feature/customer-bon-redemption';
import { ScrollTopButtonComponent } from '@isa/utils/scroll-position';
@Component({
@@ -30,15 +35,18 @@ import { ScrollTopButtonComponent } from '@isa/utils/scroll-position';
CustomerLoyaltyCardsComponent,
CrmFeatureCustomerCardTransactionsComponent,
CrmFeatureCustomerBookingComponent,
CrmFeatureCustomerBonRedemptionComponent,
ScrollTopButtonComponent,
],
providers: [CustomerBonusCardsResource],
providers: [CustomerBonusCardsResource, CustomerCardTransactionsResource],
})
export class KundenkarteMainViewComponent {
export class KundenkarteMainViewComponent implements OnDestroy {
#reloadTimeoutId?: ReturnType<typeof setTimeout>;
private _store = inject(CustomerSearchStore);
private _activatedRoute = inject(ActivatedRoute);
private _bonusCardsResource = inject(CustomerBonusCardsResource);
#cardTransactionsResource = inject(CustomerCardTransactionsResource);
elementRef = inject(ElementRef);
get hostElement() {
@@ -74,4 +82,16 @@ export class KundenkarteMainViewComponent {
}
});
}
reloadCardTransactions() {
this.#reloadTimeoutId = setTimeout(() => {
this.#cardTransactionsResource.resource.reload();
}, 500);
}
ngOnDestroy(): void {
if (this.#reloadTimeoutId) {
clearTimeout(this.#reloadTimeoutId);
}
}
}