Merged PR 1382: Kubi

Related work items: #3228, #3230, #3289, #3467, #3471, #3478
This commit is contained in:
Lorenz Hilpert
2022-09-30 13:48:19 +00:00
committed by Andreas Schickinger
parent 4ab3a3b3cf
commit fdaceb9bf8
200 changed files with 4394 additions and 2612 deletions

View File

@@ -3629,6 +3629,37 @@
}
}
}
},
"@ui/form-field": {
"projectType": "library",
"root": "apps/ui/form-field",
"sourceRoot": "apps/ui/form-field/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/ui/form-field/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/form-field/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/ui/form-field/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/form-field/src/test.ts",
"tsConfig": "apps/ui/form-field/tsconfig.spec.json",
"karmaConfig": "apps/ui/form-field/karma.conf.js"
}
}
}
}
},
"defaultProject": "isa-app"

View File

@@ -1,10 +1,10 @@
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, Optional, SkipSelf } from '@angular/core';
import { ActionHandler } from './action-handler.interface';
import { FEATURE_ACTION_HANDLERS, ROOT_ACTION_HANDLERS } from './tokens';
@Injectable()
export class CommandService {
constructor(private injector: Injector) {}
constructor(private injector: Injector, @Optional() @SkipSelf() private _parent: CommandService) {}
async handleCommand<T>(command: string, data?: T): Promise<T> {
const actions = this.getActions(command);
@@ -15,7 +15,7 @@ export class CommandService {
console.error('CommandService.handleCommand', 'Action Handler does not exist', { action });
throw new Error('Action Handler does not exist');
}
console.log('handle command', handler, data);
data = await handler.handler(data);
}
return data;
@@ -25,10 +25,16 @@ export class CommandService {
return command?.split('|') || [];
}
getActionHandler(action: string): ActionHandler {
getActionHandler(action: string): ActionHandler | undefined {
const featureActionHandlers: ActionHandler[] = this.injector.get(FEATURE_ACTION_HANDLERS, []);
const rootActionHandlers: ActionHandler[] = this.injector.get(ROOT_ACTION_HANDLERS, []);
return [...featureActionHandlers, ...rootActionHandlers].find((handler) => handler.action === action);
let handler = [...featureActionHandlers, ...rootActionHandlers].find((handler) => handler.action === action);
if (this._parent && !handler) {
handler = this._parent.getActionHandler(action);
}
return handler;
}
}

View File

@@ -37,6 +37,7 @@ import * as DomainCheckoutActions from './store/domain-checkout.actions';
import { DomainAvailabilityService } from '@domain/availability';
import { HttpErrorResponse } from '@angular/common/http';
import { ApplicationService } from '@core/application';
import { CustomerDTO, EntityDTOContainerOfAttributeDTO } from '@swagger/crm';
@Injectable()
export class DomainCheckoutService {
@@ -838,10 +839,6 @@ export class DomainCheckoutService {
.pipe(map((response) => response.result));
}
setCustomerFeatures({ processId, customerFeatures }: { processId: number; customerFeatures: { [key: string]: string } }) {
this.store.dispatch(DomainCheckoutActions.setCustomerFeatures({ processId, customerFeatures }));
}
setOlaErrors({ processId, errorIds }: { processId: number; errorIds: number[] }) {
this.store.dispatch(
DomainCheckoutActions.setOlaError({
@@ -867,6 +864,14 @@ export class DomainCheckoutService {
this.store.dispatch(DomainCheckoutActions.removeProcess({ processId }));
}
setCustomer({ processId, customerDto }: { processId: number; customerDto: CustomerDTO }) {
this.store.dispatch(DomainCheckoutActions.setCustomer({ processId, customer: customerDto }));
}
getCustomer({ processId }: { processId: number }): Observable<CustomerDTO> {
return this.store.select(DomainCheckoutSelectors.selectCustomerByProcessId, { processId });
}
setPayer({ processId, payer }: { processId: number; payer: PayerDTO }) {
this.store.dispatch(DomainCheckoutActions.setPayer({ processId, payer }));
}

View File

@@ -1,11 +1,12 @@
import { BuyerDTO, CheckoutDTO, NotificationChannel, PayerDTO, ShippingAddressDTO, ShoppingCartDTO } from '@swagger/checkout';
import { CustomerDTO } from '@swagger/crm';
import { DisplayOrderDTO } from '@swagger/oms';
export interface CheckoutEntity {
processId: number;
checkout: CheckoutDTO;
shoppingCart: ShoppingCartDTO;
customerFeatures: { [key: string]: string };
customer: CustomerDTO;
payer: PayerDTO;
buyer: BuyerDTO;
shippingAddress: ShippingAddressDTO;

View File

@@ -8,6 +8,7 @@ import {
BuyerDTO,
PayerDTO,
} from '@swagger/checkout';
import { CustomerDTO } from '@swagger/crm';
import { DisplayOrderDTO, DisplayOrderItemDTO } from '@swagger/oms';
const prefix = '[DOMAIN-CHECKOUT]';
@@ -38,11 +39,6 @@ export const setCheckoutDestination = createAction(
props<{ processId: number; destination: DestinationDTO }>()
);
export const setCustomerFeatures = createAction(
`${prefix} Set Customer Features`,
props<{ processId: number; customerFeatures: { [key: string]: string } }>()
);
export const setShippingAddress = createAction(
`${prefix} Set Shipping Address`,
props<{ processId: number; shippingAddress: ShippingAddressDTO }>()
@@ -63,3 +59,5 @@ export const setPayer = createAction(`${prefix} Set Payer`, props<{ processId: n
export const setSpecialComment = createAction(`${prefix} Set Agent Comment`, props<{ processId: number; agentComment: string }>());
export const setOlaError = createAction(`${prefix} Set Ola Error`, props<{ processId: number; olaErrorIds: number[] }>());
export const setCustomer = createAction(`${prefix} Set Customer`, props<{ processId: number; customer: CustomerDTO }>());

View File

@@ -46,11 +46,6 @@ const _domainCheckoutReducer = createReducer(
};
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setCustomerFeatures, (s, { processId, customerFeatures }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.customerFeatures = customerFeatures;
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setShippingAddress, (s, { processId, shippingAddress }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.shippingAddress = shippingAddress;
@@ -100,6 +95,11 @@ const _domainCheckoutReducer = createReducer(
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.olaErrorIds = olaErrorIds;
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setCustomer, (s, { processId, customer }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.customer = customer;
return storeCheckoutAdapter.setOne(entity, s);
})
);
@@ -115,7 +115,6 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
processId,
checkout: undefined,
shoppingCart: undefined,
customerFeatures: undefined,
shippingAddress: undefined,
orders: [],
payer: undefined,
@@ -123,6 +122,7 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
specialComment: '',
notificationChannels: 0,
olaErrorIds: [],
customer: undefined,
};
}

View File

@@ -1,5 +1,6 @@
import { Dictionary } from '@ngrx/entity';
import { createSelector } from '@ngrx/store';
import { CustomerDTO } from '@swagger/crm';
import { CheckoutEntity } from './defs/checkout.entity';
import { storeCheckoutAdapter, storeFeatureSelector } from './domain-checkout.state';
@@ -22,7 +23,7 @@ export const selectCheckoutByProcessId = createSelector(
export const selectCustomerFeaturesByProcessId = createSelector(
selectEntities,
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => entities[processId]?.customerFeatures
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => getCusomterFeatures(entities[processId]?.customer)
);
export const selectShippingAddressByProcessId = createSelector(
@@ -61,3 +62,19 @@ export const selectOlaErrorsByProcessId = createSelector(
selectEntities,
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => entities[processId]?.olaErrorIds
);
export const selectCustomerByProcessId = createSelector(
selectEntities,
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => entities[processId]?.customer
);
function getCusomterFeatures(custoemr: CustomerDTO): { [key: string]: string } {
const customerFeatures = custoemr?.features ?? [];
const features: { [key: string]: string } = {};
for (const feature of customerFeatures) {
features[feature.key] = feature.key;
}
return features;
}

View File

@@ -110,7 +110,10 @@ export class CrmCustomerService {
communicationDetails: customer?.communicationDetails,
});
return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 16, notificationChannels });
return this.customerService.CustomerCreateCustomer({
customer: { ...customer, customerType: 16, notificationChannels },
modifiers: [{ key: 'b2b', group: 'customertype' }],
});
}
createOnlineCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
@@ -157,7 +160,10 @@ export class CrmCustomerService {
];
}
return this.customerService.CustomerCreateOnlineCustomer({ ...payload, notificationChannels });
return this.customerService.CustomerCreateCustomer({
customer: { ...payload, notificationChannels },
modifiers: [{ key: 'webshop', group: 'customertype' }],
});
}
createGuestCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
@@ -202,20 +208,44 @@ export class CrmCustomerService {
];
}
return this.customerService.CustomerCreateOnlineCustomer(payload);
return this.customerService.CustomerCreateCustomer({
customer: payload,
modifiers: [{ key: 'webshop', group: 'customertype' }],
});
}
createBranchCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
createStoreCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: customer?.communicationDetails,
});
return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 8, notificationChannels });
return this.customerService.CustomerCreateCustomer({
customer: { ...customer, customerType: 8, notificationChannels },
modifiers: [{ key: 'store', group: 'customertype' }],
});
}
validateAddress(address: AddressDTO): Observable<Result<AddressDTO[]>> {
return this.addressService.AddressValidateAddress(address);
}
getOnlineCustomerByEmail(email: string): Observable<CustomerInfoDTO | null> {
return this.getCustomers(email, {
take: 1,
filter: {
customertype: 'webshop',
},
}).pipe(
map((r) => {
if (r.hits === 1) {
return r.result[0];
} else {
return null;
}
}),
catchError((err) => [null])
);
}
private cachedCountriesFailed = false;
private cachedCountries: ReplaySubject<Result<CountryDTO[]>>;
getCountries(): Observable<Result<CountryDTO[]>> {

View File

@@ -1,3 +1,5 @@
import { DialogOfString } from '@swagger/crm';
export interface Result<T> {
/** Ergebnis */
result?: T;
@@ -13,4 +15,6 @@ export interface Result<T> {
/** Fehlerhafte Daten */
invalidProperties?: { [key: string]: string };
dialog?: DialogOfString;
}

View File

@@ -11,6 +11,7 @@ import {
PrintRequestOfIEnumerableOfPriceQRCodeDTO,
PrintService,
ResponseArgs,
LoyaltyCardPrintService,
} from '@swagger/print';
import { Observable, of } from 'rxjs';
import { catchError, filter, map, switchMap, timeout } from 'rxjs/operators';
@@ -25,7 +26,8 @@ export class DomainPrinterService {
private oMSPrintService: OMSPrintService,
private catalogPrintService: CatalogPrintService,
private checkoutPrintService: CheckoutPrintService,
private eisPublicDocumentService: EISPublicDocumentService
private eisPublicDocumentService: EISPublicDocumentService,
private _loyaltyCardPrintService: LoyaltyCardPrintService
) {}
getAvailablePrinters(): Observable<Printer[] | { error: string }> {
@@ -143,6 +145,13 @@ export class DomainPrinterService {
});
}
printKubiAgb({ p4mCode, printer }: { p4mCode: string; printer: string }) {
return this._loyaltyCardPrintService.LoyaltyCardPrintPrintLoyaltyCardAGB({
printer,
data: p4mCode,
});
}
printProduct({ item, printer }: { item: ItemDTO; printer: string }): Observable<ResponseArgs> {
const params = <PrintRequestOfIEnumerableOfItemDTO>{
printer: printer,

View File

@@ -6,6 +6,7 @@ import { PlatformModule } from '@angular/cdk/platform';
import { Config, ConfigModule, JsonConfigLoader } from '@core/config';
import { AuthModule, AuthService } from '@core/auth';
import { CoreCommandModule } from '@core/command';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@@ -30,6 +31,7 @@ import { IsaLogProvider } from './providers';
import { IsaErrorHandler } from './providers/isa.error-handler';
import { ScanAdapterModule } from '@adapter/scan';
import { RootStateService } from './store/root-state.service';
import * as Commands from './commands';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
@@ -70,6 +72,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
useConfigLoader: JsonConfigLoader,
jsonConfigLoaderUrl: '/config/config.json',
}),
CoreCommandModule.forRoot(Object.values(Commands)),
CoreLoggerModule.forRoot(),
AppStoreModule,
AuthModule.forRoot(),

View File

@@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
/** Dummy Command um Fehlermeldungen aus dem Diloag zu verhinden */
@Injectable()
export class CloseCommand extends ActionHandler<any> {
constructor() {
super('CLOSE');
}
handler(ctx: any): any {
return ctx;
}
}

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { CustomerInfoDTO } from '@swagger/crm';
@Injectable()
export class CreateCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
constructor(private _router: Router, private _application: ApplicationService) {
super('CREATE_CUSTOMER');
}
async handler(data: Result<CustomerInfoDTO[]>): Promise<Result<CustomerInfoDTO[]>> {
let customerType: string;
if (data.result.length > 0) {
const customerInfo = data.result[0];
if (customerInfo.features) {
if (customerInfo.features.some((f) => f.key === 'store')) {
customerType = 'store';
}
if (customerInfo.features.some((f) => f.key === 'webshop')) {
customerType = 'webshop';
}
}
}
if (!customerType) {
customerType = 'store';
}
await this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'create', customerType]);
return data;
}
}

View File

@@ -0,0 +1,44 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { CustomerInfoDTO } from '@swagger/crm';
import { encodeFormData, mapCustomerInfoDtoToCustomerCreateFormData } from 'apps/page/customer/src/lib/create-customer';
@Injectable()
export class CreateKubiCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
constructor(private _router: Router, private _application: ApplicationService) {
super('CREATE_KUBI_CUSTOMER');
}
async handler(data: Result<CustomerInfoDTO[]>): Promise<Result<CustomerInfoDTO[]>> {
let customerType: string;
let formData: string;
if (data.result.length > 0) {
const customerInfo = data.result[0];
const fd = mapCustomerInfoDtoToCustomerCreateFormData(customerInfo);
formData = encodeFormData({
...fd,
agb: false,
});
if (customerInfo.features) {
if (customerInfo.features.some((f) => f.key === 'store')) {
customerType = 'store';
}
if (customerInfo.features.some((f) => f.key === 'webshop')) {
customerType = 'webshop';
}
}
}
if (!customerType) {
customerType = 'store';
}
await this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'create', `${customerType}-p4m`], {
queryParams: { formData },
});
return data;
}
}

View File

@@ -0,0 +1,4 @@
export * from './close.command';
export * from './create-customer.command';
export * from './create-kubi-customer.command';
export * from './print-kubi-agb.command';

View File

@@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { DomainPrinterService } from '@domain/printer';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { CustomerInfoDTO } from '@swagger/crm';
import { UiModalService } from '@ui/modal';
@Injectable()
export class PrintKubiCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
constructor(private _uiModal: UiModalService, private _printerService: DomainPrinterService) {
super('PRINT_KUBI_AGB');
}
async handler(data: Result<CustomerInfoDTO[]>): Promise<Result<CustomerInfoDTO[]>> {
const customerInfo = data.result ? data.result[0] : undefined;
let p4mCode: string;
if (customerInfo) {
p4mCode = customerInfo.features.find((f) => f.key === 'p4mUser').value;
}
await this._uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printerType: 'Label',
print: (printer) => this._printerService.printKubiAgb({ printer, p4mCode }).toPromise(),
} as PrintModalData,
})
.afterClosed$.toPromise();
return data;
}
}

View File

@@ -112,7 +112,7 @@
Weiter einkaufen
</button>
<button *ngIf="canUpgrade$ | async" class="cta-upgrade-customer" (click)="continue('add-customer-data')">
Kundendaten hinzufügen
Kundendaten erfassen
</button>
<button
*ngIf="showTakeAwayButton$ | async"

View File

