mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
4 Commits
0a1f25a1ee
...
feature/53
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50363790c1 | ||
|
|
65491fb0d4 | ||
|
|
1f2eff8615 | ||
|
|
442707774b |
@@ -10,6 +10,7 @@
|
||||
[tabId]="processId$ | async"
|
||||
class="mt-4"
|
||||
/>
|
||||
<crm-customer-booking [cardCode]="firstActiveCardCode()" class="mt-4" />
|
||||
<crm-customer-card-transactions
|
||||
[cardCode]="firstActiveCardCode()"
|
||||
class="mt-8"
|
||||
|
||||
@@ -15,6 +15,7 @@ import { CustomerLoyaltyCardsComponent } from '@isa/crm/feature/customer-loyalty
|
||||
import { CrmFeatureCustomerCardTransactionsComponent } from '@isa/crm/feature/customer-card-transactions';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { CustomerBonusCardsResource } from '@isa/crm/data-access';
|
||||
import { CrmFeatureCustomerBookingComponent } from '@isa/crm/feature/customer-booking';
|
||||
import { ScrollTopButtonComponent } from '@isa/utils/scroll-position';
|
||||
|
||||
@Component({
|
||||
@@ -28,6 +29,7 @@ import { ScrollTopButtonComponent } from '@isa/utils/scroll-position';
|
||||
AsyncPipe,
|
||||
CustomerLoyaltyCardsComponent,
|
||||
CrmFeatureCustomerCardTransactionsComponent,
|
||||
CrmFeatureCustomerBookingComponent,
|
||||
ScrollTopButtonComponent,
|
||||
],
|
||||
providers: [CustomerBonusCardsResource],
|
||||
|
||||
@@ -76,6 +76,15 @@ export { EntityDTOBaseOfCustomerInfoDTOAndICustomer } from './models/entity-dtob
|
||||
export { QueryTokenDTO } from './models/query-token-dto';
|
||||
export { QueryTokenDTO2 } from './models/query-token-dto2';
|
||||
export { ResponseArgsOfCustomerDTO } from './models/response-args-of-customer-dto';
|
||||
export { ResponseArgsOfAccountDetailsDTO } from './models/response-args-of-account-details-dto';
|
||||
export { AccountDetailsDTO } from './models/account-details-dto';
|
||||
export { AccountBalanceDTO } from './models/account-balance-dto';
|
||||
export { IdentifierDTO } from './models/identifier-dto';
|
||||
export { StateLevelDTO } from './models/state-level-dto';
|
||||
export { MembershipDetailsDTO } from './models/membership-details-dto';
|
||||
export { CustomPropertyDTO } from './models/custom-property-dto';
|
||||
export { OptinDTO } from './models/optin-dto';
|
||||
export { AddLoyaltyCardValues } from './models/add-loyalty-card-values';
|
||||
export { SaveCustomerValues } from './models/save-customer-values';
|
||||
export { ResponseArgsOfAssignedPayerDTO } from './models/response-args-of-assigned-payer-dto';
|
||||
export { ResponseArgsOfBoolean } from './models/response-args-of-boolean';
|
||||
@@ -92,7 +101,8 @@ export { DiffDTO } from './models/diff-dto';
|
||||
export { ResponseArgsOfIQueryResultOfLoyaltyBookingInfoDTO } from './models/response-args-of-iquery-result-of-loyalty-booking-info-dto';
|
||||
export { IQueryResultOfLoyaltyBookingInfoDTO } from './models/iquery-result-of-loyalty-booking-info-dto';
|
||||
export { LoyaltyBookingInfoDTO } from './models/loyalty-booking-info-dto';
|
||||
export { ResponseArgsOfIEnumerableOfString } from './models/response-args-of-ienumerable-of-string';
|
||||
export { ResponseArgsOfIEnumerableOfKeyValueDTOOfStringAndInteger } from './models/response-args-of-ienumerable-of-key-value-dtoof-string-and-integer';
|
||||
export { KeyValueDTOOfStringAndInteger } from './models/key-value-dtoof-string-and-integer';
|
||||
export { ResponseArgsOfKeyValueDTOOfStringAndString } from './models/response-args-of-key-value-dtoof-string-and-string';
|
||||
export { ResponseArgsOfLoyaltyBookingInfoDTO } from './models/response-args-of-loyalty-booking-info-dto';
|
||||
export { LoyaltyBookingValues } from './models/loyalty-booking-values';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/* tslint:disable */
|
||||
export interface AccountBalanceDTO {
|
||||
lockedPoints: number;
|
||||
points: number;
|
||||
}
|
||||
14
generated/swagger/crm-api/src/models/account-details-dto.ts
Normal file
14
generated/swagger/crm-api/src/models/account-details-dto.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/* tslint:disable */
|
||||
import { AccountBalanceDTO } from './account-balance-dto';
|
||||
import { IdentifierDTO } from './identifier-dto';
|
||||
import { StateLevelDTO } from './state-level-dto';
|
||||
import { MembershipDetailsDTO } from './membership-details-dto';
|
||||
export interface AccountDetailsDTO {
|
||||
accountBalance?: AccountBalanceDTO;
|
||||
accountId?: string;
|
||||
createdAt?: string;
|
||||
identifiers?: Array<IdentifierDTO>;
|
||||
level?: StateLevelDTO;
|
||||
memberships?: Array<MembershipDetailsDTO>;
|
||||
status?: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* tslint:disable */
|
||||
export interface AddLoyaltyCardValues {
|
||||
|
||||
/**
|
||||
* Card code
|
||||
*/
|
||||
cardCode?: string;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/* tslint:disable */
|
||||
export interface CustomPropertyDTO {
|
||||
name?: string;
|
||||
value?: string;
|
||||
}
|
||||
8
generated/swagger/crm-api/src/models/identifier-dto.ts
Normal file
8
generated/swagger/crm-api/src/models/identifier-dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/* tslint:disable */
|
||||
export interface IdentifierDTO {
|
||||
code?: string;
|
||||
displayCode?: string;
|
||||
identifierId?: string;
|
||||
status?: string;
|
||||
type?: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* tslint:disable */
|
||||
export interface KeyValueDTOOfStringAndInteger {
|
||||
command?: string;
|
||||
description?: string;
|
||||
enabled?: boolean;
|
||||
group?: string;
|
||||
key?: string;
|
||||
label?: string;
|
||||
selected?: boolean;
|
||||
sort?: number;
|
||||
value: number;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* tslint:disable */
|
||||
import { CustomPropertyDTO } from './custom-property-dto';
|
||||
import { OptinDTO } from './optin-dto';
|
||||
export interface MembershipDetailsDTO {
|
||||
birthDate?: string;
|
||||
city?: string;
|
||||
countryCode?: string;
|
||||
customProperties?: Array<CustomPropertyDTO>;
|
||||
emailAddress?: string;
|
||||
familyName?: string;
|
||||
genderCode?: string;
|
||||
givenName?: string;
|
||||
memberRole?: string;
|
||||
membershipId?: string;
|
||||
optins?: Array<OptinDTO>;
|
||||
streetHouseNo?: string;
|
||||
userId?: string;
|
||||
zipCode?: string;
|
||||
}
|
||||
5
generated/swagger/crm-api/src/models/optin-dto.ts
Normal file
5
generated/swagger/crm-api/src/models/optin-dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/* tslint:disable */
|
||||
export interface OptinDTO {
|
||||
flag: boolean;
|
||||
type?: string;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/* tslint:disable */
|
||||
import { ResponseArgs } from './response-args';
|
||||
import { AccountDetailsDTO } from './account-details-dto';
|
||||
export interface ResponseArgsOfAccountDetailsDTO extends ResponseArgs{
|
||||
result?: AccountDetailsDTO;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/* tslint:disable */
|
||||
import { ResponseArgs } from './response-args';
|
||||
import { KeyValueDTOOfStringAndInteger } from './key-value-dtoof-string-and-integer';
|
||||
export interface ResponseArgsOfIEnumerableOfKeyValueDTOOfStringAndInteger extends ResponseArgs{
|
||||
result?: Array<KeyValueDTOOfStringAndInteger>;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/* tslint:disable */
|
||||
import { ResponseArgs } from './response-args';
|
||||
export interface ResponseArgsOfIEnumerableOfString extends ResponseArgs{
|
||||
result?: Array<string>;
|
||||
}
|
||||
10
generated/swagger/crm-api/src/models/state-level-dto.ts
Normal file
10
generated/swagger/crm-api/src/models/state-level-dto.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/* tslint:disable */
|
||||
export interface StateLevelDTO {
|
||||
currentStatePoints?: number;
|
||||
name?: string;
|
||||
neededStatePoints?: number;
|
||||
neededStatePointsNextLevel?: number;
|
||||
requiredPointsToMaintainLevel?: number;
|
||||
requiredPointsToReachNextLevel?: number;
|
||||
validTo?: string;
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import { ResponseArgsOfCustomerDTO } from '../models/response-args-of-customer-d
|
||||
import { SaveCustomerValues } from '../models/save-customer-values';
|
||||
import { CustomerDTO } from '../models/customer-dto';
|
||||
import { ResponseArgsOfBoolean } from '../models/response-args-of-boolean';
|
||||
import { ResponseArgsOfAccountDetailsDTO } from '../models/response-args-of-account-details-dto';
|
||||
import { AddLoyaltyCardValues } from '../models/add-loyalty-card-values';
|
||||
import { ResponseArgsOfAssignedPayerDTO } from '../models/response-args-of-assigned-payer-dto';
|
||||
import { ResponseArgsOfIEnumerableOfCustomerInfoDTO } from '../models/response-args-of-ienumerable-of-customer-info-dto';
|
||||
import { ResponseArgsOfIEnumerableOfBonusCardInfoDTO } from '../models/response-args-of-ienumerable-of-bonus-card-info-dto';
|
||||
@@ -35,6 +37,7 @@ class CustomerService extends __BaseService {
|
||||
static readonly CustomerUpdateCustomerPath = '/customer/{customerId}';
|
||||
static readonly CustomerPatchCustomerPath = '/customer/{customerId}';
|
||||
static readonly CustomerDeleteCustomerPath = '/customer/{customerId}';
|
||||
static readonly CustomerAddLoyaltyCardPath = '/customer/{customerId}/loyalty/add-card';
|
||||
static readonly CustomerCreateCustomerPath = '/customer';
|
||||
static readonly CustomerAddPayerReferencePath = '/customer/{customerId}/payer';
|
||||
static readonly CustomerDeactivateCustomerPath = '/customer/{customerId}/deactivate';
|
||||
@@ -389,6 +392,56 @@ class CustomerService extends __BaseService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kundenkarte hinzufügen
|
||||
* @param params The `CustomerService.CustomerAddLoyaltyCardParams` containing the following parameters:
|
||||
*
|
||||
* - `loyaltyCardValues`:
|
||||
*
|
||||
* - `customerId`:
|
||||
*
|
||||
* - `locale`:
|
||||
*/
|
||||
CustomerAddLoyaltyCardResponse(params: CustomerService.CustomerAddLoyaltyCardParams): __Observable<__StrictHttpResponse<ResponseArgsOfAccountDetailsDTO>> {
|
||||
let __params = this.newParams();
|
||||
let __headers = new HttpHeaders();
|
||||
let __body: any = null;
|
||||
__body = params.loyaltyCardValues;
|
||||
|
||||
if (params.locale != null) __params = __params.set('locale', params.locale.toString());
|
||||
let req = new HttpRequest<any>(
|
||||
'POST',
|
||||
this.rootUrl + `/customer/${encodeURIComponent(String(params.customerId))}/loyalty/add-card`,
|
||||
__body,
|
||||
{
|
||||
headers: __headers,
|
||||
params: __params,
|
||||
responseType: 'json'
|
||||
});
|
||||
|
||||
return this.http.request<any>(req).pipe(
|
||||
__filter(_r => _r instanceof HttpResponse),
|
||||
__map((_r) => {
|
||||
return _r as __StrictHttpResponse<ResponseArgsOfAccountDetailsDTO>;
|
||||
})
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Kundenkarte hinzufügen
|
||||
* @param params The `CustomerService.CustomerAddLoyaltyCardParams` containing the following parameters:
|
||||
*
|
||||
* - `loyaltyCardValues`:
|
||||
*
|
||||
* - `customerId`:
|
||||
*
|
||||
* - `locale`:
|
||||
*/
|
||||
CustomerAddLoyaltyCard(params: CustomerService.CustomerAddLoyaltyCardParams): __Observable<ResponseArgsOfAccountDetailsDTO> {
|
||||
return this.CustomerAddLoyaltyCardResponse(params).pipe(
|
||||
__map(_r => _r.body as ResponseArgsOfAccountDetailsDTO)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Anlage eines neuen Kunden
|
||||
* @param payload Kundendaten
|
||||
@@ -861,6 +914,15 @@ module CustomerService {
|
||||
deletionComment?: null | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for CustomerAddLoyaltyCard
|
||||
*/
|
||||
export interface CustomerAddLoyaltyCardParams {
|
||||
loyaltyCardValues: AddLoyaltyCardValues;
|
||||
customerId: number;
|
||||
locale?: null | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for CustomerAddPayerReference
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@ import { map as __map, filter as __filter } from 'rxjs/operators';
|
||||
import { ResponseArgsOfIQueryResultOfLoyaltyBookingInfoDTO } from '../models/response-args-of-iquery-result-of-loyalty-booking-info-dto';
|
||||
import { ResponseArgsOfLoyaltyBookingInfoDTO } from '../models/response-args-of-loyalty-booking-info-dto';
|
||||
import { LoyaltyBookingValues } from '../models/loyalty-booking-values';
|
||||
import { ResponseArgsOfIEnumerableOfString } from '../models/response-args-of-ienumerable-of-string';
|
||||
import { ResponseArgsOfIEnumerableOfKeyValueDTOOfStringAndInteger } from '../models/response-args-of-ienumerable-of-key-value-dtoof-string-and-integer';
|
||||
import { ResponseArgsOfKeyValueDTOOfStringAndString } from '../models/response-args-of-key-value-dtoof-string-and-string';
|
||||
import { ResponseArgsOfBoolean } from '../models/response-args-of-boolean';
|
||||
import { LoyaltyBonValues } from '../models/loyalty-bon-values';
|
||||
@@ -133,7 +133,7 @@ class LoyaltyCardService extends __BaseService {
|
||||
/**
|
||||
* Booking reason / Buchungsgründe
|
||||
*/
|
||||
LoyaltyCardBookingReasonResponse(): __Observable<__StrictHttpResponse<ResponseArgsOfIEnumerableOfString>> {
|
||||
LoyaltyCardBookingReasonResponse(): __Observable<__StrictHttpResponse<ResponseArgsOfIEnumerableOfKeyValueDTOOfStringAndInteger>> {
|
||||
let __params = this.newParams();
|
||||
let __headers = new HttpHeaders();
|
||||
let __body: any = null;
|
||||
@@ -150,16 +150,16 @@ class LoyaltyCardService extends __BaseService {
|
||||
return this.http.request<any>(req).pipe(
|
||||
__filter(_r => _r instanceof HttpResponse),
|
||||
__map((_r) => {
|
||||
return _r as __StrictHttpResponse<ResponseArgsOfIEnumerableOfString>;
|
||||
return _r as __StrictHttpResponse<ResponseArgsOfIEnumerableOfKeyValueDTOOfStringAndInteger>;
|
||||
})
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Booking reason / Buchungsgründe
|
||||
*/
|
||||
LoyaltyCardBookingReason(): __Observable<ResponseArgsOfIEnumerableOfString> {
|
||||
LoyaltyCardBookingReason(): __Observable<ResponseArgsOfIEnumerableOfKeyValueDTOOfStringAndInteger> {
|
||||
return this.LoyaltyCardBookingReasonResponse().pipe(
|
||||
__map(_r => _r.body as ResponseArgsOfIEnumerableOfString)
|
||||
__map(_r => _r.body as ResponseArgsOfIEnumerableOfKeyValueDTOOfStringAndInteger)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { CrmSearchService } from '../services/crm-search.service';
|
||||
import { AddBookingInput } from '../schemas';
|
||||
import {
|
||||
KeyValueDTOOfStringAndInteger,
|
||||
KeyValueDTOOfStringAndString,
|
||||
LoyaltyBookingInfoDTO,
|
||||
} from '@generated/swagger/crm-api';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CustomerCardBookingFacade {
|
||||
#crmSearchService = inject(CrmSearchService);
|
||||
|
||||
async fetchBookingReasons(
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<KeyValueDTOOfStringAndInteger[]> {
|
||||
return this.#crmSearchService.fetchBookingReasons(abortSignal);
|
||||
}
|
||||
|
||||
async fetchCurrentBookingPartnerStore(
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<KeyValueDTOOfStringAndString | undefined> {
|
||||
return this.#crmSearchService.fetchCurrentBookingPartnerStore(abortSignal);
|
||||
}
|
||||
|
||||
async addBooking(
|
||||
params: AddBookingInput,
|
||||
): Promise<LoyaltyBookingInfoDTO | undefined> {
|
||||
return this.#crmSearchService.addBooking(params);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './customer-cards.facade';
|
||||
export * from './customer.facade';
|
||||
export * from './customer-card-booking.facade';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Injectable, inject, resource } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { CrmSearchService } from '@isa/crm/data-access';
|
||||
import { KeyValueDTOOfStringAndInteger } from '@generated/swagger/crm-api';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerBookingReasonsResource {
|
||||
readonly #crmSearchService = inject(CrmSearchService);
|
||||
readonly #logger = logger(() => ({
|
||||
context: 'CustomerBookingReasonsResource',
|
||||
}));
|
||||
|
||||
readonly resource = resource({
|
||||
loader: async ({
|
||||
abortSignal,
|
||||
}): Promise<KeyValueDTOOfStringAndInteger[] | undefined> => {
|
||||
this.#logger.debug('Loading Booking Reasons');
|
||||
|
||||
const reasons =
|
||||
await this.#crmSearchService.fetchBookingReasons(abortSignal);
|
||||
|
||||
this.#logger.debug('Booking Reasons loaded', () => ({
|
||||
count: reasons.length,
|
||||
}));
|
||||
|
||||
return reasons;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -15,9 +15,6 @@ import { LoyaltyBookingInfoDTO } from '@generated/swagger/crm-api';
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* providers: [CustomerCardTransactionsResource],
|
||||
* })
|
||||
* export class MyFeatureComponent {
|
||||
* #transactionsResource = inject(CustomerCardTransactionsResource);
|
||||
*
|
||||
@@ -30,7 +27,7 @@ import { LoyaltyBookingInfoDTO } from '@generated/swagger/crm-api';
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CustomerCardTransactionsResource {
|
||||
readonly #crmSearchService = inject(CrmSearchService);
|
||||
readonly #logger = logger(() => ({
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from './customer-shipping-address.resource';
|
||||
export * from './customer-shipping-addresses.resource';
|
||||
export * from './customer.resource';
|
||||
export * from './payer.resource';
|
||||
export * from './customer-booking-reasons.resource';
|
||||
|
||||
18
libs/crm/data-access/src/lib/schemas/add-booking.schema.ts
Normal file
18
libs/crm/data-access/src/lib/schemas/add-booking.schema.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const AddBookingSchema = z.object({
|
||||
cardCode: z.string().describe('Unique card code identifier'),
|
||||
booking: z
|
||||
.object({
|
||||
points: z.number().describe('Booking points'),
|
||||
reason: z.string().optional().describe('Booking Reason'),
|
||||
storeId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Booking store (convercus store id)'),
|
||||
})
|
||||
.describe('Booking details'),
|
||||
});
|
||||
|
||||
export type AddBooking = z.infer<typeof AddBookingSchema>;
|
||||
export type AddBookingInput = z.input<typeof AddBookingSchema>;
|
||||
@@ -17,3 +17,4 @@ export * from './payer.schema';
|
||||
export * from './payment-settings.schema';
|
||||
export * from './shipping-address.schema';
|
||||
export * from './user.schema';
|
||||
export * from './add-booking.schema';
|
||||
|
||||
@@ -3,8 +3,13 @@ import {
|
||||
CustomerService,
|
||||
LoyaltyCardService,
|
||||
LoyaltyBookingInfoDTO,
|
||||
KeyValueDTOOfStringAndString,
|
||||
KeyValueDTOOfStringAndInteger,
|
||||
} from '@generated/swagger/crm-api';
|
||||
import {
|
||||
AddBooking,
|
||||
AddBookingInput,
|
||||
AddBookingSchema,
|
||||
Customer,
|
||||
FetchCustomerCardsInput,
|
||||
FetchCustomerCardsSchema,
|
||||
@@ -14,6 +19,7 @@ import {
|
||||
import {
|
||||
catchResponseArgsErrorPipe,
|
||||
ResponseArgs,
|
||||
ResponseArgsError,
|
||||
takeUntilAborted,
|
||||
} from '@isa/common/data-access';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
@@ -104,4 +110,77 @@ export class CrmSearchService {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async fetchBookingReasons(
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<KeyValueDTOOfStringAndInteger[]> {
|
||||
this.#logger.info('Fetching booking reasons from API');
|
||||
|
||||
let req$ = this.#loyaltyCardService
|
||||
.LoyaltyCardBookingReason()
|
||||
.pipe(catchResponseArgsErrorPipe());
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await firstValueFrom(req$);
|
||||
this.#logger.debug('Successfully fetched booking reasons');
|
||||
|
||||
return res?.result || [];
|
||||
} catch (error) {
|
||||
this.#logger.error('Error fetching booking reasons', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async fetchCurrentBookingPartnerStore(
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<KeyValueDTOOfStringAndString | undefined> {
|
||||
this.#logger.info('Fetching current booking partner store from API');
|
||||
|
||||
let req$ = this.#loyaltyCardService
|
||||
.LoyaltyCardCurrentBookingPartnerStore()
|
||||
.pipe(catchResponseArgsErrorPipe());
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await firstValueFrom(req$);
|
||||
this.#logger.debug('Successfully fetched current booking partner store');
|
||||
|
||||
return res?.result;
|
||||
} catch (error) {
|
||||
this.#logger.error('Error fetching current booking partner store', error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async addBooking(
|
||||
params: AddBookingInput,
|
||||
): Promise<LoyaltyBookingInfoDTO | undefined> {
|
||||
const parsed = AddBookingSchema.parse(params);
|
||||
|
||||
const req$ = this.#loyaltyCardService.LoyaltyCardAddBooking({
|
||||
cardCode: parsed.cardCode,
|
||||
booking: {
|
||||
points: parsed.booking.points,
|
||||
reason: parsed.booking.reason,
|
||||
storeId: parsed.booking.storeId,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await firstValueFrom(req$);
|
||||
|
||||
if (res.error) {
|
||||
const err = new ResponseArgsError(res);
|
||||
this.#logger.error('Add Booking Failed', err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return res?.result;
|
||||
}
|
||||
}
|
||||
|
||||
7
libs/crm/feature/customer-booking/README.md
Normal file
7
libs/crm/feature/customer-booking/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# crm-feature-customer-booking
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test crm-feature-customer-booking` to execute the unit tests.
|
||||
34
libs/crm/feature/customer-booking/eslint.config.cjs
Normal file
34
libs/crm/feature/customer-booking/eslint.config.cjs
Normal file
@@ -0,0 +1,34 @@
|
||||
const nx = require('@nx/eslint-plugin');
|
||||
const baseConfig = require('../../../../eslint.config.js');
|
||||
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'lib',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'lib',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
20
libs/crm/feature/customer-booking/project.json
Normal file
20
libs/crm/feature/customer-booking/project.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "crm-feature-customer-booking",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/crm/feature/customer-booking/src",
|
||||
"prefix": "lib",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/vite:test",
|
||||
"outputs": ["{options.reportsDirectory}"],
|
||||
"options": {
|
||||
"reportsDirectory": "../../../../coverage/libs/crm/feature/customer-booking"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
1
libs/crm/feature/customer-booking/src/index.ts
Normal file
1
libs/crm/feature/customer-booking/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './lib/crm-feature-customer-booking/crm-feature-customer-booking.component';
|
||||
@@ -0,0 +1,15 @@
|
||||
:host {
|
||||
@apply h-[15.5rem] flex flex-col gap-4 rounded-2xl bg-isa-neutral-200 p-8 justify-between;
|
||||
}
|
||||
|
||||
/* Remove number input arrows */
|
||||
input[type='number']::-webkit-outer-spin-button,
|
||||
input[type='number']::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
@if (cardCode() && !bookingReasonsLoading()) {
|
||||
<div class="flex flex-col gap-1 text-isa-neutral-900">
|
||||
<span class="isa-text-body-1-bold">Kulanzbuchungen</span>
|
||||
<span class="isa-text-body-2-regular">1€ entspricht 10 Lesepunkten</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-[1fr,auto] items-center justify-between gap-4 border rounded-lg border-isa-neutral-900 px-4 py-1"
|
||||
>
|
||||
<div class="isa-text-body-1-bold">Buchen</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center">
|
||||
@if (selectedReason(); as reason) {
|
||||
<span class="isa-text-body-2-bold text-isa-neutral-900 px-2">
|
||||
{{ reason.value > 0 ? '+' : '-' }}
|
||||
</span>
|
||||
}
|
||||
<input
|
||||
name="points"
|
||||
placeholder="Punkte"
|
||||
type="number"
|
||||
[ngModel]="points()"
|
||||
(ngModelChange)="points.set($event)"
|
||||
min="0"
|
||||
data-what="input"
|
||||
data-which="points"
|
||||
class="w-20 isa-text-body-2-bold bg-isa-neutral-200 placeholder:isa-text-body-2-regular placeholder:text-isa-neutral-500 text-isa-neutral-900 focus:outline-none px-4 text-right border-none"
|
||||
/>
|
||||
</div>
|
||||
<ui-dropdown
|
||||
[ngModel]="selectedReasonKey()"
|
||||
(ngModelChange)="selectedReasonKey.set($event)"
|
||||
class="border-none w-[14rem] truncate"
|
||||
[label]="dropdownLabel()"
|
||||
data-what="dropdown"
|
||||
data-which="booking-reason"
|
||||
>
|
||||
@if (bookingReasons(); as reasons) {
|
||||
@for (reason of reasons; track reason.key) {
|
||||
<ui-dropdown-option
|
||||
[value]="reason.label"
|
||||
data-what="dropdown-option"
|
||||
data-which="reason-option"
|
||||
[attr.data-reason-key]="reason.key"
|
||||
>
|
||||
{{ reason.label }}
|
||||
</ui-dropdown-option>
|
||||
}
|
||||
}
|
||||
</ui-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="w-40"
|
||||
uiButton
|
||||
type="button"
|
||||
color="primary"
|
||||
(click)="booking()"
|
||||
[disabled]="disableBooking()"
|
||||
[pending]="isBooking()"
|
||||
data-what="button"
|
||||
data-which="booking-submit"
|
||||
>
|
||||
Jetzt buchen
|
||||
</button>
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CrmFeatureCustomerBookingComponent } from './crm-feature-customer-booking.component';
|
||||
|
||||
describe('CrmFeatureCustomerBookingComponent', () => {
|
||||
let component: CrmFeatureCustomerBookingComponent;
|
||||
let fixture: ComponentFixture<CrmFeatureCustomerBookingComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CrmFeatureCustomerBookingComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CrmFeatureCustomerBookingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
signal,
|
||||
computed,
|
||||
input,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
import {
|
||||
CustomerBookingReasonsResource,
|
||||
CustomerCardBookingFacade,
|
||||
CustomerCardTransactionsResource,
|
||||
} from '@isa/crm/data-access';
|
||||
import {
|
||||
injectFeedbackDialog,
|
||||
injectFeedbackErrorDialog,
|
||||
} from '@isa/ui/dialog';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import {
|
||||
DropdownButtonComponent,
|
||||
DropdownOptionComponent,
|
||||
} from '@isa/ui/input-controls';
|
||||
|
||||
@Component({
|
||||
selector: 'crm-customer-booking',
|
||||
imports: [
|
||||
FormsModule,
|
||||
ButtonComponent,
|
||||
DropdownButtonComponent,
|
||||
DropdownOptionComponent,
|
||||
],
|
||||
templateUrl: './crm-feature-customer-booking.component.html',
|
||||
styleUrl: './crm-feature-customer-booking.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [CustomerBookingReasonsResource],
|
||||
})
|
||||
export class CrmFeatureCustomerBookingComponent {
|
||||
#logger = logger(() => ({
|
||||
component: 'CrmFeatureCustomerBookingComponent',
|
||||
}));
|
||||
#customerCardBookingFacade = inject(CustomerCardBookingFacade);
|
||||
#bookingReasonsResource = inject(CustomerBookingReasonsResource);
|
||||
#transactionResource = inject(CustomerCardTransactionsResource);
|
||||
#errorFeedbackDialog = injectFeedbackErrorDialog();
|
||||
#feedbackDialog = injectFeedbackDialog();
|
||||
readonly cardCode = input<string | undefined>(undefined);
|
||||
|
||||
readonly bookingReasons = this.#bookingReasonsResource.resource.value;
|
||||
readonly bookingReasonsLoading =
|
||||
this.#bookingReasonsResource.resource.isLoading;
|
||||
|
||||
points = signal<number | undefined>(undefined);
|
||||
selectedReasonKey = signal<string | undefined>(undefined);
|
||||
isBooking = signal(false);
|
||||
|
||||
selectedReason = computed(() => {
|
||||
const key = this.selectedReasonKey();
|
||||
const reasons = this.bookingReasons();
|
||||
return reasons?.find((r) => r.key === key);
|
||||
});
|
||||
|
||||
calculatedPoints = computed(() => {
|
||||
const reason = this.selectedReason();
|
||||
const pointsValue = this.points();
|
||||
if (!reason || !pointsValue) return 0;
|
||||
return pointsValue * (reason.value ?? 1);
|
||||
});
|
||||
|
||||
disableBooking = computed(() => {
|
||||
return (
|
||||
this.isBooking() ||
|
||||
this.bookingReasonsLoading() ||
|
||||
!this.selectedReasonKey() ||
|
||||
!this.points() ||
|
||||
this.points() === 0
|
||||
);
|
||||
});
|
||||
|
||||
dropdownLabel = computed(() => {
|
||||
const reason = this.selectedReason()?.label;
|
||||
return reason ?? 'Buchungstyp';
|
||||
});
|
||||
|
||||
async booking() {
|
||||
this.isBooking.set(true);
|
||||
try {
|
||||
const cardCode = this.cardCode();
|
||||
const reason = this.selectedReason();
|
||||
const calculatedPoints = this.calculatedPoints();
|
||||
|
||||
if (!cardCode) {
|
||||
throw new Error('Kein Karten-Code vorhanden');
|
||||
}
|
||||
|
||||
if (!reason) {
|
||||
throw new Error('Kein Buchungsgrund ausgewählt');
|
||||
}
|
||||
|
||||
if (calculatedPoints === 0) {
|
||||
throw new Error('Punktezahl muss größer als 0 sein');
|
||||
}
|
||||
|
||||
const currentBookingPartnerStore =
|
||||
await this.#customerCardBookingFacade.fetchCurrentBookingPartnerStore();
|
||||
const storeId = currentBookingPartnerStore?.key;
|
||||
|
||||
await this.#customerCardBookingFacade.addBooking({
|
||||
cardCode,
|
||||
booking: {
|
||||
points: calculatedPoints,
|
||||
reason: reason.key,
|
||||
storeId: storeId,
|
||||
},
|
||||
});
|
||||
|
||||
this.#feedbackDialog({
|
||||
data: {
|
||||
message: `${reason.label} erfolgreich durchgeführt`,
|
||||
},
|
||||
});
|
||||
this.reloadTransactionHistory();
|
||||
} catch (error: any) {
|
||||
this.#logger.error('Booking Failed', () => ({ error }));
|
||||
this.#errorFeedbackDialog({
|
||||
data: {
|
||||
errorMessage: error?.message ?? 'Buchen/Stornieren fehlgeschlagen',
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
this.isBooking.set(false);
|
||||
this.resetInputs();
|
||||
}
|
||||
}
|
||||
|
||||
resetInputs() {
|
||||
this.points.set(undefined);
|
||||
this.selectedReasonKey.set(undefined);
|
||||
}
|
||||
|
||||
reloadTransactionHistory() {
|
||||
// Timeout to ensure that the new booking is available in the transaction history
|
||||
setTimeout(() => {
|
||||
this.#transactionResource.params({ cardCode: this.cardCode() });
|
||||
this.#transactionResource.resource.reload();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
13
libs/crm/feature/customer-booking/src/test-setup.ts
Normal file
13
libs/crm/feature/customer-booking/src/test-setup.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import '@angular/compiler';
|
||||
import '@analogjs/vitest-angular/setup-zone';
|
||||
|
||||
import {
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting,
|
||||
} from '@angular/platform-browser/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting(),
|
||||
);
|
||||
30
libs/crm/feature/customer-booking/tsconfig.json
Normal file
30
libs/crm/feature/customer-booking/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "preserve"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"typeCheckHostBindings": true,
|
||||
"strictTemplates": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
27
libs/crm/feature/customer-booking/tsconfig.lib.json
Normal file
27
libs/crm/feature/customer-booking/tsconfig.lib.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
29
libs/crm/feature/customer-booking/tsconfig.spec.json
Normal file
29
libs/crm/feature/customer-booking/tsconfig.spec.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
],
|
||||
"files": ["src/test-setup.ts"]
|
||||
}
|
||||
28
libs/crm/feature/customer-booking/vite.config.mts
Normal file
28
libs/crm/feature/customer-booking/vite.config.mts
Normal file
@@ -0,0 +1,28 @@
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../../../node_modules/.vite/libs/crm/feature/customer-booking',
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: ['default'],
|
||||
coverage: {
|
||||
reportsDirectory:
|
||||
'../../../../coverage/libs/crm/feature/customer-booking',
|
||||
provider: 'v8' as const,
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -1,7 +1,9 @@
|
||||
<div class="flex flex-col gap-[24px] px-4 overflow-hidden overflow-y-scroll">
|
||||
<h2 class="isa-text-body-1-bold text-isa-neutral-900">
|
||||
Letzte Transaktionen des Kunden
|
||||
</h2>
|
||||
<div class="flex flex-col gap-[24px] px-4">
|
||||
@if (transactions()?.length) {
|
||||
<h2 class="isa-text-body-1-bold text-isa-neutral-900">
|
||||
Letzte 5 Transaktionen des Kunden
|
||||
</h2>
|
||||
}
|
||||
|
||||
@if (isLoading()) {
|
||||
<div class="text-isa-neutral-500 text-sm">Lade Transaktionen...</div>
|
||||
@@ -11,9 +13,10 @@
|
||||
</div>
|
||||
} @else if (!transactions()?.length) {
|
||||
<ui-empty-state
|
||||
class="self-center"
|
||||
title="Keine Transaktionen"
|
||||
description="Für diese Kundenkarte wurden noch keine Transaktionen erfasst"
|
||||
appearance="no-results"
|
||||
appearance="noResults"
|
||||
/>
|
||||
} @else {
|
||||
<table cdk-table [dataSource]="dataSource()" [trackBy]="trackByDate">
|
||||
|
||||
@@ -24,10 +24,7 @@ import { LoyaltyBookingInfoDTO } from '@generated/swagger/crm-api';
|
||||
NgIconComponent,
|
||||
EmptyStateComponent,
|
||||
],
|
||||
providers: [
|
||||
CustomerCardTransactionsResource,
|
||||
provideIcons({ isaActionPolygonUp, isaActionPolygonDown }),
|
||||
],
|
||||
providers: [provideIcons({ isaActionPolygonUp, isaActionPolygonDown })],
|
||||
templateUrl: './crm-feature-customer-card-transactions.component.html',
|
||||
styleUrl: './crm-feature-customer-card-transactions.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -78,6 +75,8 @@ export class CrmFeatureCustomerCardTransactionsComponent {
|
||||
const code = this.cardCode();
|
||||
this.#logger.debug('Card code changed', () => ({ cardCode: code }));
|
||||
this.#transactionsResource.params({ cardCode: code });
|
||||
|
||||
console.log(this.transactions());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
76920
package-lock.json
generated
76920
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,9 @@
|
||||
"@isa/core/storage": ["libs/core/storage/src/index.ts"],
|
||||
"@isa/core/tabs": ["libs/core/tabs/src/index.ts"],
|
||||
"@isa/crm/data-access": ["libs/crm/data-access/src/index.ts"],
|
||||
"@isa/crm/feature/customer-booking": [
|
||||
"libs/crm/feature/customer-booking/src/index.ts"
|
||||
],
|
||||
"@isa/crm/feature/customer-card-transactions": [
|
||||
"libs/crm/feature/customer-card-transactions/src/index.ts"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user