@@ -10,6 +10,10 @@ import { PurchasingOptionsModalData } from './purchasing-options-modal.data';
import { PurchasingOptionsModalStore } from './purchasing-options-modal.store';
import { DomainCatalogService } from '@domain/catalog';
import { BreadcrumbService } from '@core/breadcrumb';
import {
encodeFormData,
mapCustomerDtoToCustomerCreateFormData,
} from 'apps/page/customer/src/lib/create-customer/customer-create-form-data';
@Component({
selector: 'page-purchasing-options-modal',
@@ -111,10 +115,7 @@ export class PurchasingOptionsModalComponent {
switchMap((processId) => this.checkoutService.getCustomerFeatures({ processId }))
);
readonly customerId$ = this.application.activatedProcessId$.pipe(
switchMap((processId) => this.checkoutService.getBuyer({ processId })),
map((buyer) => buyer.source)
);
readonly customer$ = this.application.activatedProcessId$.pipe(switchMap((processId) => this.checkoutService.getCustomer({ processId })));
price$ = combineLatest([
this.purchasingOptionsModalStore.selectAvailabilities,
@@ -228,7 +229,7 @@ export class PurchasingOptionsModalComponent {
this.activeSpinner = navigate ? 'continue-shopping' : 'continue';
try {
const processId = await this.purchasingOptionsModalStore.selectProcessId.pipe(first()).toPromise();
const customer = await this.checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
const buyer = await this.checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
const item = await this.item$.pipe(first()).toPromise();
const quantity = await this.quantity$.pipe(first()).toPromise();
const availability = await this.availability$.pipe(first()).toPromise();
@@ -238,6 +239,7 @@ export class PurchasingOptionsModalComponent {
const canAdd = await this.canAdd$.pipe(first()).toPromise();
const customPrice = await this.purchasingOptionsModalStore.selectCustomPrice.pipe(first()).toPromise();
const customVat = await this.purchasingOptionsModalStore.selectCustomVat.pipe(first()).toPromise();
const customer = await this.checkoutService.getCustomer({ processId }).pipe(first()).toPromise();
if (canAdd || navigate === 'add-customer-data') {
const newItem: AddToShoppingCartDTO = {
@@ -340,7 +342,7 @@ export class PurchasingOptionsModalComponent {
} else if (navigate === 'continue') {
// Set filter for navigation to customer search if customer is not set
let filter: { [key: string]: string };
if (!customer) {
if (!buyer) {
filter = await this.customerFeatures$
.pipe(
first(),
@@ -357,10 +359,15 @@ export class PurchasingOptionsModalComponent {
this.router.navigate(['/kunde', this.application.activatedProcessId, 'cart', 'review']);
}
} else if (navigate === 'add-customer-data') {
const upgradeCustomerId = await this.customerId$.pipe(first()).toPromise();
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', 'create', 'webshop'], {
queryParams: { upgradeCustomerId },
});
if (customer?.attributes.some((attr) => attr.data.key === 'p4mUser')) {
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', 'create', 'webshop-p4m'], {
queryParams: { formData: encodeFormData(mapCustomerDtoToCustomerCreateFormData(customer)) },
});
} else {
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', 'create', 'webshop'], {
queryParams: { upgradeCustomerId: customer?.id },
});
}
}
} catch (error) {
console.log('PurchasingOptionsModalComponent.continue', error);

View File

@@ -0,0 +1,327 @@
import { HttpErrorResponse } from '@angular/common/http';
import { ChangeDetectorRef, Directive, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AbstractControl, AsyncValidatorFn, FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, CustomerDTO, ShippingAddressDTO } from '@swagger/crm';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { UiValidators } from '@ui/validators';
import { isNull } from 'lodash';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { first, map, distinctUntilChanged, shareReplay, delay, mergeMap, catchError, tap, debounceTime, takeUntil } from 'rxjs/operators';
import { AddressFormBlockComponent, DeviatingAddressFormBlockComponent, DeviatingAddressFormBlockData } from '../form-blocks';
import { FormBlock } from '../form-blocks/form-block';
import { AddressSelectionModalService } from '../modals/address-selection-modal/address-selection-modal.service';
import { CustomerCreateFormData, decodeFormData, encodeFormData } from './customer-create-form-data';
@Directive()
export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
protected onDestroy$ = new Subject<void>();
abstract validateAddress?: boolean;
abstract validateShippingAddress?: boolean;
private _formData = new BehaviorSubject<CustomerCreateFormData>({});
formData$ = this._formData.asObservable();
get formData() {
return this._formData.getValue();
}
form = new FormGroup({});
processId$: Observable<number>;
busy$ = new BehaviorSubject(false);
customerExists$ = new Subject<boolean>();
@ViewChild(DeviatingAddressFormBlockComponent)
deviatingDeliveryAddressFormBlock: DeviatingAddressFormBlockComponent;
@ViewChild(AddressFormBlockComponent)
addressFormBlock: AddressFormBlockComponent;
constructor(
protected activatedRoute: ActivatedRoute,
protected router: Router,
protected customerService: CrmCustomerService,
protected addressVlidationModal: AddressSelectionModalService,
protected modal: UiModalService,
protected breadcrumb: BreadcrumbService,
protected cdr: ChangeDetectorRef
) {
this._initProcessId$();
this._initFormData();
}
ngOnInit(): void {
this.initBreadcrumb();
}
async initBreadcrumb() {
await this.cleanupBreadcrumb();
await this.addOrUpdateBreadcrumb();
}
async cleanupBreadcrumb() {
const processId = await this.processId$.pipe(first()).toPromise();
const crumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['customer', 'main']).pipe(first()).toPromise();
for (const crumb of crumbs) {
await this.breadcrumb.removeBreadcrumbsAfter(crumb.id);
}
}
async addOrUpdateBreadcrumb() {
const processId = await this.processId$.pipe(first()).toPromise();
this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: 'Kundendaten erfassen',
path: this.getPath(),
params: this.getQueryParams(),
tags: ['customer', 'create'],
section: 'customer',
});
}
getPath() {
let path = '';
let current = this.activatedRoute;
while (current) {
path = current.snapshot.url.join('/') + '/' + path;
current = current.parent;
}
while (path.includes('//')) {
path = path.replace('//', '/');
}
return path;
}
getQueryParams(): Record<string, string> {
const formData = this.formData ? encodeFormData(this.formData) : undefined;
return { ...this.activatedRoute.snapshot.queryParams, formData };
}
private _initProcessId$(): void {
this.processId$ = this.activatedRoute.parent.parent.data.pipe(
map((data) => +data.processId),
distinctUntilChanged(),
shareReplay(1)
);
}
private _initFormData(): void {
const formData = this.activatedRoute.snapshot.queryParams.formData;
if (formData) {
const parsedFormData = decodeFormData(formData);
this._formData.next(parsedFormData);
}
}
async customerTypeChanged(customerType: string) {
const processId = await this.processId$.pipe(first()).toPromise();
this.router.navigate(['/kunde', processId, 'customer', 'create', customerType], {
queryParams: this.getQueryParams(),
});
}
addFormBlock(key: keyof CustomerCreateFormData, block: FormBlock<any, AbstractControl>) {
this.form.addControl(key, block.control);
this.cdr.markForCheck();
}
patchFormData(key: keyof CustomerCreateFormData, value: any) {
this._formData.next({ ...this.formData, [key]: value });
}
emailExistsValidator: AsyncValidatorFn = (control) => {
return of(control.value).pipe(
tap((_) => this.customerExists$.next(false)),
delay(500),
mergeMap((value) => {
return this.customerService.emailExists(value).pipe(
map((response) => {
if (response?.result) {
return { exists: response?.message };
}
return null;
}),
catchError((error) => {
if (error instanceof HttpErrorResponse) {
if (error?.error?.invalidProperties?.email) {
return of({ invalid: error.error.invalidProperties.email });
} else {
return of({ invalid: 'E-Mail ist ungültig' });
}
}
})
);
}),
tap((error) => {
if (error) {
this.customerExists$.next(true);
}
control.markAsTouched();
this.cdr.markForCheck();
})
);
};
async navigateToCustomerDetails(customer: CustomerDTO) {
const processId = await this.processId$.pipe(first()).toPromise();
return this.router.navigate(['/kunde', processId, 'customer', customer.id]);
}
async validateAddressData(address: AddressDTO): Promise<AddressDTO> {
const addressValidationResult = await this.addressVlidationModal.validateAddress(address);
if (addressValidationResult !== undefined && addressValidationResult !== 'continue') {
address = addressValidationResult;
}
return address;
}
async getCustomerFromFormData(): Promise<CustomerDTO> {
const data: CustomerCreateFormData = this.form.value;
const customer: CustomerDTO = {
communicationDetails: {},
attributes: [],
features: [],
};
if (data.name) {
customer.gender = data.name.gender;
customer.title = data.name.title;
customer.firstName = data.name.firstName;
customer.lastName = data.name.lastName;
}
if (data.organisation) {
customer.organisation = data.organisation;
}
if (data.email) {
customer.communicationDetails.email = data.email;
}
if (data.phoneNumbers) {
customer.communicationDetails.mobile = data.phoneNumbers.mobile;
customer.communicationDetails.phone = data.phoneNumbers.phone;
}
if (data.address) {
customer.address = data.address;
if (this.validateAddress) {
try {
const address = await this.validateAddressData(customer.address);
this.addressFormBlock.data = address;
customer.address = address;
} catch (error) {
this.form.enable();
setTimeout(() => {
this.addressFormBlock.setAddressValidationError(error.error.invalidProperties);
}, 10);
return;
}
}
}
if (data.birthDate && isNull(UiValidators.date(new FormControl(data.birthDate)))) {
customer.dateOfBirth = data.birthDate;
}
if (data.deviatingDeliveryAddress?.deviatingAddress) {
const shippingAddress = this.mapToShippingAddress(data.deviatingDeliveryAddress);
if (this.validateShippingAddress) {
try {
debugger;
shippingAddress.address = await this.validateAddressData(shippingAddress.address);
} catch (error) {
this.form.enable();
setTimeout(() => {
this.addressFormBlock.setAddressValidationError(error.error.invalidProperties);
}, 10);
return;
}
}
customer.shippingAddresses = [
{
data: shippingAddress,
},
];
}
return customer;
}
mapToShippingAddress({ name, address, email, organisation, phoneNumbers }: DeviatingAddressFormBlockData): ShippingAddressDTO {
return {
gender: name?.gender,
title: name?.title,
firstName: name?.firstName,
lastName: name?.lastName,
address,
communicationDetails: {
email: email ? email : null,
mobile: phoneNumbers?.mobile ? phoneNumbers.mobile : null,
phone: phoneNumbers?.phone ? phoneNumbers.phone : null,
},
organisation,
isDefault: new Date().toJSON(),
};
}
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
this.busy$.complete();
this._formData.complete();
}
async save() {
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
try {
this.busy$.next(true);
this.form.disable();
const customer: CustomerDTO = await this.getCustomerFromFormData();
if (!customer) {
this.form.enable();
return;
}
const response = await this.saveCustomer(customer);
if (!!response) {
this.navigateToCustomerDetails(response);
}
} catch (error) {
this.form.enable();
this.modal.open({
content: UiErrorModalComponent,
data: error,
});
} finally {
this.busy$.next(false);
}
}
abstract saveCustomer(customer: CustomerDTO): Promise<CustomerDTO>;
}

View File

@@ -0,0 +1,91 @@
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
<h1 class="title">Kundendaten erfassen</h1>
<p class="description">
Um Ihnen den ausgewählten Service <br />
zu ermöglichen, legen wir Ihnen <br />
gerne ein Kundenkonto an. <br />
</p>
<app-customer-type-selector
[processId]="processId$ | async"
[p4mUser]="false"
customerType="b2b"
(valueChanges)="customerTypeChanged($event)"
>
</app-customer-type-selector>
<app-organisation-form-block
#orga
[tabIndexStart]="1"
[data]="data.organisation"
(dataChanges)="patchFormData('organisation', $event)"
(onInit)="addFormBlock('organisation', $event)"
[requiredMarks]="organisationFormBlockRequiredMarks"
[validatorFns]="organisationFormBlockValidators"
>
</app-organisation-form-block>
<app-name-form-block
#name
[tabIndexStart]="orga.tabIndexEnd + 1"
[data]="data.name"
(dataChanges)="patchFormData('name', $event)"
(onInit)="addFormBlock('name', $event)"
>
</app-name-form-block>
<app-address-form-block
#address
[tabIndexStart]="name.tabIndexEnd + 1"
[data]="data.address"
(dataChanges)="patchFormData('address', $event)"
(onInit)="addFormBlock('address', $event)"
[requiredMarks]="addressRequiredMarks"
[validatorFns]="addressValidators"
[defaults]="{ country: 'DEU' }"
>
</app-address-form-block>
<app-email-form-block
#email
[tabIndexStart]="address.tabIndexEnd + 1"
[data]="data.email"
(dataChanges)="patchFormData('email', $event)"
[validatorFns]="emailFormBlockValidators"
(onInit)="addFormBlock('email', $event)"
>
</app-email-form-block>
<app-phone-numbers-form-block
#phoneNumbers
[tabIndexStart]="email.tabIndexEnd + 1"
[data]="data.phoneNumbers"
(dataChanges)="patchFormData('phoneNumbers', $event)"
(onInit)="addFormBlock('phoneNumbers', $event)"
>
</app-phone-numbers-form-block>
<app-deviating-address-form-block
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
[data]="data.deviationgDeliveryAddress"
(dataChanges)="patchFormData('deviationgDeliveryAddress', $event)"
(onInit)="addFormBlock('deviationgDeliveryAddress', $event)"
[nameRequiredMarks]="deviatingNameRequiredMarks"
[nameValidatorFns]="deviatingNameValidationFns"
[addressRequiredMarks]="addressRequiredMarks"
[addressValidatorFns]="addressValidators"
[organisationRequiredMarks]="organisationFormBlockRequiredMarks"
[organisationValidatorFns]="organisationFormBlockValidators"
[defaults]="{ address: { country: 'DEU' } }"
[organisation]="true"
[email]="true"
[phoneNumbers]="true"
>
Die Lieferadresse weicht von der Rechnungsadresse ab
</app-deviating-address-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</form>

View File

@@ -0,0 +1,51 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { ValidatorFn, Validators } from '@angular/forms';
import { CustomerDTO } from '@swagger/crm';
import { map } from 'rxjs/operators';
import { validateEmail } from '../../validators/email-validator';
import { AbstractCreateCustomer } from '../abstract-create-customer';
@Component({
selector: 'app-create-b2b-customer',
templateUrl: 'create-b2b-customer.component.html',
styleUrls: ['../create-customer.scss', 'create-b2b-customer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CreateB2BCustomerComponent extends AbstractCreateCustomer {
validateAddress = true;
validateShippingAddress = true;
organisationFormBlockRequiredMarks = ['name'];
organisationFormBlockValidators: Record<string, ValidatorFn[]> = {
name: [Validators.required],
};
addressRequiredMarks = ['street', 'streetNumber', 'zipCode', 'city', 'country'];
addressValidators: Record<string, ValidatorFn[]> = {
street: [Validators.required],
streetNumber: [Validators.required],
zipCode: [Validators.required],
city: [Validators.required],
country: [Validators.required],
};
emailFormBlockValidators = [Validators.email, validateEmail];
deviatingNameRequiredMarks = ['gender', 'firstName', 'lastName'];
deviatingNameValidationFns: Record<string, ValidatorFn[]> = {
gender: [Validators.required],
firstName: [Validators.required],
lastName: [Validators.required],
};
async saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
return await this.customerService
.createB2BCustomer(customer)
.pipe(map((r) => r.result))
.toPromise();
}
}

View File

@@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CreateB2BCustomerComponent } from './create-b2b-customer.component';
import { OrganisationFormBlockModule } from '../../form-blocks/organisation';
import { NameFormBlockModule } from '../../form-blocks/name';
import { AddressFormBlockModule } from '../../form-blocks/address';
import { CustomerTypeSelectorModule } from '../../shared/customer-type-selector';
import { DeviatingAddressFormBlockComponentModule, EmailFormBlockModule } from '../../form-blocks';
import { PhoneNumbersFormBlockModule } from '../../form-blocks/phone-numbers/phone-numbers-form-block.module';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [
CommonModule,
OrganisationFormBlockModule,
NameFormBlockModule,
AddressFormBlockModule,
DeviatingAddressFormBlockComponentModule,
CustomerTypeSelectorModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
UiSpinnerModule,
],
exports: [CreateB2BCustomerComponent],
declarations: [CreateB2BCustomerComponent],
})
export class CreateB2BCustomerModule {}

View File

@@ -0,0 +1,2 @@
export * from './create-b2b-customer.component';
export * from './create-b2b-customer.module';

View File

@@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { CreateB2BCustomerModule } from './create-b2b-customer/create-b2b-customer.module';
import { CreateGuestCustomerModule } from './create-guest-customer';
import { CreateP4MCustomerModule } from './create-p4m-customer';
import { CreateStoreCustomerModule } from './create-store-customer/create-store-customer.module';
import { CreateWebshopCustomerModule } from './create-webshop-customer/create-webshop-customer.module';
import { UpdateP4MWebshopCustomerModule } from './update-p4m-webshop-customer';
@NgModule({
imports: [
CreateB2BCustomerModule,
CreateGuestCustomerModule,
CreateStoreCustomerModule,
CreateWebshopCustomerModule,
CreateP4MCustomerModule,
UpdateP4MWebshopCustomerModule,
],
exports: [
CreateB2BCustomerModule,
CreateGuestCustomerModule,
CreateStoreCustomerModule,
CreateWebshopCustomerModule,
CreateP4MCustomerModule,
UpdateP4MWebshopCustomerModule,
],
})
export class CreateCustomerModule {}

View File

@@ -0,0 +1,42 @@
:host {
@apply block bg-white rounded-card px-20 py-10;
}
h1.title {
@apply text-2xl font-bold text-center mb-6;
}
p.description {
@apply text-xl text-center;
}
p.info {
@apply mt-8 font-semibold;
}
form {
@apply relative pb-4;
}
button.cta-submit {
@apply sticky left-1/2 bottom-8 text-center bg-brand text-cta-l text-white font-bold px-7 py-3 rounded-full transform -translate-x-1/2 transition-all duration-200 ease-in-out;
&:disabled {
@apply bg-active-branch cursor-not-allowed;
}
}
app-newsletter-form-block,
app-accept-agb-form-block,
app-interests-form-block,
app-deviating-address-form-block {
@apply mt-8;
}
.spacer {
@apply w-full h-8;
}
app-customer-type-selector {
@apply mt-8;
}

View File

@@ -0,0 +1,104 @@
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
<h1 class="title">Kundendaten erfassen</h1>
<p class="description">
Um Ihnen den ausgewählten Service <br />
zu ermöglichen, legen wir Ihnen <br />
gerne ein Kundenkonto an. <br />
</p>
<app-customer-type-selector
[processId]="processId$ | async"
[p4mUser]="false"
customerType="guest"
(valueChanges)="customerTypeChanged($event)"
>
</app-customer-type-selector>
<app-name-form-block
#name
[tabIndexStart]="1"
[data]="data.name"
(dataChanges)="patchFormData('name', $event)"
(onInit)="addFormBlock('name', $event)"
[requiredMarks]="nameRequiredMarks"
[validatorFns]="nameValidationFns"
>
</app-name-form-block>
<p class="info">
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere als
die Übermittlungskosten nach den Basistarifen entstehen.
</p>
<app-email-form-block
#email
[tabIndexStart]="name.tabIndexEnd + 1"
[data]="data.email"
[requiredMark]="true"
(dataChanges)="patchFormData('email', $event)"
[validatorFns]="emailFormBlockValidators"
(onInit)="addFormBlock('email', $event)"
>
</app-email-form-block>
<app-organisation-form-block
#orga
[tabIndexStart]="email.tabIndexStart + 1"
[data]="data.organisation"
(dataChanges)="patchFormData('organisation', $event)"
(onInit)="addFormBlock('organisation', $event)"
appearence="compact"
>
</app-organisation-form-block>
<app-address-form-block
#address
[tabIndexStart]="orga.tabIndexEnd + 1"
[data]="data.address"
(dataChanges)="patchFormData('address', $event)"
(onInit)="addFormBlock('address', $event)"
[requiredMarks]="addressRequiredMarks"
[validatorFns]="addressValidators"
[defaults]="{ country: 'DEU' }"
>
</app-address-form-block>
<app-phone-numbers-form-block
#phoneNumbers
[tabIndexStart]="address.tabIndexEnd + 1"
[data]="data.phoneNumbers"
(dataChanges)="patchFormData('phoneNumbers', $event)"
(onInit)="addFormBlock('phoneNumbers', $event)"
>
</app-phone-numbers-form-block>
<app-birth-date-form-block
#birthDate
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
(onInit)="addFormBlock('birthDate', $event)"
[data]="data.birthDate"
(dataChanges)="patchFormData('birthDate', $event)"
>
</app-birth-date-form-block>
<app-deviating-address-form-block
[tabIndexStart]="birthDate.tabIndexEnd + 1"
[data]="data.deviationgDeliveryAddress"
(dataChanges)="patchFormData('deviationgDeliveryAddress', $event)"
(onInit)="addFormBlock('deviationgDeliveryAddress', $event)"
[nameRequiredMarks]="deviatingNameRequiredMarks"
[nameValidatorFns]="deviatingNameValidationFns"
[addressRequiredMarks]="addressRequiredMarks"
[addressValidatorFns]="addressValidators"
[defaults]="{ address: { country: 'DEU' } }"
[organisation]="true"
>
Die Lieferadresse weicht von der Rechnungsadresse ab
</app-deviating-address-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</form>

View File

@@ -0,0 +1,63 @@
import { Component, ChangeDetectionStrategy, ViewChild } from '@angular/core';
import { ValidatorFn, Validators } from '@angular/forms';
import { CustomerDTO } from '@swagger/crm';
import { map } from 'rxjs/operators';
import { DeviatingAddressFormBlockComponent } from '../../form-blocks';
import { AddressFormBlockComponent } from '../../form-blocks/address';
import { NameFormBlockData } from '../../form-blocks/name/name-form-block-data';
import { validateEmail } from '../../validators/email-validator';
import { AbstractCreateCustomer } from '../abstract-create-customer';
@Component({
selector: 'app-create-guest-customer',
templateUrl: 'create-guest-customer.component.html',
styleUrls: ['../create-customer.scss', 'create-guest-customer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CreateGuestCustomerComponent extends AbstractCreateCustomer {
validateAddress = true;
validateShippingAddress = true;
nameRequiredMarks = ['gender', 'firstName', 'lastName'];
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required],
title: [],
};
addressRequiredMarks = ['street', 'streetNumber', 'zipCode', 'city', 'country'];
addressValidators: Record<string, ValidatorFn[]> = {
street: [Validators.required],
streetNumber: [Validators.required],
zipCode: [Validators.required],
city: [Validators.required],
country: [Validators.required],
};
emailFormBlockValidators = [Validators.email, validateEmail, Validators.required];
deviatingNameRequiredMarks = ['gender', 'firstName', 'lastName'];
deviatingNameValidationFns: Record<string, ValidatorFn[]> = {
gender: [Validators.required],
firstName: [Validators.required],
lastName: [Validators.required],
};
@ViewChild(AddressFormBlockComponent, { static: false })
addressFormBlock: AddressFormBlockComponent;
@ViewChild(DeviatingAddressFormBlockComponent, { static: false })
deviatingDeliveryAddressFormBlock: DeviatingAddressFormBlockComponent;
async saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
return await this.customerService
.createGuestCustomer(customer)
.pipe(map((r) => r.result))
.toPromise();
}
}

View File

@@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CreateGuestCustomerComponent } from './create-guest-customer.component';
import { OrganisationFormBlockModule } from '../../form-blocks/organisation';
import { NameFormBlockModule } from '../../form-blocks/name';
import { AddressFormBlockModule } from '../../form-blocks/address';
import { CustomerTypeSelectorModule } from '../../shared/customer-type-selector';
import { BirthDateFormBlockModule, DeviatingAddressFormBlockComponentModule, EmailFormBlockModule } from '../../form-blocks';
import { PhoneNumbersFormBlockModule } from '../../form-blocks/phone-numbers/phone-numbers-form-block.module';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [
CommonModule,
OrganisationFormBlockModule,
NameFormBlockModule,
AddressFormBlockModule,
DeviatingAddressFormBlockComponentModule,
CustomerTypeSelectorModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
BirthDateFormBlockModule,
UiSpinnerModule,
],
exports: [CreateGuestCustomerComponent],
declarations: [CreateGuestCustomerComponent],
})
export class CreateGuestCustomerModule {}

View File

@@ -0,0 +1,2 @@
export * from './create-guest-customer.component';
export * from './create-guest-customer.module';

View File

@@ -0,0 +1,142 @@
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
<h1 class="title flex flex-row items-center justify-center">
Kundendaten erfassen
<!-- <span
class="rounded-full ml-4 h-8 w-8 text-xl text-center border-2 border-solid border-brand text-brand">i</span> -->
</h1>
<p class="description">Haben Sie eine Kundenkarte?</p>
<app-customer-type-selector
[processId]="processId$ | async"
[p4mUser]="true"
[customerType]="customerType"
(valueChanges)="customerTypeChanged($event)"
[p4mReadonly]="data?._meta?.p4mRequired"
>
</app-customer-type-selector>
<app-p4m-number-form-block
#p4mBlock
[tabIndexStart]="1"
(onInit)="addFormBlock('p4m', $event)"
[data]="data.p4m"
(dataChanges)="patchFormData('p4m', $event)"
[readonly]="data?._meta?.p4mRequired"
[focusAfterInit]="!data?._meta?.p4mRequired"
>
</app-p4m-number-form-block>
<app-newsletter-form-block
#newsletterBlock
[tabIndexStart]="p4mBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('newsletter', $event)"
[data]="data.newsletter"
(dataChanges)="patchFormData('newsletter', $event)"
[focusAfterInit]="data?._meta?.p4mRequired"
>
</app-newsletter-form-block>
<app-name-form-block
#nameBlock
[tabIndexStart]="newsletterBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('name', $event)"
[data]="data.name"
[requiredMarks]="nameRequiredMarks"
[validatorFns]="nameValidationFns"
(dataChanges)="patchFormData('name', $event)"
>
</app-name-form-block>
<app-email-form-block
class="flex-grow"
#email
[tabIndexStart]="nameBlock.tabIndexEnd + 1"
[requiredMark]="emailRequiredMark"
(onInit)="addFormBlock('email', $event)"
[data]="data.email"
(dataChanges)="patchFormData('email', $event)"
[validatorFns]="emailValidatorFn"
[asyncValidatorFns]="asyncEmailVlaidtorFn"
>
</app-email-form-block>
<app-organisation-form-block
#orgBlock
[tabIndexStart]="email.tabIndexEnd + 1"
appearence="compact"
(onInit)="addFormBlock('organisation', $event)"
[data]="data.organisation"
(dataChanges)="patchFormData('organisation', $event)"
>
</app-organisation-form-block>
<app-address-form-block
[defaults]="{ country: 'DEU' }"
#addressBlock
[tabIndexStart]="orgBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('address', $event)"
[data]="data.address"
(dataChanges)="patchFormData('address', $event)"
[requiredMarks]="addressRequiredMarks"
[validatorFns]="addressValidatorFns"
>
</app-address-form-block>
<app-deviating-address-form-block
#ddaBlock
[defaults]="{ address: { country: 'DEU' } }"
[tabIndexStart]="addressBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('deviatingDeliveryAddress', $event)"
[data]="data.deviatingDeliveryAddress"
(dataChanges)="patchFormData('deviatingDeliveryAddress', $event)"
[nameRequiredMarks]="nameRequiredMarks"
[nameValidatorFns]="nameValidationFns"
[addressRequiredMarks]="shippingAddressRequiredMarks"
[addressValidatorFns]="shippingAddressValidators"
>
Die Lieferadresse weicht von der Rechnungsadresse ab
</app-deviating-address-form-block>
<app-phone-numbers-form-block
#phoneNumbers
[tabIndexStart]="ddaBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('phoneNumbers', $event)"
[data]="data.phoneNumbers"
(dataChanges)="patchFormData('phoneNumbers', $event)"
></app-phone-numbers-form-block>
<app-birth-date-form-block
#bdBlock
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
(onInit)="addFormBlock('birthDate', $event)"
[data]="data.birthDate"
(dataChanges)="patchFormData('birthDate', $event)"
[requiredMark]="true"
[validatorFns]="birthDateValidatorFns"
>
</app-birth-date-form-block>
<app-interests-form-block
#inBlock
[tabIndexStart]="bdBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('interests', $event)"
[data]="data.interests"
(dataChanges)="patchFormData('interests', $event)"
></app-interests-form-block>
<app-accept-agb-form-block
[tabIndexStart]="inBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('agb', $event)"
[data]="data.agb"
(dataChanges)="patchFormData('agb', $event)"
[requiredMark]="true"
[validatorFns]="agbValidatorFns"
>
</app-accept-agb-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</form>

View File

@@ -0,0 +1,255 @@
import { Component, ChangeDetectionStrategy, ViewChild, OnInit } from '@angular/core';
import { AsyncValidatorFn, ValidatorFn, Validators } from '@angular/forms';
import { Result } from '@domain/defs';
import { CustomerDTO, CustomerInfoDTO, KeyValueDTOOfStringAndString } from '@swagger/crm';
import { UiErrorModalComponent, UiModalResult } from '@ui/modal';
import { NEVER, Observable, of } from 'rxjs';
import { catchError, distinctUntilChanged, first, map, switchMap, takeUntil } from 'rxjs/operators';
import { AddressFormBlockComponent, DeviatingAddressFormBlockComponent } from '../../form-blocks';
import { NameFormBlockData } from '../../form-blocks/name/name-form-block-data';
import { WebshopCustomnerAlreadyExistsModalComponent } from '../../modals';
import { validateEmail } from '../../validators/email-validator';
import { AbstractCreateCustomer } from '../abstract-create-customer';
import { encodeFormData, mapCustomerDtoToCustomerCreateFormData } from '../customer-create-form-data';
@Component({
selector: 'app-create-p4m-customer',
templateUrl: 'create-p4m-customer.component.html',
styleUrls: ['../create-customer.scss', 'create-p4m-customer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CreateP4MCustomerComponent extends AbstractCreateCustomer implements OnInit {
validateAddress = true;
validateShippingAddress = true;
get customerType() {
return this.activatedRoute.snapshot.data.customerType;
}
nameRequiredMarks = ['gender', 'firstName', 'lastName'];
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required],
title: [],
};
emailRequiredMark: boolean;
emailValidatorFn: ValidatorFn[];
asyncEmailVlaidtorFn: AsyncValidatorFn[];
shippingAddressRequiredMarks = ['street', 'streetNumber', 'zipCode', 'city', 'country'];
shippingAddressValidators: Record<string, ValidatorFn[]> = {
street: [Validators.required],
streetNumber: [Validators.required],
zipCode: [Validators.required],
city: [Validators.required],
country: [Validators.required],
};
addressRequiredMarks: string[];
addressValidatorFns: Record<string, ValidatorFn[]>;
@ViewChild(AddressFormBlockComponent, { static: false })
addressFormBlock: AddressFormBlockComponent;
@ViewChild(DeviatingAddressFormBlockComponent, { static: false })
deviatingDeliveryAddressFormBlock: DeviatingAddressFormBlockComponent;
agbValidatorFns = [Validators.requiredTrue];
birthDateValidatorFns = [Validators.required];
existingCustomer$: Observable<CustomerInfoDTO | null>;
ngOnInit(): void {
super.ngOnInit();
this.initMarksAndValidators();
this.existingCustomer$ = this.customerExists$.pipe(
distinctUntilChanged(),
switchMap((exists) => {
if (exists) {
return this.fetchCustomerInfo();
}
return of(null);
})
);
this.existingCustomer$
.pipe(
takeUntil(this.onDestroy$),
switchMap((info) => {
if (info) {
return this.customerService.getCustomer(info.id, 2).pipe(
map((res) => res.result),
catchError((err) => NEVER)
);
}
return NEVER;
})
)
.subscribe((customer) => {
if (customer) {
this.modal
.open({
content: WebshopCustomnerAlreadyExistsModalComponent,
data: customer,
title: 'Es existiert bereits ein Onlinekonto mit dieser E-Mail-Adresse',
})
.afterClosed$.subscribe(async (result: UiModalResult<boolean>) => {
if (result.data) {
this.navigateToUpdatePage(customer);
}
});
}
});
}
async navigateToUpdatePage(customer: CustomerDTO) {
const processId = await this.processId$.pipe(first()).toPromise();
this.router.navigate(['/kunde', processId, 'customer', 'create', 'webshop-p4m', 'update'], {
queryParams: {
formData: encodeFormData({
...mapCustomerDtoToCustomerCreateFormData(customer),
p4m: this.formData.p4m,
}),
},
});
}
initMarksAndValidators() {
if (this.customerType === 'webshop') {
this.emailRequiredMark = true;
this.emailValidatorFn = [Validators.required, Validators.email, validateEmail];
this.asyncEmailVlaidtorFn = [this.emailExistsValidator];
this.addressRequiredMarks = this.shippingAddressRequiredMarks;
this.addressValidatorFns = this.shippingAddressValidators;
} else {
this.emailRequiredMark = false;
this.emailValidatorFn = [Validators.email, validateEmail];
}
}
fetchCustomerInfo(): Observable<CustomerDTO | null> {
const email = this.formData.email;
return this.customerService.getOnlineCustomerByEmail(email).pipe(
map((result) => {
if (result) {
return result;
}
return null;
}),
catchError((err) => {
this.modal.open({
content: UiErrorModalComponent,
data: err,
});
return [null];
})
);
}
getInterests(): KeyValueDTOOfStringAndString[] {
const interests: KeyValueDTOOfStringAndString[] = [];
for (const key in this.formData.interests) {
if (this.formData.interests[key]) {
interests.push({ key, group: 'KUBI_INTERESSEN' });
}
}
return interests;
}
getNewsletter(): KeyValueDTOOfStringAndString | undefined {
if (this.formData.newsletter) {
return { key: 'kubi_newsletter', group: 'KUBI_NEWSLETTER' };
}
}
mapCustomerInfoDtoToCustomerDto(customerInfoDto: CustomerInfoDTO): CustomerDTO {
return {
address: customerInfoDto.address,
agentComment: customerInfoDto.agentComment,
bonusCard: customerInfoDto.bonusCard,
campaignCode: customerInfoDto.campaignCode,
communicationDetails: customerInfoDto.communicationDetails,
createdInBranch: customerInfoDto.createdInBranch,
customerGroup: customerInfoDto.customerGroup,
customerNumber: customerInfoDto.customerNumber,
customerStatus: customerInfoDto.customerStatus,
customerType: customerInfoDto.customerType,
dateOfBirth: customerInfoDto.dateOfBirth,
features: customerInfoDto.features,
firstName: customerInfoDto.firstName,
lastName: customerInfoDto.lastName,
gender: customerInfoDto.gender,
hasOnlineAccount: customerInfoDto.hasOnlineAccount,
isGuestAccount: customerInfoDto.isGuestAccount,
label: customerInfoDto.label,
notificationChannels: customerInfoDto.notificationChannels,
organisation: customerInfoDto.organisation,
title: customerInfoDto.title,
id: customerInfoDto.id,
pId: customerInfoDto.pId,
};
}
async saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
const isWebshop = this.customerType === 'webshop';
let res: Result<CustomerDTO>;
const { customerDto, customerInfoDto } = this.formData?._meta ?? {};
if (customerDto) {
customer = { ...customerDto, ...customer };
} else if (customerInfoDto) {
customer = { ...this.mapCustomerInfoDtoToCustomerDto(customerInfoDto), ...customer };
}
const p4mFeature = customer.features?.find((attr) => attr.key === 'p4mUser');
if (p4mFeature) {
p4mFeature.value = this.formData.p4m;
} else {
customer.features.push({
key: 'p4mUser',
value: this.formData.p4m,
});
}
const interests = this.getInterests();
if (interests.length > 0) {
customer.features?.push(...interests);
// TODO: Klärung wie Interessen zukünftig gespeichert werden
// await this._loyaltyCardService
// .LoyaltyCardSaveInteressen({
// customerId: res.result.id,
// interessen: this.getInterests(),
// })
// .toPromise();
}
const newsletter = this.getNewsletter();
if (newsletter) {
customer.features.push(newsletter);
} else {
customer.features = customer.features.filter((feature) => feature.key !== 'kubi_newsletter' && feature.group !== 'KUBI_NEWSLETTER');
}
if (isWebshop) {
res = await this.customerService.createOnlineCustomer(customer).toPromise();
} else {
res = await this.customerService.createStoreCustomer(customer).toPromise();
}
return res.result;
}
}

View File

@@ -0,0 +1,45 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CreateP4MCustomerComponent } from './create-p4m-customer.component';
import {
AddressFormBlockModule,
BirthDateFormBlockModule,
InterestsFormBlockModule,
NameFormBlockModule,
OrganisationFormBlockModule,
P4mNumberFormBlockModule,
NewsletterFormBlockModule,
DeviatingAddressFormBlockComponentModule,
AcceptAGBFormBlockModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
} from '../../form-blocks';
import { CustomerTypeSelectorModule } from '../../shared/customer-type-selector';
import { UiSpinnerModule } from '@ui/spinner';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
@NgModule({
imports: [
CommonModule,
CustomerTypeSelectorModule,
AddressFormBlockModule,
BirthDateFormBlockModule,
InterestsFormBlockModule,
NameFormBlockModule,
OrganisationFormBlockModule,
P4mNumberFormBlockModule,
NewsletterFormBlockModule,
DeviatingAddressFormBlockComponentModule,
AcceptAGBFormBlockModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
UiSpinnerModule,
UiIconModule,
RouterModule,
],
exports: [CreateP4MCustomerComponent],
declarations: [CreateP4MCustomerComponent],
})
export class CreateP4MCustomerModule {}

View File

@@ -0,0 +1,2 @@
export * from './create-p4m-customer.component';
export * from './create-p4m-customer.module';

View File

@@ -0,0 +1,94 @@
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
<h1 class="title">Kundendaten erfassen</h1>
<p class="description">
Um Ihnen den ausgewählten Service <br />
zu ermöglichen, legen wir Ihnen <br />
gerne ein Kundenkonto an.
</p>
<app-customer-type-selector
[processId]="processId$ | async"
[p4mUser]="false"
customerType="store"
(valueChanges)="customerTypeChanged($event)"
>
</app-customer-type-selector>
<app-name-form-block
#name
[tabIndexStart]="1"
(onInit)="addFormBlock('name', $event)"
[requiredMarks]="nameRequiredMarks"
[validatorFns]="nameValidationFns"
[data]="data.name"
(dataChanges)="patchFormData('name', $event)"
>
</app-name-form-block>
<app-email-form-block
#email
[tabIndexStart]="name.tabIndexEnd + 1"
[validatorFns]="emailFormBlockValidators"
(onInit)="addFormBlock('email', $event)"
[data]="data.email"
(dataChanges)="patchFormData('email', $event)"
>
</app-email-form-block>
<app-organisation-form-block
#orga
[tabIndexStart]="email.tabIndexEnd + 1"
appearence="name"
(onInit)="addFormBlock('organisation', $event)"
[data]="data.organisation"
(dataChanges)="patchFormData('organisation', $event)"
>
</app-organisation-form-block>
<app-address-form-block
#address
[tabIndexStart]="orga.tabIndexEnd + 1"
(onInit)="addFormBlock('address', $event)"
[data]="data.address"
(dataChanges)="patchFormData('address', $event)"
>
</app-address-form-block>
<app-phone-numbers-form-block
#phoneNumbers
[tabIndexStart]="address.tabIndexEnd + 1"
(onInit)="addFormBlock('phoneNumbers', $event)"
[data]="data.phoneNumbers"
(dataChanges)="patchFormData('phoneNumbers', $event)"
></app-phone-numbers-form-block>
<app-birth-date-form-block
#birthDate
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
(onInit)="addFormBlock('birthDate', $event)"
[data]="data.birthDate"
(dataChanges)="patchFormData('birthDate', $event)"
></app-birth-date-form-block>
<app-deviating-address-form-block
[tabIndexStart]="birthDate.tabIndexEnd + 1"
(onInit)="addFormBlock('deviatingDeliveryAddress', $event)"
[organisation]="true"
[nameRequiredMarks]="nameRequiredMarks"
[nameValidatorFns]="nameValidationFns"
[addressRequiredMarks]="addressRequiredMarks"
[addressValidatorFns]="addressValidators"
[defaults]="{ address: { country: 'DEU' } }"
[data]="data.deviatingDeliveryAddress"
(dataChanges)="patchFormData('deviatingDeliveryAddress', $event)"
>
Die Lieferadresse weicht von der Rechnungsadresse ab
</app-deviating-address-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">
Speichern
</ui-spinner>
</button>
</form>

View File

@@ -0,0 +1,47 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { ValidatorFn, Validators } from '@angular/forms';
import { CustomerDTO } from '@swagger/crm';
import { map } from 'rxjs/operators';
import { validateEmail } from '../../validators/email-validator';
import { AbstractCreateCustomer } from '../abstract-create-customer';
@Component({
selector: 'app-create-store-customer',
templateUrl: 'create-store-customer.component.html',
styleUrls: ['../create-customer.scss', 'create-store-customer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CreateStoreCustomerComponent extends AbstractCreateCustomer {
validateAddress = false;
validateShippingAddress = true;
addressRequiredMarks = ['street', 'streetNumber', 'zipCode', 'city', 'country'];
addressValidators: Record<string, ValidatorFn[]> = {
street: [Validators.required],
streetNumber: [Validators.required],
zipCode: [Validators.required],
city: [Validators.required],
country: [Validators.required],
};
emailFormBlockValidators = [Validators.email, validateEmail];
nameRequiredMarks = ['gender', 'firstName', 'lastName'];
nameValidationFns: Record<string, ValidatorFn[]> = {
gender: [Validators.required],
firstName: [Validators.required],
lastName: [Validators.required],
};
saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
customer.isGuestAccount = false;
return this.customerService
.createStoreCustomer(customer)
.pipe(map((res) => res.result))
.toPromise();
}
}

View File

@@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CreateStoreCustomerComponent } from './create-store-customer.component';
import { AddressFormBlockModule } from '../../form-blocks/address';
import { NameFormBlockModule } from '../../form-blocks/name';
import { OrganisationFormBlockModule } from '../../form-blocks/organisation';
import { BirthDateFormBlockModule } from '../../form-blocks/birth-date';
import { CustomerTypeSelectorModule } from '../../shared/customer-type-selector';
import { DeviatingAddressFormBlockComponentModule, EmailFormBlockModule, PhoneNumbersFormBlockModule } from '../../form-blocks';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [
CommonModule,
OrganisationFormBlockModule,
NameFormBlockModule,
AddressFormBlockModule,
DeviatingAddressFormBlockComponentModule,
BirthDateFormBlockModule,
CustomerTypeSelectorModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
UiSpinnerModule,
],
exports: [CreateStoreCustomerComponent],
declarations: [CreateStoreCustomerComponent],
})
export class CreateStoreCustomerModule {}

View File

@@ -0,0 +1,2 @@
export * from './create-store-customer.component';
export * from './create-store-customer.module';

View File

@@ -0,0 +1,98 @@
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
<h1 class="title">Kundendaten erfassen</h1>
<p class="description">
Wenn Sie möchten legen wir Ihnen <br />
gerne ein Onlinekonto an. Dort können <br />
Sie Ihre Bestellungen einsehen.
</p>
<app-customer-type-selector
[processId]="processId$ | async"
[p4mUser]="false"
customerType="webshop"
(valueChanges)="customerTypeChanged($event)"
>
</app-customer-type-selector>
<app-name-form-block
#name
[tabIndexStart]="1"
(onInit)="addFormBlock('name', $event)"
[requiredMarks]="nameRequiredMarks"
[validatorFns]="nameValidationFns"
[data]="data.name"
(dataChanges)="patchFormData('name', $event)"
>
</app-name-form-block>
<app-email-form-block
#email
[tabIndexStart]="name.tabIndexEnd + 1"
(onInit)="addFormBlock('email', $event)"
[requiredMark]="true"
[asyncValidatorFns]="asyncEmailValidatorFns"
[validatorFns]="emailValidatorFns"
[data]="data.email"
(dataChanges)="patchFormData('email', $event)"
></app-email-form-block>
<app-organisation-form-block
#org
[tabIndexStart]="email.tabIndexEnd + 1"
appearence="name"
(onInit)="addFormBlock('organisation', $event)"
[data]="data.organisation"
(dataChanges)="patchFormData('organisation', $event)"
>
</app-organisation-form-block>
<app-address-form-block
#address
[tabIndexStart]="org.tabIndexEnd + 1"
[requiredMarks]="addressRequiredMarks"
[validatorFns]="addressValidators"
(onInit)="addFormBlock('address', $event)"
[defaults]="{ country: 'DEU' }"
[data]="data.address"
(dataChanges)="patchFormData('address', $event)"
>
</app-address-form-block>
<p class="info">Das Anlegen geht für Sie noch schneller, wenn wir Ihnen das initiale Passwort per SMS auf Ihr Mobilgerät schicken.</p>
<app-phone-numbers-form-block
#phoneNumbers
[tabIndexStart]="address.tabIndexEnd + 1"
(onInit)="addFormBlock('phoneNumbers', $event)"
[data]="data.phoneNumbers"
(dataChanges)="patchFormData('phoneNumbers', $event)"
></app-phone-numbers-form-block>
<app-birth-date-form-block
#birthDate
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
(onInit)="addFormBlock('birthDate', $event)"
[data]="data.birthDate"
(dataChanges)="patchFormData('birthDate', $event)"
></app-birth-date-form-block>
<app-deviating-address-form-block
[tabIndexStart]="birthDate.tabIndexEnd + 1"
(onInit)="addFormBlock('deviatingDeliveryAddress', $event)"
appearence="b2b"
[nameRequiredMarks]="nameRequiredMarks"
[nameValidatorFns]="nameValidationFns"
[addressRequiredMarks]="addressRequiredMarks"
[addressValidatorFns]="addressValidators"
[defaults]="{ address: { country: 'DEU' } }"
[data]="data.deviatingDeliveryAddress"
(dataChanges)="patchFormData('deviatingDeliveryAddress', $event)"
>
Die Lieferadresse weicht von der Rechnungsadresse ab
</app-deviating-address-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</form>

View File

@@ -0,0 +1,59 @@
import { Component, ChangeDetectionStrategy, ViewChild } from '@angular/core';
import { ValidatorFn, Validators } from '@angular/forms';
import { CustomerDTO } from '@swagger/crm';
import { map } from 'rxjs/operators';
import { AddressFormBlockComponent, DeviatingAddressFormBlockComponent } from '../../form-blocks';
import { NameFormBlockData } from '../../form-blocks/name/name-form-block-data';
import { validateEmail } from '../../validators/email-validator';
import { AbstractCreateCustomer } from '../abstract-create-customer';
@Component({
selector: 'app-create-webshop-customer',
templateUrl: 'create-webshop-customer.component.html',
styleUrls: ['../create-customer.scss', 'create-webshop-customer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CreateWebshopCustomerComponent extends AbstractCreateCustomer {
validateAddress?: boolean;
validateShippingAddress?: boolean;
nameRequiredMarks = ['gender', 'firstName', 'lastName'];
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required],
title: [],
};
addressRequiredMarks = ['street', 'streetNumber', 'zipCode', 'city', 'country'];
addressValidators: Record<string, ValidatorFn[]> = {
street: [Validators.required],
streetNumber: [Validators.required],
zipCode: [Validators.required],
city: [Validators.required],
country: [Validators.required],
};
communicationDetailsValidators: Record<string, ValidatorFn[]> = {
email: [Validators.required, Validators.email, validateEmail],
};
emailValidatorFns = [Validators.required, Validators.email, validateEmail];
asyncEmailValidatorFns = [this.emailExistsValidator];
@ViewChild(AddressFormBlockComponent, { static: false })
addressFormBlock: AddressFormBlockComponent;
@ViewChild(DeviatingAddressFormBlockComponent, { static: false })
deviatingDeliveryAddressFormBlock: DeviatingAddressFormBlockComponent;
saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
return this.customerService
.createOnlineCustomer(customer)
.pipe(map((res) => res.result))
.toPromise();
}
}

View File

@@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CreateWebshopCustomerComponent } from './create-webshop-customer.component';
import { AddressFormBlockModule } from '../../form-blocks/address';
import { BirthDateFormBlockModule } from '../../form-blocks/birth-date';
import { NameFormBlockModule } from '../../form-blocks/name';
import { OrganisationFormBlockModule } from '../../form-blocks/organisation';
import { CustomerTypeSelectorModule } from '../../shared/customer-type-selector';
import { DeviatingAddressFormBlockComponentModule, EmailFormBlockModule, PhoneNumbersFormBlockModule } from '../../form-blocks';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [
CommonModule,
OrganisationFormBlockModule,
NameFormBlockModule,
AddressFormBlockModule,
DeviatingAddressFormBlockComponentModule,
BirthDateFormBlockModule,
CustomerTypeSelectorModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
UiSpinnerModule,
],
exports: [CreateWebshopCustomerComponent],
declarations: [CreateWebshopCustomerComponent],
})
export class CreateWebshopCustomerModule {}

View File

@@ -0,0 +1,2 @@
export * from './create-webshop-customer.component';
export * from './create-webshop-customer.module';

View File

@@ -0,0 +1,102 @@
import { CustomerDTO, CustomerInfoDTO } from '@swagger/crm';
import { AddressFormBlockData, DeviatingAddressFormBlockData, PhoneNumbersFormBlockData } from '../form-blocks';
import { InterestsFormBlockData } from '../form-blocks/interests/interests-form-block-data';
import { NameFormBlockData } from '../form-blocks/name/name-form-block-data';
import { OrganisationFormBlockData } from '../form-blocks/organisation/organisation-form-block-data';
export interface CustomerCreateFormData {
_meta?: {
customerDto?: CustomerDTO;
customerInfoDto?: CustomerInfoDTO;
p4mRequired?: boolean;
};
agb?: boolean;
address?: AddressFormBlockData;
birthDate?: string;
billingAddress?: DeviatingAddressFormBlockData;
deviatingDeliveryAddress?: DeviatingAddressFormBlockData;
email?: string;
interests?: InterestsFormBlockData;
name?: NameFormBlockData;
newsletter?: boolean;
organisation?: OrganisationFormBlockData;
p4m?: string;
phoneNumbers?: PhoneNumbersFormBlockData;
}
export function mapCustomerDtoToCustomerCreateFormData(customerDto: CustomerDTO): CustomerCreateFormData {
let p4m = customerDto.attributes?.find((attr) => attr.data.key === 'p4mUser')?.data.value;
if (!p4m) {
p4m = customerDto.features?.find((f) => f.key === 'p4mUser')?.value;
}
return {
_meta: {
customerDto,
p4mRequired: !!p4m,
},
p4m,
address: customerDto.address,
birthDate: customerDto.dateOfBirth,
email: customerDto?.communicationDetails?.email,
name: {
gender: customerDto?.gender,
title: customerDto?.title,
firstName: customerDto?.firstName,
lastName: customerDto?.lastName,
},
organisation: customerDto.organisation,
phoneNumbers: {
mobile: customerDto?.communicationDetails?.mobile,
phone: customerDto?.communicationDetails?.phone,
},
agb: !!p4m,
};
}
export function mapCustomerInfoDtoToCustomerCreateFormData(customerInfoDto: CustomerInfoDTO): CustomerCreateFormData {
const p4m = customerInfoDto.features?.find((f) => f.key === 'p4mUser')?.value;
const interests = customerInfoDto.features
?.filter((f) => f.group === 'KUBI_INTERESSEN')
.reduce<Record<string, boolean>>((dic, f) => {
dic[f.key] = true;
return dic;
}, {});
const newsletter = !!customerInfoDto.features?.find((f) => f.key === 'kubi_newsletter' && f.group === 'KUBI_NEWSLETTER');
return {
_meta: {
customerInfoDto,
p4mRequired: !!p4m,
},
p4m,
address: customerInfoDto.address,
birthDate: customerInfoDto.dateOfBirth,
email: customerInfoDto?.communicationDetails?.email,
name: {
gender: customerInfoDto?.gender,
title: customerInfoDto?.title,
firstName: customerInfoDto?.firstName,
lastName: customerInfoDto?.lastName,
},
organisation: customerInfoDto.organisation,
phoneNumbers: {
mobile: customerInfoDto?.communicationDetails?.mobile,
phone: customerInfoDto?.communicationDetails?.phone,
},
agb: !!p4m,
interests,
newsletter,
};
}
export function encodeFormData(data: CustomerCreateFormData): string {
return encodeURIComponent(JSON.stringify(data));
}
export function decodeFormData(data: string): CustomerCreateFormData {
return JSON.parse(decodeURIComponent(data));
}

View File

@@ -0,0 +1,22 @@
import { CustomerDTO, Gender } from '@swagger/crm';
export interface CreateCustomerQueryParams {
p4mNumber?: string;
customerId?: number;
gender?: Gender;
title?: string;
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
mobile?: string;
street?: string;
streetNumber?: string;
zipCode?: string;
city?: string;
country?: string;
info?: string;
organisationName?: string;
organisationDepartment?: string;
organisationVatId?: string;
}

View File

@@ -0,0 +1 @@
export * from './customer-query-params';

View File

@@ -0,0 +1,9 @@
export * from './create-b2b-customer';
export * from './create-guest-customer';
export * from './create-p4m-customer';
export * from './create-store-customer';
export * from './create-webshop-customer';
export * from './defs';
export * from './abstract-create-customer';
export * from './create-customer.module';
export * from './customer-create-form-data';

View File

@@ -0,0 +1,2 @@
export * from './update-p4m-webshop-customer.component';
export * from './update-p4m-webshop-customer.module';

View File

@@ -0,0 +1,87 @@
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
<h1 class="title flex flex-row items-center justify-center">Kundenkartendaten erfasen</h1>
<app-p4m-number-form-block
#p4mBlock
[tabIndexStart]="1"
(onInit)="addFormBlock('p4m', $event)"
[data]="data.p4m"
(dataChanges)="patchFormData('p4m', $event)"
[focusAfterInit]="!data?._meta?.p4mRequired"
>
</app-p4m-number-form-block>
<app-newsletter-form-block
#newsletterBlock
[tabIndexStart]="p4mBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('newsletter', $event)"
[data]="data.newsletter"
(dataChanges)="patchFormData('newsletter', $event)"
[focusAfterInit]="data?._meta?.p4mRequired"
>
</app-newsletter-form-block>
<app-deviating-address-form-block
#dbaBlock
[defaults]="{ address: { country: 'DEU' } }"
[tabIndexStart]="newsletterBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('billingAddress', $event)"
[data]="data.billingAddress"
(dataChanges)="patchFormData('billingAddress', $event)"
[nameRequiredMarks]="nameRequiredMarks"
[nameValidatorFns]="nameValidationFns"
[addressRequiredMarks]="shippingAddressRequiredMarks"
[addressValidatorFns]="shippingAddressValidators"
>
Abweichende Rechnugsaddresse anlegen
</app-deviating-address-form-block>
<app-deviating-address-form-block
#ddaBlock
[defaults]="{ address: { country: 'DEU' } }"
[tabIndexStart]="dbaBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('deviatingDeliveryAddress', $event)"
[data]="data.deviatingDeliveryAddress"
(dataChanges)="patchFormData('deviatingDeliveryAddress', $event)"
[nameRequiredMarks]="nameRequiredMarks"
[nameValidatorFns]="nameValidationFns"
[addressRequiredMarks]="shippingAddressRequiredMarks"
[addressValidatorFns]="shippingAddressValidators"
>
Abweichende Lieferadresse anlegen
</app-deviating-address-form-block>
<app-birth-date-form-block
#bdBlock
[tabIndexStart]="ddaBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('birthDate', $event)"
[data]="data.birthDate"
(dataChanges)="patchFormData('birthDate', $event)"
[requiredMark]="true"
[validatorFns]="birthDateValidatorFns"
>
</app-birth-date-form-block>
<app-interests-form-block
#inBlock
[tabIndexStart]="bdBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('interests', $event)"
[data]="data.interests"
(dataChanges)="patchFormData('interests', $event)"
></app-interests-form-block>
<app-accept-agb-form-block
[tabIndexStart]="inBlock.tabIndexEnd + 1"
(onInit)="addFormBlock('agb', $event)"
[data]="data.agb"
(dataChanges)="patchFormData('agb', $event)"
[requiredMark]="true"
[validatorFns]="agbValidatorFns"
>
</app-accept-agb-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</form>

View File

@@ -0,0 +1,19 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { CustomerDTO } from '@swagger/crm';
import { AbstractCreateCustomer } from '../abstract-create-customer';
@Component({
selector: 'page-update-p4m-webshop-customer',
templateUrl: 'update-p4m-webshop-customer.component.html',
styleUrls: ['../create-customer.scss', 'update-p4m-webshop-customer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UpdateP4MWebshopCustomerComponent extends AbstractCreateCustomer {
validateAddress = true;
validateShippingAddress = true;
saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
throw new Error('Method not implemented.');
}
}

View File

@@ -0,0 +1,38 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UpdateP4MWebshopCustomerComponent } from './update-p4m-webshop-customer.component';
import {
AddressFormBlockModule,
BirthDateFormBlockModule,
InterestsFormBlockModule,
NameFormBlockModule,
OrganisationFormBlockModule,
P4mNumberFormBlockModule,
NewsletterFormBlockModule,
DeviatingAddressFormBlockComponentModule,
AcceptAGBFormBlockModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
} from '../../form-blocks';
@NgModule({
imports: [
CommonModule,
AddressFormBlockModule,
BirthDateFormBlockModule,
InterestsFormBlockModule,
NameFormBlockModule,
OrganisationFormBlockModule,
P4mNumberFormBlockModule,
NewsletterFormBlockModule,
DeviatingAddressFormBlockComponentModule,
AcceptAGBFormBlockModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
],
exports: [UpdateP4MWebshopCustomerComponent],
declarations: [UpdateP4MWebshopCustomerComponent],
})
export class UpdateP4MWebshopCustomerModule {}

View File

@@ -1,222 +0,0 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
Für eine B2B Bestellung benötigen Sie<br />
einen Firmenaccount. Wir legen diesen<br />
gerne direkt für Sie an.
</p>
<page-customer-type-selector [ngModel]="cusotmers$ | async" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" #formControl [formGroup]="control" (ngSubmit)="submit()">
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname" requiredMark="*">
<input uiInput type="text" formControlName="name" tabindex="1" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung" [clearable]="false">
<input uiInput type="text" formControlName="department" tabindex="2" />
</ui-form-control>
<ui-form-control label="USt-ID" [clearable]="false">
<input uiInput type="text" formControlName="vatId" tabindex="3" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="true" [requiredMark]="control.value.lastName ? '*' : ''">
<ui-select formControlName="gender" tabindex="4">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control [clearable]="true" label="Titel">
<ui-select formControlName="title" tabindex="5">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" [clearable]="false">
<input uiInput type="text" formControlName="lastName" tabindex="6" />
</ui-form-control>
<ui-form-control label="Vorname" [clearable]="false">
<input uiInput type="text" formControlName="firstName" tabindex="7" />
</ui-form-control>
</div>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="8" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="9" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="10" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="11" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="12" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="true" requiredMark="*">
<ui-select formControlName="country" tabindex="13">
<ui-select-option
*ngFor="let country of countries$ | async"
[label]="country.name"
[value]="country.isO3166_A_3"
></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail" [clearable]="false">
<input uiInput type="mail" formControlName="email" tabindex="14" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="15" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="16" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control class="different-shipping-address" label="Die Lieferadresse weicht von der Rechnungsadresse ab">
<input uiInput type="checkbox" formControlName="differentShippingAddress" tabindex="17" />
</ui-form-control>
<div *ngIf="control.value.differentShippingAddress" formGroupName="shippingAddress">
<ng-container formGroupName="organisation">
<ui-form-control
label="Firmenname"
[requiredMark]="
control.get('shippingAddress').value.lastName &&
control.get('shippingAddress').value.firstName &&
control.get('shippingAddress').value.gender
? ''
: '*'
"
>
<input uiInput type="text" formControlName="name" tabindex="18" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="19" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="20" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control
label="Anrede"
[clearable]="true"
[requiredMark]="control.get('shippingAddress').get('organisation').value.name ? '' : '*'"
>
<ui-select formControlName="gender" tabindex="21">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control [clearable]="true" label="Titel">
<ui-select formControlName="title" tabindex="22">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" [requiredMark]="control.get('shippingAddress').get('organisation').value.name ? '' : '*'">
<input uiInput type="text" formControlName="lastName" tabindex="23" />
</ui-form-control>
<ui-form-control label="Vorname" [requiredMark]="control.get('shippingAddress').get('organisation').value.name ? '' : '*'">
<input uiInput type="text" formControlName="firstName" tabindex="24" />
</ui-form-control>
</div>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="25" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="26" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="27" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="28" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="29" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="true" requiredMark="*">
<ui-select formControlName="country" tabindex="30">
<ui-select-option
*ngFor="let country of countries$ | async"
[label]="country.name"
[value]="country.isO3166_A_3"
></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail">
<input uiInput type="text" formControlName="email" tabindex="31" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="32" />
</ui-form-control>
<ui-form-control label="Mobilnummer">
<input uiInput type="tel" formControlName="mobile" tabindex="33" />
</ui-form-control>
</div>
</ng-container>
</div>
<div class="center sticky-bottom">
<button class="create-customer-submit" type="submit" [disabled]="control.disabled">
<ui-spinner [show]="!control.enabled">
Speichern
</ui-spinner>
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,229 +0,0 @@
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
import { CustomerDTO } from '@swagger/crm';
import { UiModalService } from '@ui/modal';
import { AddressSelectionModalService } from '../modals/address-selection-modal/address-selection-modal.service';
import { CustomerCreateComponentBase } from './customer-create.component';
import { validateEmail } from '../validators/email-validator';
import { DomainCheckoutService } from '@domain/checkout';
import { Subscription } from 'rxjs';
import { genderLastNameValidator } from '../validators/gender-b2b-validator';
import { UiValidators } from '@ui/validators';
import { organisationB2bDeliveryValidator } from '../validators/organisation-delivery-b2b-validator';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'page-customer-create-b2b',
templateUrl: 'customer-create-b2b.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateB2BComponent extends CustomerCreateComponentBase implements OnInit, OnDestroy {
@ViewChild('formControl')
formControlRef: ElementRef;
subscription: Subscription;
get shippingAddressGroup(): AbstractControl {
return this.control?.get('shippingAddress');
}
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
private fb: FormBuilder,
public modal: UiModalService,
public customerService: CrmCustomerService,
public breadcrumb: BreadcrumbService,
public application: ApplicationService,
public addressSelectionModal: AddressSelectionModalService,
public checkoutService: DomainCheckoutService,
public cdr: ChangeDetectorRef
) {
super();
}
ngOnInit(): void {
this.init();
this.shippingAddressGroup.disable();
this.subscription = this.control.get('differentShippingAddress').valueChanges.subscribe((isChecked) => {
if (isChecked) {
this.shippingAddressGroup.enable();
this.setShippingAddressValidators(this.shippingAddressGroup);
} else {
this.shippingAddressGroup.disable();
this.clearShippingAddressValidators(this.shippingAddressGroup);
this.control.updateValueAndValidity();
}
});
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
createControl(customer?: CustomerDTO): FormGroup {
const fb = this.fb;
return fb.group(
{
organisation: fb.group({
name: fb.control(customer?.organisation?.name, [Validators.required]),
department: fb.control(customer?.organisation?.department),
vatId: fb.control(customer?.organisation?.vatId),
}),
gender: fb.control(customer?.gender),
title: fb.control(customer?.title),
lastName: fb.control(customer?.lastName),
firstName: fb.control(customer?.firstName),
communicationDetails: fb.group({
email: fb.control(customer?.communicationDetails?.email, [validateEmail]),
phone: fb.control(customer?.communicationDetails?.phone, [UiValidators.phone]),
mobile: fb.control(customer?.communicationDetails?.mobile, [UiValidators.phone]),
}),
address: fb.group({
street: fb.control(customer?.address?.street, [Validators.required]),
streetNumber: fb.control(customer?.address?.streetNumber, [Validators.required]),
zipCode: fb.control(customer?.address?.zipCode, [Validators.required]),
city: fb.control(customer?.address?.city, [Validators.required]),
info: fb.control(customer?.address?.info),
country: fb.control(customer?.address?.country || 'DEU', [Validators.required]),
}),
differentShippingAddress: fb.control(false),
shippingAddress: fb.group({
organisation: fb.group({
name: fb.control(''),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(undefined),
title: fb.control(''),
firstName: fb.control(''),
lastName: fb.control(''),
address: fb.group({
street: fb.control(''),
streetNumber: fb.control(''),
zipCode: fb.control(''),
city: fb.control(''),
info: fb.control(''),
country: fb.control('DEU'),
}),
communicationDetails: fb.group({
email: fb.control(''),
phone: fb.control(''),
mobile: fb.control(''),
}),
}),
},
{ validators: [genderLastNameValidator(true), organisationB2bDeliveryValidator()] }
);
}
async submit(): Promise<void> {
if (this.control.disabled) {
return;
}
if (this.control.invalid) {
this.control.disable({ emitEvent: false });
setTimeout(() => {
this.enableControl();
this.focusFirstInvalidFormControl(this.formControlRef);
}, 200);
return;
}
this.control.disable({ emitEvent: false });
try {
const address = await this.addressSelectionModal.validateAddress(this.control.value.address);
if (address) {
if (typeof address === 'string') {
if (address === 'close') {
this.enableControl();
return;
}
} else {
this.control.patchValue({ address });
}
}
} catch (error) {
this.enableControl();
if (error instanceof HttpErrorResponse) {
if (error.error.invalidProperties) {
this.setValidateAddressError(error.error?.invalidProperties, this.control.get('address'));
}
}
return;
}
try {
if (this.control.value.differentShippingAddress) {
const shippingAddress = await this.addressSelectionModal.validateAddress(this.control.value.shippingAddress.address);
if (shippingAddress) {
if (typeof shippingAddress === 'string') {
if (shippingAddress === 'close') {
this.enableControl();
return;
}
} else {
this.control.patchValue({ shippingAddress });
}
}
}
} catch (error) {
this.enableControl();
if (error instanceof HttpErrorResponse) {
if (error.error.invalidProperties) {
this.setValidateAddressError(error.error?.invalidProperties, this.control.get('shippingAddress').get('address'));
}
}
return;
}
try {
const response = await this.customerService.createB2BCustomer(this.control.value).toPromise();
if (this.control.value.differentShippingAddress) {
try {
const shippingResponse = await this.customerService
.createShippingAddress(response.result.id, this.control.value.shippingAddress, true)
.toPromise();
if (shippingResponse.error) {
throw new Error(shippingResponse.message);
}
} catch (error) {
console.error(error);
}
}
if (response.error) {
this.enableControl();
throw new Error(response.message);
} else {
this.removeBreadcrumb();
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', response.result.id]);
}
} catch (error) {
this.setValidationError(error.error?.invalidProperties, this.control);
}
}
enableControl() {
this.control.enable();
if (!this.control.get('differentShippingAddress').value) {
this.control.get('differentShippingAddress').disable();
}
this.control.markAllAsTouched();
this.control.updateValueAndValidity();
}
}

View File

@@ -1,204 +0,0 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
Um Ihnen den ausgewählten Service <br />
zu ermöglichen, legen wir Ihnen <br />
gerne ein Kundenkonto an.
</p>
<page-customer-type-selector [ngModel]="cusotmers$ | async" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" #formControl [formGroup]="control" (ngSubmit)="submit()">
<div class="control-row">
<ui-form-control [clearable]="true" label="Anrede" requiredMark="*">
<ui-select formControlName="gender" tabindex="1">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control [clearable]="true" label="Titel">
<ui-select formControlName="title" tabindex="2">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="3" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="4" />
</ui-form-control>
</div>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail" [clearable]="false">
<input uiInput type="mail" formControlName="email" tabindex="5" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="6" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße">
<input uiInput type="text" formControlName="street" tabindex="7" />
</ui-form-control>
<ui-form-control label="Hausnummer">
<input uiInput type="text" formControlName="streetNumber" tabindex="8" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ">
<input uiInput type="text" formControlName="zipCode" tabindex="9" />
</ui-form-control>
<ui-form-control label="Ort">
<input uiInput type="text" formControlName="city" tabindex="10" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="11" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="true">
<ui-select formControlName="country" tabindex="12">
<ui-select-option
*ngFor="let country of countries$ | async"
[label]="country.name"
[value]="country.isO3166_A_3"
></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
<ng-container formGroupName="communicationDetails">
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="13" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="14" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)">
<input uiDateInput type="text" formControlName="dateOfBirth" tabindex="15" />
</ui-form-control>
<ng-container formGroupName="organisation" *ngIf="!!control.value?.organisation?.name">
<h1>Geschäftliche Angaben</h1>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="16" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="17" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control class="different-shipping-address" label="Die Lieferadresse weicht von der Rechnungsadresse ab">
<input uiInput type="checkbox" formControlName="differentShippingAddress" tabindex="18" />
</ui-form-control>
<div *ngIf="control.value.differentShippingAddress" formGroupName="shippingAddress">
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="19" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="20" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="21" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="true" requiredMark="*">
<ui-select formControlName="gender" tabindex="22">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control [clearable]="true" label="Titel">
<ui-select formControlName="title" tabindex="23">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="24" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="25" />
</ui-form-control>
</div>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="26" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="27" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="28" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="29" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="30" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="true" requiredMark="*">
<ui-select formControlName="country" tabindex="31">
<ui-select-option
*ngFor="let country of countries$ | async"
[label]="country.name"
[value]="country.isO3166_A_3"
></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
</div>
<div class="center sticky-bottom">
<button class="create-customer-submit" type="submit" [disabled]="control.disabled">
<ui-spinner [show]="!control.enabled">
Speichern
</ui-spinner>
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,275 +0,0 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, CustomerDTO } from '@swagger/crm';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { AddressSelectionModalService } from '../modals/address-selection-modal/address-selection-modal.service';
import { CustomerCreateComponentBase } from './customer-create.component';
import { validateEmail } from '../validators/email-validator';
import { Subscription } from 'rxjs';
import { DomainCheckoutService } from '@domain/checkout';
import { UiValidators } from '@ui/validators';
import { isStringEmpty } from '@utils/common';
import { HttpErrorResponse } from '@angular/common/http';
import { map } from 'rxjs/operators';
@Component({
selector: 'page-customer-create-branch',
templateUrl: 'customer-create-branch.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateBranchComponent extends CustomerCreateComponentBase implements OnInit, OnDestroy {
@ViewChild('formControl')
formControlRef: ElementRef;
shippingAddressSubscription: Subscription;
organisationSubscription: Subscription;
addressSubscription: Subscription;
get addressGroup(): AbstractControl {
return this.control?.get('address');
}
get shippingAddressGroup(): AbstractControl {
return this.control?.get('shippingAddress');
}
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
private fb: FormBuilder,
public modal: UiModalService,
public customerService: CrmCustomerService,
public breadcrumb: BreadcrumbService,
public application: ApplicationService,
public addressSelectionModal: AddressSelectionModalService,
public checkoutService: DomainCheckoutService,
public cdr: ChangeDetectorRef,
private _modal: UiModalService
) {
super();
}
ngOnInit() {
this.init();
if (!!this.p4mUser) {
this.cusotmers$ = this.cusotmers$.pipe(
map((data) => ({
values: data.values.filter((f) => f.value === 'store'),
}))
);
}
this.shippingAddressGroup.disable();
this.shippingAddressSubscription = this.control.get('differentShippingAddress').valueChanges.subscribe((isChecked) => {
if (isChecked) {
this.shippingAddressGroup.enable();
this.setShippingAddressValidators(this.shippingAddressGroup);
} else {
this.shippingAddressGroup.disable();
this.clearShippingAddressValidators(this.shippingAddressGroup);
this.control.updateValueAndValidity();
}
});
this.organisationSubscription = this.organisationGroup.get('name').valueChanges.subscribe((value: string) => {
if (!value) {
this.organisationGroup.get('department').reset('', { onlySelf: true });
this.organisationGroup.get('vatId').reset('', { onlySelf: true });
}
});
this.addressSubscription = this.addressGroup.valueChanges.subscribe((value: AddressDTO) => {
if (value) {
const country = this.addressGroup.get('country');
if (!country.value) {
country.setValue('DEU');
} else if (this.isAddressEmpty(value)) {
country.setValue('', { onlySelf: true });
}
}
});
}
isAddressEmpty(value: AddressDTO): boolean {
return (
isStringEmpty(value?.street) && isStringEmpty(value?.streetNumber) && isStringEmpty(value?.zipCode) && isStringEmpty(value?.city)
);
}
ngOnDestroy(): void {
if (this.shippingAddressSubscription) {
this.shippingAddressSubscription.unsubscribe();
}
if (this.organisationSubscription) {
this.organisationSubscription.unsubscribe();
}
if (this.addressSubscription) {
this.addressSubscription.unsubscribe();
}
}
createControl(customer?: CustomerDTO): FormGroup {
const fb = this.fb;
return fb.group({
organisation: fb.group({
name: fb.control(customer?.organisation?.name),
department: fb.control(customer?.organisation?.department),
vatId: fb.control(customer?.organisation?.vatId),
}),
gender: fb.control(customer?.gender, [Validators.required, Validators.min(1)]),
title: fb.control(customer?.title),
lastName: fb.control(customer?.lastName, [Validators.required]),
firstName: fb.control(customer?.firstName, [Validators.required]),
dateOfBirth: fb.control(customer?.dateOfBirth, [UiValidators.date]),
communicationDetails: fb.group({
email: fb.control(customer?.communicationDetails?.email, [validateEmail]),
phone: fb.control(customer?.communicationDetails?.phone, [UiValidators.phone]),
mobile: fb.control(customer?.communicationDetails?.mobile, [UiValidators.phone]),
}),
address: fb.group({
street: fb.control(customer?.address?.street),
streetNumber: fb.control(customer?.address?.streetNumber),
zipCode: fb.control(customer?.address?.zipCode),
city: fb.control(customer?.address?.city),
country: fb.control(!this.isAddressEmpty(customer?.address) ? customer?.address?.country : ''),
info: fb.control(customer?.address?.info),
}),
differentShippingAddress: fb.control(false),
shippingAddress: fb.group({
organisation: fb.group({
name: fb.control(''),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(undefined),
title: fb.control(''),
firstName: fb.control(''),
lastName: fb.control(''),
address: fb.group({
street: fb.control(''),
streetNumber: fb.control(''),
zipCode: fb.control(''),
city: fb.control(''),
info: fb.control(''),
country: fb.control('DEU'),
}),
}),
});
}
async submit(): Promise<void> {
if (this.control.disabled) {
return;
}
if (this.control.invalid) {
this.control.disable({ emitEvent: false });
setTimeout(() => {
this.enableControl();
this.focusFirstInvalidFormControl(this.formControlRef);
}, 200);
return;
}
this.control.disable({ emitEvent: false });
try {
let address = await this.addressSelectionModal.validateAddress(this.control.value.address);
if (address) {
if (typeof address === 'string') {
if (address === 'close') {
this.enableControl();
return;
}
} else {
this.control.patchValue({ address });
}
}
} catch (error) {
this.enableControl();
if (error instanceof HttpErrorResponse) {
if (error.error.invalidProperties) {
this.setValidateAddressError(error.error?.invalidProperties, this.control.get('address'));
}
}
return;
}
try {
if (this.control.value.differentShippingAddress) {
const shippingAddress = await this.addressSelectionModal.validateAddress(this.control.value.shippingAddress.address);
if (shippingAddress) {
if (typeof shippingAddress === 'string') {
if (shippingAddress === 'close') {
this.enableControl();
return;
}
} else {
this.control.patchValue({ shippingAddress });
}
}
}
} catch (error) {
this.enableControl();
if (error instanceof HttpErrorResponse) {
if (error.error.invalidProperties) {
this.setValidateAddressError(error.error?.invalidProperties, this.control.get('shippingAddress').get('address'));
}
}
return;
}
try {
let newCustomer: CustomerDTO = this.control.value;
if (!!this.p4mUser) {
newCustomer.features = newCustomer.features ? [...newCustomer.features, this.p4mUser] : [this.p4mUser];
}
const response = await this.customerService.createBranchCustomer(newCustomer).toPromise();
if (this.control.value.differentShippingAddress) {
try {
const shippingResponse = await this.customerService
.createShippingAddress(response.result.id, this.control.value.shippingAddress, true)
.toPromise();
if (shippingResponse.error) {
throw new Error(shippingResponse.message);
}
} catch (error) {
console.error(error);
}
}
if (response.error) {
this.enableControl();
throw new Error(response.message);
} else {
this.removeBreadcrumb();
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', response.result.id]);
}
} catch (error) {
this.setValidationError(error.error?.invalidProperties, this.control);
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim Anlegen eines Filialkunden' });
}
}
enableControl() {
this.control.enable();
if (!this.control.get('differentShippingAddress').value) {
this.control.get('differentShippingAddress').disable();
}
this.control.markAllAsTouched();
this.control.updateValueAndValidity();
}
}

View File

@@ -1,209 +0,0 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
Wenn Sie möchten legen wir Ihnen <br />
gerne ein Onlinekonto an. Dort können <br />
Sie Ihre Bestellungen einsehen.
</p>
<page-customer-type-selector [ngModel]="cusotmers$ | async" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" #formControl [formGroup]="control" (ngSubmit)="submit()">
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="true" requiredMark="*">
<ui-select formControlName="gender" tabindex="1">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control [clearable]="true" label="Titel">
<ui-select formControlName="title" tabindex="2">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="3" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="4" />
</ui-form-control>
</div>
<p class="bold">
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere
als die Übermittlungskosten nach den Basistarifen entstehen.
</p>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail" requiredMark="*">
<input uiInput type="mail" formControlName="email" tabindex="5" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="6" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="7" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="8" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="9" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="10" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="11" />
</ui-form-control>
<ui-form-control [clearable]="true" label="Land" requiredMark="*">
<ui-select formControlName="country" tabindex="12">
<ui-select-option
*ngFor="let country of countries$ | async"
[label]="country.name"
[value]="country.isO3166_A_3"
></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
<ng-container formGroupName="communicationDetails">
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="13" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="14" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)">
<input type="text" formControlName="dateOfBirth" uiDateInput tabindex="15" />
</ui-form-control>
<ng-container formGroupName="organisation" *ngIf="!!control.value?.organisation?.name">
<h1>Geschäftliche Angaben</h1>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="16" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="17" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control class="different-shipping-address" label="Die Lieferadresse weicht von der Rechnungsadresse ab">
<input uiInput type="checkbox" formControlName="differentShippingAddress" tabindex="18" />
</ui-form-control>
<div *ngIf="control.value.differentShippingAddress" formGroupName="shippingAddress">
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="19" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="20" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="21" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="true" requiredMark="*">
<ui-select formControlName="gender" tabindex="22">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control label="Titel" [clearable]="true">
<ui-select formControlName="title" tabindex="23">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="24" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="25" />
</ui-form-control>
</div>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="26" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="27" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="28" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="29" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="30" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="true" requiredMark="*">
<ui-select formControlName="country" tabindex="31">
<ui-select-option
*ngFor="let country of countries$ | async"
[label]="country.name"
[value]="country.isO3166_A_3"
></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
</div>
<div class="center sticky-bottom">
<button class="create-customer-submit" type="submit" [disabled]="control.disabled">
<ui-spinner [show]="!control.enabled">
Speichern
</ui-spinner>
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,245 +0,0 @@
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef, OnDestroy, ElementRef, ViewChild } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
import { CustomerDTO } from '@swagger/crm';
import { UiValidators } from '@ui/validators';
import { UiModalService } from '@ui/modal';
import { AddressSelectionModalService } from '../modals/address-selection-modal/address-selection-modal.service';
import { CustomerCreateComponentBase } from './customer-create.component';
import { validateEmail } from '../validators/email-validator';
import { Subscription } from 'rxjs';
import { DomainCheckoutService } from '@domain/checkout';
import { map } from 'rxjs/operators';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'page-customer-create-guest',
templateUrl: 'customer-create-guest.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateGuestComponent extends CustomerCreateComponentBase implements OnInit, OnDestroy {
@ViewChild('formControl')
formControlRef: ElementRef;
shippingAddressSubscription: Subscription;
organisationSubscription: Subscription;
get shippingAddressGroup(): AbstractControl {
return this.control?.get('shippingAddress');
}
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
private fb: FormBuilder,
public modal: UiModalService,
public customerService: CrmCustomerService,
public breadcrumb: BreadcrumbService,
public application: ApplicationService,
public addressSelectionModal: AddressSelectionModalService,
public checkoutService: DomainCheckoutService,
public cdr: ChangeDetectorRef
) {
super();
}
ngOnInit(): void {
this.init();
this.countries$ = this.countries$.pipe(map((countries) => countries.filter((country) => country.name === 'Deutschland')));
this.shippingAddressGroup.disable();
this.shippingAddressSubscription = this.control.get('differentShippingAddress').valueChanges.subscribe((isChecked) => {
if (isChecked) {
this.shippingAddressGroup.enable();
this.setShippingAddressValidators(this.shippingAddressGroup);
} else {
this.shippingAddressGroup.disable();
this.clearShippingAddressValidators(this.shippingAddressGroup);
this.control.updateValueAndValidity();
}
});
this.organisationSubscription = this.organisationGroup.get('name').valueChanges.subscribe((value: string) => {
if (!value) {
this.organisationGroup.get('department').reset('', { onlySelf: true });
this.organisationGroup.get('vatId').reset('', { onlySelf: true });
}
});
}
ngOnDestroy(): void {
if (this.shippingAddressSubscription) {
this.shippingAddressSubscription.unsubscribe();
}
if (this.organisationSubscription) {
this.organisationSubscription.unsubscribe();
}
}
createControl(customer?: CustomerDTO): FormGroup {
const { fb } = this;
return fb.group({
organisation: fb.group({
name: fb.control(customer?.organisation?.name),
department: fb.control(customer?.organisation?.department),
vatId: fb.control(customer?.organisation?.vatId),
}),
gender: fb.control(customer?.gender, [Validators.required, Validators.min(1)]),
title: fb.control(customer?.title),
lastName: fb.control(customer?.lastName, [Validators.required]),
firstName: fb.control(customer?.firstName, [Validators.required]),
dateOfBirth: fb.control(customer?.dateOfBirth, [UiValidators.date]),
communicationDetails: fb.group({
email: fb.control(customer?.communicationDetails?.email, [Validators.required, validateEmail]),
phone: fb.control(customer?.communicationDetails?.phone, [UiValidators.phone]),
mobile: fb.control(customer?.communicationDetails?.mobile, [UiValidators.phone]),
}),
address: fb.group({
street: fb.control(customer?.address?.street, [Validators.required]),
streetNumber: fb.control(customer?.address?.streetNumber, [Validators.required]),
zipCode: fb.control(customer?.address?.zipCode, [Validators.required]),
city: fb.control(customer?.address?.city, [Validators.required]),
country: fb.control(customer?.address?.country || 'DEU', [Validators.required]),
info: fb.control(customer?.address?.info),
}),
differentShippingAddress: fb.control(false),
shippingAddress: fb.group({
organisation: fb.group({
name: fb.control(''),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(undefined),
title: fb.control(''),
firstName: fb.control(''),
lastName: fb.control(''),
address: fb.group({
street: fb.control(''),
streetNumber: fb.control(''),
zipCode: fb.control(''),
city: fb.control(''),
info: fb.control(''),
country: fb.control('DEU'),
}),
}),
});
}
async submit(): Promise<void> {
if (this.control.disabled) {
return;
}
if (this.control.invalid) {
this.control.disable({ emitEvent: false });
setTimeout(() => {
this.enableControl();
this.focusFirstInvalidFormControl(this.formControlRef);
}, 200);
return;
}
this.control.disable({ emitEvent: false });
try {
const address = await this.addressSelectionModal.validateAddress(this.control.value.address);
if (address) {
if (typeof address === 'string') {
if (address === 'close') {
this.enableControl();
return;
}
} else {
this.control.patchValue({ address });
}
}
} catch (error) {
this.enableControl();
if (error instanceof HttpErrorResponse) {
if (error.error.invalidProperties) {
this.setValidateAddressError(error.error?.invalidProperties, this.control.get('address'));
}
}
return;
}
try {
if (this.control.value.differentShippingAddress) {
const shippingAddress = await this.addressSelectionModal.validateAddress(this.control.value.shippingAddress.address);
if (shippingAddress) {
if (typeof shippingAddress === 'string') {
if (shippingAddress === 'close') {
this.enableControl();
return;
}
} else {
this.control.patchValue({ shippingAddress });
}
}
}
} catch (error) {
this.enableControl();
if (error instanceof HttpErrorResponse) {
if (error.error.invalidProperties) {
this.setValidateAddressError(error.error?.invalidProperties, this.control.get('shippingAddress').get('address'));
}
}
return;
}
try {
let newCustomer: CustomerDTO = this.control.value;
if (this.control.value.differentShippingAddress) {
newCustomer.shippingAddresses = [
{
data: {
...this.control.value.shippingAddress,
},
},
];
}
const response = await this.customerService.createGuestCustomer(newCustomer).toPromise();
if (this.control.value.differentShippingAddress) {
try {
const shippingResponse = await this.customerService
.createShippingAddress(response.result.id, this.control.value.shippingAddress, true)
.toPromise();
if (shippingResponse.error) {
throw new Error(shippingResponse.message);
}
} catch (error) {
console.error(error);
}
}
if (response.error) {
this.enableControl();
throw new Error(response.message);
} else {
this.removeBreadcrumb();
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', response.result.id]);
}
} catch (error) {
this.setValidationError(error.error?.invalidProperties, this.control);
}
}
enableControl() {
this.control.enable();
if (!this.control.get('differentShippingAddress').value) {
this.control.get('differentShippingAddress').disable();
}
this.control.markAllAsTouched();
this.control.updateValueAndValidity();
}
}

View File

@@ -1,206 +0,0 @@
<div class="card">
<h1>Kundendaten erfassen</h1>
<p>
Wenn Sie möchten legen wir Ihnen <br />
gerne ein Onlinekonto an. Dort können <br />
Sie Ihre Bestellungen einsehen.
</p>
<page-customer-type-selector [ngModel]="cusotmers$ | async" (ngModelChange)="setType($event)"></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" #formControl [formGroup]="control" (ngSubmit)="submit()">
<div class="control-row">
<ui-form-control [clearable]="true" label="Anrede" requiredMark="*">
<ui-select formControlName="gender" tabindex="1">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control [clearable]="true" label="Titel">
<ui-select formControlName="title" tabindex="2">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="3" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="4" />
</ui-form-control>
</div>
<ng-container formGroupName="communicationDetails">
<ui-form-control label="E-Mail" requiredMark="*">
<input uiInput type="mail" formControlName="email" tabindex="5" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="6" />
</ui-form-control>
</ng-container>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="7" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="8" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="9" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="10" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="11" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="true" requiredMark="*">
<ui-select formControlName="country" tabindex="12">
<ui-select-option
*ngFor="let country of countries$ | async"
[label]="country.name"
[value]="country.isO3166_A_3"
></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
<p class="bold">Das Anlegen geht für Sie noch schneller, wenn wir Ihnen das initiale Passwort per SMS auf Ihr Mobilgerät schicken.</p>
<ng-container formGroupName="communicationDetails">
<div class="control-row">
<ui-form-control label="Festnetznummer">
<input uiInput type="tel" formControlName="phone" tabindex="13" />
</ui-form-control>
<ui-form-control label="Mobilnummer" [clearable]="false">
<input uiInput type="tel" formControlName="mobile" tabindex="14" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)" [requiredMark]="!!p4mUser ? '*' : ''">
<input uiDateInput type="text" formControlName="dateOfBirth" tabindex="15" />
</ui-form-control>
<ng-container formGroupName="organisation" *ngIf="!!control.value?.organisation?.name">
<h1>Geschäftliche Angaben</h1>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="16" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="17" />
</ui-form-control>
</div>
</ng-container>
<ui-form-control class="different-shipping-address" label="Die Lieferadresse weicht von der Rechnungsadresse ab">
<input uiInput type="checkbox" formControlName="differentShippingAddress" tabindex="18" />
</ui-form-control>
<div *ngIf="control.value.differentShippingAddress" formGroupName="shippingAddress">
<ng-container formGroupName="organisation">
<ui-form-control label="Firmenname">
<input uiInput type="text" formControlName="name" tabindex="19" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="20" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="21" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="true" requiredMark="*">
<ui-select formControlName="gender" tabindex="22">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control label="Titel" [clearable]="true">
<ui-select formControlName="title" tabindex="23">
<ui-select-option value="Dipl.-Ing." label="Dipl.-Ing."></ui-select-option>
<ui-select-option value="Dr." label="Dr."></ui-select-option>
<ui-select-option value="Dr. med." label="Dr. med."></ui-select-option>
<ui-select-option value="Prof." label="Prof."></ui-select-option>
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
<ui-select-option value="RA" label="RA"></ui-select-option>
</ui-select>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="24" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="25" />
</ui-form-control>
</div>
<ng-container formGroupName="address">
<div class="control-row">
<ui-form-control label="Straße" requiredMark="*">
<input uiInput type="text" formControlName="street" tabindex="26" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="27" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="28" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="29" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="30" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="true" requiredMark="*">
<ui-select formControlName="country" tabindex="31">
<ui-select-option
*ngFor="let country of countries$ | async"
[label]="country.name"
[value]="country.isO3166_A_3"
></ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>
</div>
<div class="center sticky-bottom">
<button class="create-customer-submit" type="submit" [disabled]="control.disabled">
<ui-spinner [show]="!control.enabled">
Speichern
</ui-spinner>
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,294 +0,0 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainCheckoutService } from '@domain/checkout';
import { CrmCustomerService } from '@domain/crm';
import { CustomerDTO } from '@swagger/crm';
import { UiValidators } from '@ui/validators';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { map } from 'rxjs/operators';
import { AddressSelectionModalService } from '../modals/address-selection-modal/address-selection-modal.service';
import { validateEmail } from '../validators/email-validator';
import { CustomerCreateComponentBase } from './customer-create.component';
import { Subscription } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'customer-create-online',
templateUrl: 'customer-create-online.component.html',
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase implements OnInit, OnDestroy {
@ViewChild('formControl')
formControlRef: ElementRef;
upgradeCustomerId: number;
upgradeCustomer: CustomerDTO;
shippingAddressSubscription: Subscription;
organisationSubscription: Subscription;
invalidDomainEmails: string[] = [];
emailDomainErrorValidator = (control: AbstractControl): ValidationErrors => {
const controlValue: string = control.value || '';
const emailDomain = this.invalidDomainEmails.find((s) => controlValue.endsWith(s));
if (controlValue && emailDomain) {
return { emailDomain: `Die Email-Domain ${emailDomain} ist ungültig.` };
}
return null;
};
get shippingAddressGroup(): AbstractControl {
return this.control?.get('shippingAddress');
}
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
private fb: FormBuilder,
public modal: UiModalService,
public customerService: CrmCustomerService,
public breadcrumb: BreadcrumbService,
public application: ApplicationService,
public addressSelectionModal: AddressSelectionModalService,
public checkoutService: DomainCheckoutService,
public cdr: ChangeDetectorRef,
private _modal: UiModalService
) {
super();
}
ngOnInit(): void {
this.init();
this.countries$ = this.countries$.pipe(map((countries) => countries.filter((country) => country.name === 'Deutschland')));
this.upgradeCustomerId = Number(this.activatedRoute.snapshot.queryParams.upgradeCustomerId);
if (!!this.upgradeCustomerId || !!this.p4mUser) {
this.cusotmers$ = this.cusotmers$.pipe(
map((data) => ({
...data,
values: data.values.filter((f) => f.value === 'webshop'),
}))
);
}
if (!isNaN(this.upgradeCustomerId)) {
this.customerService
.getCustomer(this.upgradeCustomerId, 2)
.pipe(map((response) => response.result))
.subscribe((customer) => {
this.upgradeCustomer = customer;
this.control.patchValue(customer);
this.control.updateValueAndValidity();
this.control.markAllAsTouched();
});
}
this.shippingAddressGroup.disable();
this.shippingAddressSubscription = this.control.get('differentShippingAddress').valueChanges.subscribe((isChecked) => {
if (isChecked) {
this.shippingAddressGroup.enable();
this.setShippingAddressValidators(this.shippingAddressGroup);
} else {
this.shippingAddressGroup.disable();
this.clearShippingAddressValidators(this.shippingAddressGroup);
this.control.updateValueAndValidity();
}
});
this.organisationSubscription = this.organisationGroup.get('name').valueChanges.subscribe((value: string) => {
if (!value) {
this.organisationGroup.get('department').reset('', { onlySelf: true });
this.organisationGroup.get('vatId').reset('', { onlySelf: true });
}
});
}
ngOnDestroy(): void {
if (this.shippingAddressSubscription) {
this.shippingAddressSubscription.unsubscribe();
}
if (this.organisationSubscription) {
this.organisationSubscription.unsubscribe();
}
}
createControl(customer?: CustomerDTO): FormGroup {
const { fb } = this;
return fb.group({
organisation: fb.group({
name: fb.control(customer?.organisation?.name),
department: fb.control(customer?.organisation?.department),
vatId: fb.control(customer?.organisation?.vatId),
}),
gender: fb.control(customer?.gender || undefined, [Validators.required, Validators.min(1)]),
title: fb.control(customer?.title),
lastName: fb.control(customer?.lastName, [Validators.required]),
firstName: fb.control(customer?.firstName, [Validators.required]),
dateOfBirth: fb.control(customer?.dateOfBirth, [!!this.p4mUser ? Validators.required : () => null, UiValidators.date]),
communicationDetails: fb.group({
email: fb.control(
customer?.communicationDetails?.email,
[Validators.required, validateEmail, this.emailDomainErrorValidator],
[this.emailExistsValidator()]
),
phone: fb.control(customer?.communicationDetails?.phone, [UiValidators.phone]),
mobile: fb.control(customer?.communicationDetails?.mobile, [UiValidators.phone]),
}),
address: fb.group({
street: fb.control(customer?.address?.street, [Validators.required]),
streetNumber: fb.control(customer?.address?.streetNumber, [Validators.required]),
zipCode: fb.control(customer?.address?.zipCode, [Validators.required]),
city: fb.control(customer?.address?.city, [Validators.required]),
country: fb.control(customer?.address?.country || 'DEU', [Validators.required]),
info: fb.control(customer?.address?.info),
}),
differentShippingAddress: fb.control(false),
shippingAddress: fb.group({
organisation: fb.group({
name: fb.control(''),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(undefined),
title: fb.control(''),
firstName: fb.control(''),
lastName: fb.control(''),
address: fb.group({
street: fb.control(''),
streetNumber: fb.control(''),
zipCode: fb.control(''),
city: fb.control(''),
info: fb.control(''),
country: fb.control('DEU'),
}),
}),
});
}
async submit(): Promise<void> {
if (this.control.disabled) {
return;
}
if (this.control.invalid) {
this.control.disable({ emitEvent: false });
setTimeout(() => {
this.enableControl();
this.focusFirstInvalidFormControl(this.formControlRef);
}, 200);
return;
}
this.control.disable({ emitEvent: false });
try {
let address = await this.addressSelectionModal.validateAddress(this.control.value.address);
if (address) {
if (typeof address === 'string') {
if (address === 'close') {
this.enableControl();
return;
}
} else {
this.control.patchValue({ address });
}
}
} catch (error) {
this.enableControl();
if (error instanceof HttpErrorResponse) {
if (error.error.invalidProperties) {
this.setValidateAddressError(error.error?.invalidProperties, this.control.get('address'));
}
}
return;
}
try {
if (this.control.value.differentShippingAddress) {
const shippingAddress = await this.addressSelectionModal.validateAddress(this.control.value.shippingAddress.address);
if (shippingAddress) {
if (typeof shippingAddress === 'string') {
if (shippingAddress === 'close') {
this.enableControl();
return;
}
} else {
this.control.patchValue({ shippingAddress });
}
}
}
} catch (error) {
this.enableControl();
if (error instanceof HttpErrorResponse) {
if (error.error.invalidProperties) {
this.setValidateAddressError(error.error?.invalidProperties, this.control.get('shippingAddress').get('address'));
}
}
return;
}
try {
let newCustomer: CustomerDTO = this.control.value;
if (this.upgradeCustomer) {
newCustomer = { ...this.upgradeCustomer, ...newCustomer };
}
if (this.control.value.differentShippingAddress) {
newCustomer.shippingAddresses = [
{
data: {
...this.control.value.shippingAddress,
isDefault: new Date().toISOString(),
},
},
];
}
if (!!this.p4mUser) {
newCustomer.features = newCustomer.features ? [...newCustomer.features, this.p4mUser] : [this.p4mUser];
}
const response = await this.customerService.createOnlineCustomer(newCustomer).toPromise();
if (response.error) {
this.enableControl();
throw new Error(response.message);
} else {
this.removeBreadcrumb();
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', response.result.id]);
}
} catch (error) {
if (error?.error?.invalidProperties?.Email || error?.error?.invalidProperties?.email) {
this.addInvalidDomain(this.control.value.communicationDetails?.email);
this.enableControl();
} else {
this.setValidationError(error?.error?.invalidProperties, this.control);
}
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim Anlegen eines Onlinekunden' });
}
this.control.markAllAsTouched();
this.control.updateValueAndValidity();
}
addInvalidDomain(email: string) {
const indexOfAt = email.indexOf('@');
this.invalidDomainEmails.push(email.substring(indexOfAt));
}
enableControl() {
this.control.enable();
if (!this.control.get('differentShippingAddress').value) {
this.control.get('differentShippingAddress').disable();
}
this.control.markAllAsTouched();
this.control.updateValueAndValidity();
}
}

View File

@@ -1,84 +0,0 @@
:host {
@apply flex flex-col box-border;
}
.card {
@apply bg-white rounded-card p-card;
}
h1 {
@apply m-0 text-center font-bold;
font-size: 26px;
margin-top: 27px;
}
p {
@apply m-0;
font-size: 22px;
margin-top: 10px;
}
.card > h1,
.card > p {
@apply text-center;
}
page-customer-type-selector {
margin-top: 45px;
}
.router-outlet-wrapper {
max-width: 650px;
@apply mx-auto;
p {
@apply text-regular mt-8;
}
}
.control-row {
@apply flex flex-row gap-8;
ui-form-control {
width: 50%;
}
}
.center {
@apply text-center;
}
.sticky-bottom {
@apply sticky bottom-0;
}
.create-customer-submit {
@apply border-none outline-none bg-brand text-white font-bold text-cta-l px-px-25 py-px-15 rounded-full my-8;
&:disabled {
@apply bg-inactive-branch;
}
}
.bold {
@apply font-semibold;
}
.spin {
@apply animate-spin;
}
.different-shipping-address {
@apply flex-col justify-around text-cta-l mt-8;
input {
@apply text-cta-l;
}
}
.organisation-header {
}
::ng-deep.spin {
@apply text-white;
}

View File

@@ -1,289 +0,0 @@
import { AbstractControl, AsyncValidatorFn, FormArray, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { CrmCustomerService } from '@domain/crm';
import { CountryDTO, CustomerDTO, InputOptionsDTO, KeyValueDTOOfStringAndString } from '@swagger/crm';
import { Observable, of } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged, first, map, switchMap } from 'rxjs/operators';
import { UiModalService } from '@ui/modal';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { AddressSelectionModalService } from '../modals/address-selection-modal/address-selection-modal.service';
import { DomainCheckoutService } from '@domain/checkout';
import { ChangeDetectorRef, ElementRef } from '@angular/core';
import { isBoolean } from '@utils/common';
import { setInvalidPropertyErrors } from '@ui/validators';
import { camelCase } from 'lodash';
import { validateEmail } from '../validators/email-validator';
import { HttpErrorResponse } from '@angular/common/http';
export abstract class CustomerCreateComponentBase {
emailExistsValidator(): AsyncValidatorFn {
return (control) =>
control.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap((value) => this.customerService.emailExists(value)),
map((result) => {
if (result?.result) {
return { exists: result?.message ? result.message : 'E-Mail existiert bereits' };
}
}),
catchError((error) => {
if (error instanceof HttpErrorResponse) {
if (error?.error?.invalidProperties?.email) {
return of({ invalid: error.error.invalidProperties.email });
} else {
return of({ invalid: 'E-Mail ist ungültig' });
}
}
}),
first()
);
}
type: string;
control: FormGroup;
countries$: Observable<CountryDTO[]>;
abstract activatedRoute: ActivatedRoute;
abstract router: Router;
abstract modal: UiModalService;
abstract customerService: CrmCustomerService;
abstract breadcrumb: BreadcrumbService;
abstract application: ApplicationService;
abstract addressSelectionModal: AddressSelectionModalService;
abstract checkoutService: DomainCheckoutService;
abstract cdr: ChangeDetectorRef;
cusotmers$: Observable<InputOptionsDTO>;
private _p4mUser: KeyValueDTOOfStringAndString;
private _p4mUserFeature: 'webshop' | 'store';
get p4mUser() {
return this._p4mUser;
}
set p4mUser(data: KeyValueDTOOfStringAndString) {
this._p4mUser = data;
}
get p4mUserFeature() {
return this._p4mUserFeature;
}
set p4mUserFeature(data: 'webshop' | 'store') {
this._p4mUserFeature = data;
}
get organisationGroup(): AbstractControl {
return this.control?.get('organisation');
}
init() {
this.type = this.activatedRoute.snapshot?.routeConfig?.path;
this.control = this.createControl(this.parseCustomerDataQuery(this.activatedRoute.snapshot.queryParams));
this.countries$ = this.customerService.getCountries().pipe(map((response) => response.result));
this.breadcrumb.addBreadcrumbIfNotExists({
key: this.application.activatedProcessId,
name: 'Kundendaten erfassen',
path: `/kunde/${this.application.activatedProcessId}/customer/create/${this.type}`,
tags: ['customer', 'create'],
section: 'customer',
params: {},
});
this.cusotmers$ = this.checkoutService
.canSetCustomer({
processId: this.application.activatedProcessId,
})
.pipe(
map((r) => {
const result = { ...r.create };
result.options.values = [...result.options.values];
result.options.values.forEach((f) => {
f.selected = f.value === this.type;
f.enabled = isBoolean(f.enabled) ? f.enabled : true;
});
// Upgrade p4mUser if necessary - Only show available option and select it
if (!!this.p4mUser && !!r?.filter?.customertype) {
if (!r.filter.customertype.includes(this.p4mUserFeature)) {
result.options.values = result.options.values.filter(
(option) => option.value === (this.p4mUserFeature === 'store' ? 'webshop' : 'store')
);
result.options.values[0].selected = true;
result.options.values[0].enabled = true;
}
}
return result.options;
})
);
}
async setType(options: InputOptionsDTO) {
let selected = options.values.find((f) => f.selected);
if (!selected) {
selected = options.values.find((f) => f.enabled);
}
const navigated = await this.router.navigate(['../', selected.value], {
relativeTo: this.activatedRoute,
queryParams: this.createCustomerDataQuery(this.control.value),
});
// const navigated = await this.router.navigate(['../', type], {
// relativeTo: this.activatedRoute,
// queryParams: this.createCustomerDataQuery(this.control.value),
// });
// if (!navigated) {
// this.type = this.activatedRoute.snapshot?.routeConfig?.path;
// this.cdr.markForCheck();
// }
}
createCustomerDataQuery(customer: CustomerDTO): { [key: string]: string } {
const query: { [key: string]: string } = {
gender: String(customer.gender),
title: customer.title,
firstName: customer.firstName,
lastName: customer.lastName,
email: customer.communicationDetails.email,
phone: customer.communicationDetails.phone,
mobile: customer.communicationDetails.mobile,
street: customer.address.street,
streetNumber: customer.address.streetNumber,
zipCode: customer.address.zipCode,
city: customer.address.city,
country: customer.address.country,
info: customer.address.info,
name: customer.organisation.name,
department: customer.organisation.department,
vatId: customer.organisation.vatId,
dateOfBirth: customer.dateOfBirth,
};
return query;
}
parseCustomerDataQuery(query: Params): CustomerDTO {
if (Object.keys(query).length === 0) {
return undefined;
}
if (query.card && query.cardFeature) {
this.p4mUser = JSON.parse(decodeURIComponent(query.card));
this.p4mUserFeature = decodeURIComponent(query.cardFeature) as 'webshop' | 'store';
}
return {
gender: (+query.gender || 0) as any,
title: query.title,
firstName: query.firstName,
lastName: query.lastName,
communicationDetails: {
email: query.email,
phone: query.phone,
mobile: query.mobile,
},
address: {
street: query.street,
streetNumber: query.streetNumber,
zipCode: query.zipCode,
city: query.city,
country: query.country,
info: query.info,
},
organisation: {
name: query.name,
department: query.department,
vatId: query.vatId,
},
dateOfBirth: query.dateOfBirth,
};
}
setShippingAddressValidators(group: AbstractControl) {
group.get('gender')?.setValidators([Validators.required]);
group.get('firstName')?.setValidators([Validators.required]);
group.get('lastName')?.setValidators([Validators.required]);
group.get('address')?.get('street')?.setValidators([Validators.required]);
group.get('address')?.get('streetNumber')?.setValidators([Validators.required]);
group.get('address')?.get('zipCode')?.setValidators([Validators.required]);
group.get('address')?.get('city')?.setValidators([Validators.required]);
group.get('address')?.get('country')?.setValidators([Validators.required]);
group.get('communicationDetails')?.get('email')?.setValidators([validateEmail]);
}
clearShippingAddressValidators(group: AbstractControl) {
group.get('gender')?.clearValidators();
group.get('firstName')?.clearValidators();
group.get('lastName')?.clearValidators();
group.get('address')?.clearValidators();
group.get('communicationDetails')?.clearValidators();
}
setValidationError(invalidProperties: { [key: string]: string }, formGroup: AbstractControl) {
this.control.enable();
this.control.reset(this.control.value);
if (invalidProperties) {
setInvalidPropertyErrors({ invalidProperties, formGroup });
}
this.control.markAllAsTouched();
}
setValidateAddressError(invalidProperties: { [key: string]: string }, control: AbstractControl) {
const keys = Object.keys(invalidProperties);
for (const key of keys) {
control?.get(camelCase(key))?.setErrors({ validateAddress: invalidProperties[key] });
}
}
focusFirstInvalidFormControl(formControlRef: ElementRef) {
const invalidKeys = this.findInvalidControlsRecursive(this.control);
const invalidControl = formControlRef.nativeElement.querySelector('[formcontrolname="' + invalidKeys?.find((_) => true) + '"]');
invalidControl?.focus();
}
findInvalidControlsRecursive(formToInvestigate: FormGroup | FormArray): string[] {
const invalidControls: string[] = [];
let recursiveFunc = (form: FormGroup | FormArray) => {
Object.keys(form.controls).forEach((field) => {
if (field === 'shippingAddress') {
return;
}
const control = form.get(field);
if (control instanceof FormGroup) {
recursiveFunc(control);
} else if (control instanceof FormArray) {
recursiveFunc(control);
} else if (control.invalid) {
invalidControls.push(field);
}
});
};
recursiveFunc(formToInvestigate);
return invalidControls;
}
abstract createControl(customer?: CustomerDTO): FormGroup;
abstract submit(): Promise<void>;
async removeBreadcrumb() {
const bc = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.application.activatedProcessId, ['customer', 'create'])
.pipe(first())
.toPromise();
if (bc.length > 0) {
this.breadcrumb.removeBreadcrumb(bc[0].id);
}
}
}

View File

@@ -1,44 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomerTypeSelectorComponent } from './customer-type-selector/customer-type-selector.component';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { UiSelectModule } from '@ui/select';
import { CustomerCreateBranchComponent } from './customer-create-branch.component';
import { CustomerCreateGuestComponent } from './customer-create-guest.component';
import { CustomerCreateOnlineComponent } from './customer-create-online.component';
import { CustomerCreateB2BComponent } from './customer-create-b2b.component';
import { AddressSelectionModalModule } from '../modals/address-selection-modal/address-selection-modal.module';
import { CantAddCustomerToCartModalModule } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.module';
import { UiIconModule } from '@ui/icon';
import { UiCheckboxModule } from '@ui/checkbox';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [
CommonModule,
UiFormControlModule,
UiInputModule,
FormsModule,
ReactiveFormsModule,
RouterModule,
UiSelectModule,
UiIconModule,
AddressSelectionModalModule,
CantAddCustomerToCartModalModule,
UiCheckboxModule,
UiSpinnerModule,
],
exports: [CustomerCreateBranchComponent, CustomerCreateGuestComponent, CustomerCreateOnlineComponent, CustomerCreateB2BComponent],
declarations: [
CustomerCreateBranchComponent,
CustomerTypeSelectorComponent,
CustomerCreateGuestComponent,
CustomerCreateOnlineComponent,
CustomerCreateB2BComponent,
],
})
export class CustomerCreateModule {}

View File

@@ -1,39 +0,0 @@
<!-- <ui-checkbox
id="Gastkunde"
[ngModel]="value"
(ngModelChange)="setValue($event)"
[disabled]="disabledOptions?.guest"
name="customerType"
value="guest"
>
Gastkunde</ui-checkbox
>
<ui-checkbox
[ngModel]="value"
(ngModelChange)="setValue($event)"
[disabled]="disabledOptions?.webshop"
id="Onlinekonto"
value="webshop"
name="customerType"
>
Onlinekonto
</ui-checkbox>
<ui-checkbox
[ngModel]="value"
(ngModelChange)="setValue($event)"
[disabled]="disabledOptions?.b2b"
id="B2B Kunde"
value="b2b"
name="customerType"
>
B2B Kunde
</ui-checkbox> -->
<ui-checkbox
*ngFor="let option of value?.values"
[ngModel]="option.selected"
(ngModelChange)="check($event, option)"
[disabled]="!option.enabled || option.selected"
>
{{ option.label }}
</ui-checkbox>

View File

@@ -1,7 +0,0 @@
:host {
@apply grid grid-flow-col flex-row text-card-sub justify-center gap-6;
}
ui-checkbox.disabled.checked {
@apply text-black;
}

View File

@@ -1,84 +0,0 @@
import {
Component,
ChangeDetectionStrategy,
Input,
Output,
EventEmitter,
ChangeDetectorRef,
forwardRef,
OnInit,
ViewChildren,
QueryList,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { InputDTO, InputOptionsDTO, OptionDTO } from '@swagger/crm';
import { UiCheckboxComponent } from '@ui/checkbox';
@Component({
selector: 'page-customer-type-selector',
templateUrl: 'customer-type-selector.component.html',
styleUrls: ['customer-type-selector.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomerTypeSelectorComponent),
multi: true,
},
],
})
export class CustomerTypeSelectorComponent implements OnInit, ControlValueAccessor {
@ViewChildren(UiCheckboxComponent, { read: UiCheckboxComponent })
checkboxes: QueryList<UiCheckboxComponent>;
@Input()
value: InputOptionsDTO;
@Output()
valueChange = new EventEmitter<InputOptionsDTO>();
@Input()
disabled: boolean;
private onChange = (value: InputOptionsDTO) => {};
private onTouched = () => {};
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit(): void {}
writeValue(obj: any): void {
this.value = obj;
this.cdr.markForCheck();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
setValue(value: InputOptionsDTO) {
this.onChange(value);
this.onTouched();
}
check(checked: boolean = false, option: OptionDTO) {
this.value.values.forEach((o) => {
if (o.value === option.value) {
o.selected = checked;
} else {
o.selected = false;
}
});
this.cdr.markForCheck();
this.onChange(this.value);
this.onTouched();
}
}

View File

@@ -18,7 +18,7 @@
</div>
<p class="info">Sind Ihre {{ (customerType$ | async) === 'b2b' ? 'Firmendaten' : 'Kundendaten' }} korrekt?</p>
<div class="tags">
<div class="tag" *ngFor="let feature of customerFeatures$ | async">
<div class="tag" *ngFor="let feature of displayCustomerType$ | async">
<ui-icon icon="check" size="15px"></ui-icon>
{{ feature.description }}
</div>
@@ -61,8 +61,8 @@
</div>
<ng-container [ngSwitch]="isB2b$ | async">
<page-customer-data-b2b *ngSwitchCase="true" [customer]="customer" [customerType]="customerType$ | async"></page-customer-data-b2b>
<page-customer-data-b2c *ngSwitchCase="false" [customer]="customer" [customerType]="customerType$ | async"></page-customer-data-b2c>
<page-customer-data-b2b *ngSwitchCase="true" [customer]="customer" [customerType]="customerType$ | async"> </page-customer-data-b2b>
<page-customer-data-b2c *ngSwitchCase="false" [customer]="customer" [customerType]="customerType$ | async"> </page-customer-data-b2c>
</ng-container>
<div class="address-wrapper">

View File

@@ -44,6 +44,9 @@ export class CustomerDetailsComponent implements OnInit {
shippingAddresses$: Observable<ShippingAddressDTO[]>;
customerFeatures$: Observable<KeyValueDTOOfStringAndString[]>;
displayCustomerType$: Observable<KeyValueDTOOfStringAndString[]>;
customerType$: Observable<string>;
canAdd$: Observable<boolean>;
@@ -93,7 +96,7 @@ export class CustomerDetailsComponent implements OnInit {
this.customerId$ = this.activatedRoute.params.pipe(map((params) => Number(params['customerId'])));
this.customer$ = this.customerId$.pipe(
switchMap((customerId) => this.customerDetailsService.getCustomer(customerId, 1)),
switchMap((customerId) => this.customerDetailsService.getCustomer(customerId, 2)),
map(({ result }) => result),
map((customer) => {
this.updateBreadcrumbName(customer);
@@ -156,9 +159,12 @@ export class CustomerDetailsComponent implements OnInit {
shareReplay()
);
this.customerFeatures$ = this.customer$.pipe(map((customer: CustomerDTO) => Object.values(customer.features).filter((f) => f.enabled)));
this.customerFeatures$ = this.customer$.pipe(map((customer: CustomerDTO) => Object.values(customer.features)));
this.displayCustomerType$ = this.customerFeatures$.pipe(map((features) => features.filter((f) => f.group === 'd-customertype')));
this.hasCustomerCard$ = this.customerFeatures$.pipe(
map((features) => !!features.find((feature) => feature.description === 'Kundenkarte'))
map((features) => features.some((feature) => feature.key?.toLowerCase() === 'p4muser' || feature.key?.toLowerCase() === 'loyalty'))
);
this.customerType$ = this.customerFeatures$.pipe(
map(
@@ -237,9 +243,10 @@ export class CustomerDetailsComponent implements OnInit {
content: CantAddCustomerToCartModalComponent,
data: {
message: canSetCustomer.message,
customerId: customer.id,
customer: customer,
required: canSetCustomer.create,
upgradeableTo: canBeExtendedTo,
attributes: customer.attributes.map((s) => s.data),
} as CantAddCustomerToCartData,
});
this.showSpinner = false;
@@ -290,9 +297,10 @@ export class CustomerDetailsComponent implements OnInit {
content: CantAddCustomerToCartModalComponent,
data: {
message: canSetCustomer.message,
customerId: customer.id,
customer: customer,
required: canSetCustomer.create,
upgradeableTo: undefined,
attributes: customer.attributes.map((s) => s.data),
} as CantAddCustomerToCartData,
});
@@ -322,17 +330,17 @@ export class CustomerDetailsComponent implements OnInit {
const currentBuyer = await this.checkoutService.getBuyer({ processId: this.application.activatedProcessId }).pipe(first()).toPromise();
this.checkoutService.setCustomer({
processId: this.application.activatedProcessId,
customerDto: customer,
});
// Set Buyer For Process
this.checkoutService.setBuyer({
processId: this.application.activatedProcessId,
buyer: this.getCheckoutBuyerDtoFromCrmCustomerDto(customer),
});
this.checkoutService.setCustomerFeatures({
processId: this.application.activatedProcessId,
customerFeatures: this.getCusomterFeatures(customer),
});
if (currentBuyer?.buyerNumber !== customer.customerNumber) {
this.checkoutService.setNotificationChannels({
processId: this.application.activatedProcessId,

View File

@@ -39,6 +39,10 @@ ui-icon {
@apply bg-white mt-px-10 box-border;
width: 201px;
height: 80px;
img {
@apply mx-auto;
}
}
.barcode {

View File

@@ -16,6 +16,7 @@ import { CustomerInfoDTO } from '@swagger/crm';
import { UiFilterAutocompleteProvider } from '@ui/filter';
import { CustomerSearchMainAutocompleteProvider } from './providers/customer-search-main-autocomplete.provider';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { encodeFormData, mapCustomerInfoDtoToCustomerCreateFormData } from '../create-customer/customer-create-form-data';
@Component({
selector: 'page-customer-search',
@@ -94,6 +95,10 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
handleSearchCompleted(state: SearchState<CustomerInfoDTO>, processId: number) {
const queryParams = state.filter?.getQueryParams();
if (state.latestResponse?.dialog) {
return;
}
if (state.hits === 1) {
const customer = state.items[0];
if (!customer) {
@@ -129,32 +134,17 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
}
navigateToCreateCustomer(processId: number, customer: CustomerInfoDTO) {
const feature = customer?.features?.find((feature) => feature.key === 'webshop') ? 'webshop' : 'store';
const customerCreateQueryParams = {
gender: customer?.gender,
title: customer?.title,
firstName: customer?.firstName,
lastName: customer?.lastName,
email: customer?.communicationDetails?.email,
phone: customer?.communicationDetails?.phone,
mobile: customer?.communicationDetails?.mobile,
street: customer?.address?.street,
streetNumber: customer?.address?.streetNumber,
zipCode: customer?.address?.zipCode,
city: customer?.address?.city,
country: customer?.address?.country,
info: customer?.address?.info,
name: customer?.organisation?.name,
department: customer?.organisation?.department,
vatId: customer?.organisation?.vatId,
dateOfBirth: customer?.dateOfBirth,
card: encodeURIComponent(JSON.stringify(customer?.features[0])),
cardFeature: feature,
};
const formData = encodeFormData(mapCustomerInfoDtoToCustomerCreateFormData(customer));
this._router.navigate(
['/kunde', processId, 'customer', 'create', customer?.features?.find((feature) => feature.key === 'webshop') ? 'webshop' : 'store'],
[
'/kunde',
processId,
'customer',
'create',
customer?.features?.find((feature) => feature.key === 'webshop') ? 'webshop-p4m' : 'store-p4m',
],
{
queryParams: customerCreateQueryParams,
queryParams: { formData },
}
);
}

View File

@@ -31,7 +31,7 @@
<div class="features">
<ng-container>
<ng-container *ngFor="let feature of customer?.features">
<div class="feature" *ngIf="feature.enabled">
<div class="feature" *ngIf="feature.group === 'd-customertype'">
<ui-icon icon="check" size="15px"></ui-icon>
<span>{{ feature.description }}</span>
</div>

View File

@@ -36,6 +36,7 @@ export class CustomerSearchStateSearchService implements SearchStateSearchServic
message: response.message,
hits: response.hits ?? 0,
error: response.error ?? false,
response: response,
};
}
}

View File

@@ -0,0 +1,3 @@
<ui-checkbox [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
AGBs sind unterschrieben{{ requiredMark ? '*' : '' }}
</ui-checkbox>

View File

@@ -0,0 +1,3 @@
:host {
@apply grid justify-center font-bold;
}

View File

@@ -0,0 +1,36 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { FormBlockControl } from '../form-block';
@Component({
selector: 'app-accept-agb-form-block',
templateUrl: 'accept-agb-form-block.component.html',
styleUrls: ['accept-agb-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'appAcceptAGBFormBlock',
})
export class AcceptAGBFormBlockComponent extends FormBlockControl<boolean> {
get tabIndexEnd(): number {
return this.tabIndexStart;
}
constructor() {
super();
}
initializeControl(data?: boolean): void {
this.control = new FormControl(data ?? false, this.getValidatorFn(), this.getAsyncValidatorFn());
}
_patchValue(update: { previous: boolean; current: boolean }): void {
this.control.patchValue(update.current);
}
updateValidators(): void {
this.control.setValidators(this.getValidatorFn());
}
setValue(value: boolean): void {
this.control.setValue(value);
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AcceptAGBFormBlockComponent } from './accept-agb-form-block.component';
import { UiCheckboxModule } from '@ui/checkbox';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiCommonModule, UiCheckboxModule, ReactiveFormsModule],
exports: [AcceptAGBFormBlockComponent],
declarations: [AcceptAGBFormBlockComponent],
})
export class AcceptAGBFormBlockModule {}

View File

@@ -0,0 +1,2 @@
export * from './accept-agb-form-block.component';
export * from './accept-agb-form-block.module';

View File

@@ -0,0 +1,8 @@
export interface AddressFormBlockData {
street?: string;
streetNumber?: string;
zipCode?: string;
city?: string;
info?: string;
country?: string;
}

View File

@@ -0,0 +1,25 @@
<ng-container [formGroup]="control">
<ui-form-control label="Straße" [requiredMark]="requiredMarks.includes('street') ? '*' : ''">
<input uiInput type="text" formControlName="street" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="Hausnummer" [requiredMark]="requiredMarks.includes('streetNumber') ? '*' : ''">
<input uiInput type="text" formControlName="streetNumber" [tabindex]="tabIndexStart + 1" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="PLZ" [requiredMark]="requiredMarks.includes('zipCode') ? '*' : ''">
<input uiInput type="text" formControlName="zipCode" [tabindex]="tabIndexStart + 2" [readonly]="readonly" />
</ui-form-control>
<ui-form-control label="Ort" [requiredMark]="requiredMarks.includes('city') ? '*' : ''">
<input uiInput type="text" formControlName="city" [tabindex]="tabIndexStart + 3" [readonly]="readonly" />
</ui-form-control>
<ui-form-control class="col-span-2" label="Adresszusatz" [clearable]="false" [requiredMark]="requiredMarks.includes('info') ? '*' : ''">
<input uiInput type="text" formControlName="info" [tabindex]="tabIndexStart + 4" [readonly]="readonly" />
</ui-form-control>
<ui-form-control class="col-span-2" label="Land" [clearable]="true" [requiredMark]="requiredMarks.includes('country') ? '*' : ''">
<ui-select formControlName="country" [tabindex]="tabIndexStart + 5" [readonly]="readonly">
<ui-select-option *ngFor="let country of countries || (countries$ | async)" [label]="country.name" [value]="country.isO3166_A_3">
</ui-select-option>
</ui-select>
</ui-form-control>
</ng-container>

View File

@@ -0,0 +1,3 @@
:host {
@apply grid grid-cols-2 gap-x-8;
}

View File

@@ -0,0 +1,78 @@
import { Component, ChangeDetectionStrategy, Input, OnInit, ChangeDetectorRef } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { CrmCustomerService } from '@domain/crm';
import { CountryDTO } from '@swagger/crm';
import { camelCase } from 'lodash';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { FormBlockGroup } from '../form-block';
import { AddressFormBlockData } from './address-form-block-data';
@Component({
selector: 'app-address-form-block',
templateUrl: 'address-form-block.component.html',
styleUrls: ['address-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddressFormBlockComponent extends FormBlockGroup<AddressFormBlockData> implements OnInit {
@Input() countries: CountryDTO[];
@Input() defaults: Partial<AddressFormBlockData>;
get tabIndexEnd(): number {
return this.tabIndexStart + 5;
}
countries$: Observable<CountryDTO[]>;
constructor(private readonly _customerService: CrmCustomerService, private _cdr: ChangeDetectorRef) {
super();
}
ngOnInit(): void {
super.ngOnInit();
this.countries$ = this._customerService.getCountries().pipe(map((r) => r.result));
}
initializeControl(data?: AddressFormBlockData): void {
this.control = new FormGroup({
country: new FormControl(data?.country ?? this.defaults?.country ?? '', this.getValidatorFn('country')),
city: new FormControl(data?.city ?? this.defaults?.city ?? '', this.getValidatorFn('city')),
street: new FormControl(data?.street ?? this.defaults?.street ?? '', this.getValidatorFn('street')),
streetNumber: new FormControl(data?.streetNumber ?? this.defaults?.streetNumber ?? '', this.getValidatorFn('streetNumber')),
zipCode: new FormControl(data?.zipCode ?? this.defaults?.zipCode ?? '', this.getValidatorFn('zipCode')),
info: new FormControl(data?.info ?? this.defaults?.info ?? '', this.getValidatorFn('info')),
});
}
_patchValue(update: { previous: AddressFormBlockData; current: AddressFormBlockData }): void {
this.control.patchValue({
country: update.current.country ?? '',
city: update.current.city ?? '',
street: update.current.street ?? '',
streetNumber: update.current.streetNumber ?? '',
zipCode: update.current.zipCode ?? '',
info: update.current.info ?? '',
});
}
setAddressValidationError(invalidProperties: Record<keyof AddressFormBlockData, string>) {
const keys = Object.keys(invalidProperties);
for (const key of keys) {
this.control.get(camelCase(key))?.setErrors({ validateAddress: invalidProperties[key] });
}
this.control.markAllAsTouched();
this._cdr.markForCheck();
}
updateValidators(): void {
this.control.get('country')?.setValidators(this.getValidatorFn('country'));
this.control.get('city')?.setValidators(this.getValidatorFn('city'));
this.control.get('street')?.setValidators(this.getValidatorFn('street'));
this.control.get('streetNumber')?.setValidators(this.getValidatorFn('streetNumber'));
this.control.get('zipCode')?.setValidators(this.getValidatorFn('zipCode'));
this.control.get('info')?.setValidators(this.getValidatorFn('info'));
this.control.updateValueAndValidity();
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AddressFormBlockComponent } from './address-form-block.component';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { UiSelectModule } from '@ui/select';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, UiCommonModule, UiFormControlModule, UiInputModule, UiSelectModule, ReactiveFormsModule],
exports: [AddressFormBlockComponent],
declarations: [AddressFormBlockComponent],
})
export class AddressFormBlockModule {}

View File

@@ -0,0 +1,3 @@
export * from './address-form-block-data';
export * from './address-form-block.component';
export * from './address-form-block.module';

View File

@@ -0,0 +1,3 @@
<ui-form-control label="Geburtsdatum (TT.MM.JJJJ)" [requiredMark]="requiredMark ? '*' : ''">
<input uiDateInput type="text" [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
</ui-form-control>

View File

@@ -0,0 +1,3 @@
:host {
@apply block;
}

View File

@@ -0,0 +1,29 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { UiValidators } from '@ui/validators';
import { FormBlockControl } from '../form-block';
@Component({
selector: 'app-birth-date-form-block',
templateUrl: 'birth-date-form-block.component.html',
styleUrls: ['birth-date-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BirthDateFormBlockComponent extends FormBlockControl<Date> {
get tabIndexEnd() {
return this.tabIndexStart;
}
initializeControl(data?: Date): void {
this.control = new FormControl(data, [...this.getValidatorFn(), UiValidators.date]);
}
_patchValue(update: { previous: Date; current: Date }): void {
this.control.patchValue(update.current);
}
updateValidators(): void {
this.control.setValidators([...this.getValidatorFn(), UiValidators.date]);
this.control.updateValueAndValidity();
}
}

View File

@@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BirthDateFormBlockComponent } from './birth-date-form-block.component';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [CommonModule, ReactiveFormsModule, UiFormControlModule, UiInputModule, UiCommonModule],
exports: [BirthDateFormBlockComponent],
declarations: [BirthDateFormBlockComponent],
})
export class BirthDateFormBlockModule {}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './birth-date-form-block.component';
export * from './birth-date-form-block.module';
// end:ng42.barrel

View File

@@ -0,0 +1,12 @@
import { AddressFormBlockData } from '../address';
import { NameFormBlockData } from '../name/name-form-block-data';
import { OrganisationFormBlockData } from '../organisation/organisation-form-block-data';
export interface DeviatingAddressFormBlockData {
deviatingAddress?: boolean;
organisation?: OrganisationFormBlockData;
name?: NameFormBlockData;
address?: AddressFormBlockData;
email?: string;
phoneNumbers?: { mobile?: string; phone?: string };
}

View File

@@ -0,0 +1,68 @@
<ui-checkbox [formControl]="control.get('deviatingAddress')" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly">
<ng-content></ng-content>
</ui-checkbox>
<div class="address-block" *ngIf="control.value.deviatingAddress">
<div class="wrapper">
<app-organisation-form-block
*ngIf="organisation"
[tabIndexStart]="tabIndexStart + 1"
#orgaBlock
(onInit)="addOrganisationGroup($event)"
(onDestroy)="removeOrganisationGroup()"
[data]="data?.organisation"
#nameFormBlock
[tabIndexStart]="tabIndexStart + 1"
[requiredMarks]="organisationRequiredMarks"
[validatorFns]="organisationValidatorFns"
[readonly]="readonly"
>
</app-organisation-form-block>
<app-name-form-block
(onInit)="addNameGroup($event)"
(onDestroy)="removeNameGroup()"
[data]="data?.name"
#nameFormBlock
[tabIndexStart]="orgaBlock ? orgaBlock?.tabIndexEnd + 1 : tabIndexStart + 1"
[requiredMarks]="nameRequiredMarks"
[validatorFns]="nameValidatorFns"
[readonly]="readonly"
>
</app-name-form-block>
<app-address-form-block
#addressFormBlock
[defaults]="defaults?.address"
(onInit)="addAddressGroup($event)"
(onDestroy)="removeAddressGroup()"
[data]="data?.address"
[tabIndexStart]="nameFormBlock?.tabIndexEnd + 1"
[requiredMarks]="addressRequiredMarks"
[validatorFns]="addressValidatorFns"
[readonly]="readonly"
>
</app-address-form-block>
<app-email-form-block
*ngIf="email"
#emailFormBlock
[tabIndexStart]="addressFormBlock + 1"
[defaults]="defaults?.email"
(onInit)="addEmailGroup($event)"
(onDestroy)="removeEmailGroup()"
[data]="data?.email"
[tabIndexStart]="nameFormBlock?.tabIndexEnd + 1"
[requiredMark]="emailRequiredMark"
[validatorFns]="emailValidationFns"
[readonly]="readonly"
>
</app-email-form-block>
<app-phone-numbers-form-block
*ngIf="phoneNumbers"
[tabIndexStart]="emailFormBlock ? emailFormBlock.tabIndexEnd + 1 : addressFormBlock.tabIndexEnd + 1"
[defaults]="defaults?.phoneNumbers"
(onInit)="addPhoneNumbersGroup($event)"
(onDestroy)="removePhoneNumbersGroup()"
[readonly]="readonly"
>
[tabIndexStart]="emailFormBlock?.tabIndexEnd+1" [requiredMarks]="phoneNumbersRequiredMarks" [validatorFns]="phoneNumbersValidatorFns">
</app-phone-numbers-form-block>
</div>
</div>

View File

@@ -0,0 +1,7 @@
:host {
@apply block;
}
ui-checkbox {
@apply font-semibold;
}

View File

@@ -0,0 +1,175 @@
import { Component, ChangeDetectionStrategy, ViewChild, ElementRef, Self, AfterViewInit, ChangeDetectorRef, Input } from '@angular/core';
import { FormBuilder, ValidatorFn } from '@angular/forms';
import { FormBlockGroup } from '../form-block';
import { DeviatingAddressFormBlockData } from './deviating-address-form-block-data';
import { NameFormBlockComponent } from '../name';
import { AddressFormBlockComponent, AddressFormBlockData } from '../address';
import { NameFormBlockData } from '../name/name-form-block-data';
import { OrganisationFormBlockComponent } from '../organisation';
import { OrganisationFormBlockData } from '../organisation/organisation-form-block-data';
import { EmailFormBlockComponent } from '../email';
import { PhoneNumbersFormBlockComponent, PhoneNumbersFormBlockData } from '../phone-numbers';
@Component({
selector: 'app-deviating-address-form-block',
templateUrl: 'deviating-address-form-block.component.html',
styleUrls: ['deviating-address-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DeviatingAddressFormBlockComponent extends FormBlockGroup<DeviatingAddressFormBlockData> implements AfterViewInit {
@Input()
organisation = false;
@Input()
email = false;
@Input()
phoneNumbers = false;
@ViewChild(AddressFormBlockComponent, { static: false })
private readonly _addressFormBlock: AddressFormBlockComponent;
@ViewChild(EmailFormBlockComponent, { static: false })
private readonly _emailFormBlock: AddressFormBlockComponent;
@ViewChild(PhoneNumbersFormBlockComponent, { static: false })
private readonly _phoneNumbersFormBlock: AddressFormBlockComponent;
get tabIndexEnd() {
let tabIndex = this.tabIndexStart;
if (this.control.value.deviatingDeliveryAddress) {
return (
this._phoneNumbersFormBlock?.tabIndexEnd ?? this._emailFormBlock?.tabIndexEnd ?? this._addressFormBlock?.tabIndexEnd ?? tabIndex
);
}
return tabIndex;
}
@Input() defaults: Partial<DeviatingAddressFormBlockData>;
@Input()
nameRequiredMarks: Array<keyof NameFormBlockData>;
@Input()
nameValidatorFns: Record<keyof NameFormBlockData, ValidatorFn[]>;
@Input()
addressRequiredMarks: Array<keyof AddressFormBlockData>;
@Input()
addressValidatorFns: Record<keyof AddressFormBlockData, ValidatorFn[]>;
@Input()
organisationRequiredMarks: Array<keyof OrganisationFormBlockData>;
@Input()
organisationValidatorFns: Record<keyof OrganisationFormBlockData, ValidatorFn[]>;
@Input()
emailRequiredMark = false;
@Input()
emailValidationFns: ValidatorFn[] = [];
@Input()
phoneNumbersRequiredMarks: Array<keyof PhoneNumbersFormBlockData>;
@Input()
phoneNumbersValidatorFns: Record<keyof PhoneNumbersFormBlockData, ValidatorFn[]>;
constructor(private readonly _fb: FormBuilder, @Self() private _elementRef: ElementRef, private _cdr: ChangeDetectorRef) {
super();
}
ngAfterViewInit(): void {}
initializeControl(data?: DeviatingAddressFormBlockData): void {
this.control = this._fb.group({
deviatingAddress: this._fb.control(data?.deviatingAddress ?? false, this.getValidatorFn('deviatingAddress')),
});
}
_patchValue(update: { previous: DeviatingAddressFormBlockData; current: DeviatingAddressFormBlockData }): void {
this.control.patchValue({
deviatingAddress: update.current?.deviatingAddress ?? false,
});
}
addOrganisationGroup(cmp: OrganisationFormBlockComponent) {
if (!this.control.contains('organisation')) {
this.control.addControl('organisation', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removeOrganisationGroup() {
if (this.control.contains('organisation')) {
this.control.removeControl('organisation');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
addNameGroup(cmp: NameFormBlockComponent) {
if (!this.control.contains('name')) {
this.control.addControl('name', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removeNameGroup() {
if (this.control.contains('name')) {
this.control.removeControl('name');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
addAddressGroup(cmp: AddressFormBlockComponent) {
if (!this.control.contains('address')) {
this.control.addControl('address', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removeAddressGroup() {
if (this.control.contains('address')) {
this.control.removeControl('address');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
addEmailGroup(cmp: AddressFormBlockComponent) {
if (!this.control.contains('email')) {
this.control.addControl('email', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removeEmailGroup() {
if (this.control.contains('email')) {
this.control.removeControl('email');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
addPhoneNumbersGroup(cmp: AddressFormBlockComponent) {
if (!this.control.contains('phoneNumbers')) {
this.control.addControl('phoneNumbers', cmp.control);
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
removePhoneNumbersGroup() {
if (this.control.contains('phoneNumbers')) {
this.control.removeControl('phoneNumbers');
}
setTimeout(() => this._cdr.markForCheck(), 0);
}
setAddressValidationError(invalidProperties: Record<keyof AddressFormBlockData, string>) {
this._addressFormBlock?.setAddressValidationError(invalidProperties);
}
updateValidators(): void {
throw new Error('Method not implemented.');
}
}

View File

@@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DeviatingAddressFormBlockComponent } from './deviating-address-form-block.component';
import { UiCheckboxModule } from '@ui/checkbox';
import { AddressFormBlockModule } from '../address';
import { NameFormBlockModule } from '../name';
import { ReactiveFormsModule } from '@angular/forms';
import { OrganisationFormBlockModule } from '../organisation';
import { EmailFormBlockModule } from '../email';
import { PhoneNumbersFormBlockModule } from '../phone-numbers';
import { UiCommonModule } from '@ui/common';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
UiCheckboxModule,
NameFormBlockModule,
AddressFormBlockModule,
OrganisationFormBlockModule,
EmailFormBlockModule,
PhoneNumbersFormBlockModule,
UiCommonModule,
],
exports: [DeviatingAddressFormBlockComponent],
declarations: [DeviatingAddressFormBlockComponent],
})
export class DeviatingAddressFormBlockComponentModule {}

View File

@@ -0,0 +1,3 @@
export * from './deviating-address-form-block-data';
export * from './deviating-address-form-block.component';
export * from './deviating-address-form-block.module';

View File

@@ -0,0 +1,3 @@
<ui-form-control label="E-Mail" [requiredMark]="requiredMark ? '*' : ''">
<input uiInput type="mail" [formControl]="control" [tabindex]="tabIndexStart" [autofocus]="focusAfterInit" [readonly]="readonly" />
</ui-form-control>

View File

@@ -0,0 +1,35 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { UiValidators } from '@ui/validators';
import { validateEmail } from '../../validators/email-validator';
import { FormBlockControl } from '../form-block';
@Component({
selector: 'app-email-form-block',
templateUrl: 'email-form-block.component.html',
styleUrls: ['email-form-block.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EmailFormBlockComponent extends FormBlockControl<string> {
get tabIndexEnd() {
return this.tabIndexStart;
}
constructor() {
super();
}
updateValidators(): void {
this.control.setValidators([...this.getValidatorFn(), validateEmail]);
this.control.setAsyncValidators(this.getAsyncValidatorFn());
this.control.updateValueAndValidity();
}
initializeControl(data?: string): void {
this.control = new FormControl(data, [...this.getValidatorFn(), validateEmail], this.getAsyncValidatorFn());
}
_patchValue(update: { previous: string; current: string }): void {
this.control.patchValue(update.current);
}
}

Some files were not shown because too many files have changed in this diff Show More