Merge branch 'feature/customer_pages'

Conflicts:
	apps/page/customer/src/lib/customer-details/customer-details.component.html
	apps/page/customer/src/lib/customer-details/customer-details.module.ts
	apps/page/customer/src/lib/page-customer-routing.module.ts
This commit is contained in:
Andreas Schickinger
2021-01-28 11:56:07 +01:00
55 changed files with 1439 additions and 295 deletions

View File

@@ -17,11 +17,11 @@ export class CartService {
throw new Error('Not Implemented');
}
getRequiredCustomerTypes(processId: number): Observable<('branch' | 'guest' | 'online' | 'b2b')[]> {
throw new Error('Not Implemented');
}
// getRequiredCustomerTypes(processId: number): Observable<('branch' | 'guest' | 'online' | 'b2b')[]> {
// throw new Error('Not Implemented');
// }
canAddItem(processId: number, availability: OLAAvailabilityDTO): Promise<boolean> {
canAddItem(processId: number, availability: OLAAvailabilityDTO): Promise<true | string> {
throw new Error('Not Implemented');
}
}

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { PayerDTO, ShippingAddressDTO } from '@swagger/checkout';
import { StringDictionary } from '@cmf/core';
import { BuyerResult, PayerDTO, ShippingAddressDTO } from '@swagger/checkout';
import { CustomerDTO, PayerDTO as CrmPayerDTO, ShippingAddressDTO as CrmShippingAddressDTO } from '@swagger/crm';
import { Observable } from 'rxjs';
@@ -9,7 +10,7 @@ import { Observable } from 'rxjs';
export class CheckoutService {
constructor() {}
setCustomer(processId: number, customer: CustomerDTO): Promise<boolean> {
setCustomer(processId: number, customer: CustomerDTO): Observable<boolean> {
throw new Error('Not Implemented');
}
@@ -32,4 +33,15 @@ export class CheckoutService {
getSelectedShippingAddress(processId: number): Observable<ShippingAddressDTO> {
throw new Error('Not Implemented');
}
canSetCustomer(
processId: number,
customer: CustomerDTO
): Observable<{ ok?: boolean; filter?: StringDictionary<string>; message?: string }> {
throw new Error('Not Implemented');
}
getSetableCustomerTypes(processId: number): Observable<string[]> {
throw new Error('Not Implemented');
}
}

View File

@@ -11,6 +11,7 @@ import {
InputDTO,
PayerDTO,
PayerService,
ResponseArgsOfIEnumerableOfBonusCardInfoDTO,
ShippingAddressDTO,
} from '@swagger/crm';
import { PagedResult, Result } from 'apps/domain/defs/src/public-api';
@@ -173,4 +174,8 @@ export class CrmCustomerService {
getPayer(payerId: number): Observable<Result<PayerDTO>> {
return this.payerService.PayerGetPayer(payerId);
}
getCustomerCard(customerId: number): Observable<ResponseArgsOfIEnumerableOfBonusCardInfoDTO> {
return this.customerService.CustomerGetBonuscards(customerId);
}
}

View File

@@ -5,7 +5,11 @@
einen Firmenaccount. Wir legen diesen<br />
gerne direkt für Sie an.
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<page-customer-type-selector
[(ngModel)]="type"
(ngModelChange)="setType($event)"
[disabledOptions]="disabledSelectOptions$ | async"
></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
<ng-container formGroupName="organisation">
@@ -97,6 +101,100 @@
</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">
<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]="false" requiredMark="*">
<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 label="Titel">
<ui-select formControlName="title" tabindex="22">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="23" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<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]="false" 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.invalid || control.disabled" [ngSwitch]="control.enabled">
<ng-container *ngSwitchCase="true">

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
@@ -6,9 +6,11 @@ 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.service';
import { AddressSelectionModalService } from '../modals/address-selection-modal/address-selection-modal.service';
import { CustomerCreateComponentBase } from './customer-create.component';
import { validateEmail } from '../validators/email-validator';
import { CheckoutService } from '@domain/checkout';
import { Subscription } from 'rxjs';
@Component({
selector: 'page-customer-create-b2b',
@@ -16,7 +18,9 @@ import { validateEmail } from '../validators/email-validator';
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateB2BComponent extends CustomerCreateComponentBase implements OnInit {
export class CustomerCreateB2BComponent extends CustomerCreateComponentBase implements OnInit, OnDestroy {
subscription: Subscription;
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
@@ -25,13 +29,33 @@ export class CustomerCreateB2BComponent extends CustomerCreateComponentBase impl
public customerService: CrmCustomerService,
public breadcrumb: BreadcrumbService,
public application: ApplicationService,
public addressSelectionModal: AddressSelectionModalService
public addressSelectionModal: AddressSelectionModalService,
public checkoutService: CheckoutService,
public cdr: ChangeDetectorRef
) {
super();
}
ngOnInit(): void {
this.init();
this.control.get('shippingAddress').disable();
this.subscription = this.control.get('differentShippingAddress').valueChanges.subscribe((isChecked) => {
if (isChecked) {
this.control.get('shippingAddress').enable();
this.control.updateValueAndValidity();
} else {
this.control.get('shippingAddress').disable();
this.control.updateValueAndValidity();
}
});
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
createControl(customer?: CustomerDTO): FormGroup {
@@ -60,6 +84,31 @@ export class CustomerCreateB2BComponent extends CustomerCreateComponentBase impl
phone: fb.control(customer?.communicationDetails?.phone),
mobile: fb.control(customer?.communicationDetails?.mobile),
}),
differentShippingAddress: fb.control(false),
shippingAddress: fb.group({
organisation: fb.group({
name: fb.control(''),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(undefined, [Validators.required]),
title: fb.control(''),
firstName: fb.control('', [Validators.required]),
lastName: fb.control('', [Validators.required]),
address: fb.group({
street: fb.control('', [Validators.required]),
streetNumber: fb.control('', [Validators.required]),
zipCode: fb.control('', [Validators.required]),
city: fb.control('', [Validators.required]),
info: fb.control(''),
country: fb.control('DEU', [Validators.required]),
}),
communicationDetails: fb.group({
email: fb.control('', [validateEmail]),
phone: fb.control(''),
mobile: fb.control(''),
}),
}),
});
}
@@ -68,16 +117,38 @@ export class CustomerCreateB2BComponent extends CustomerCreateComponentBase impl
return;
}
this.control.disable();
this.control.disable({ emitEvent: false });
let address = await this.addressSelectionModal.validateAddress(this.control.value.address);
const address = await this.addressSelectionModal.validateAddress(this.control.value.address);
if (address) {
this.control.patchValue({ address });
}
if (this.control.value.differentShippingAddress) {
const shippingAddress = await this.addressSelectionModal.validateAddress(this.control.value.shippingAddress.address);
if (shippingAddress) {
this.control.patchValue({ shippingAddress });
}
}
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) {
this.control.enable();
console.error(error);
return;
}
}
if (response.error) {
throw new Error(response.message);
} else {

View File

@@ -3,7 +3,11 @@
<p>
TEXT FÜR FILIALKUNDE(MINIMALKUNDE)
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<page-customer-type-selector
[(ngModel)]="type"
(ngModelChange)="setType($event)"
[disabledOptions]="disabledSelectOptions$ | async"
></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
@@ -93,6 +97,85 @@
<input uiDateInput type="text" formControlName="dateOfBirth" tabindex="15" />
</ui-form-control>
<ui-form-control class="different-shipping-address" label="Die Lieferadresse weicht von der Rechnungsadresse ab">
<input uiInput type="checkbox" formControlName="differentShippingAddress" tabindex="16" />
</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="17" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="18" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="19" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="false" requiredMark="*">
<ui-select formControlName="gender" tabindex="20">
<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">
<ui-select formControlName="title" tabindex="21">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="22" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="23" />
</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="24" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="25" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="26" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="27" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="28" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="false" requiredMark="*">
<ui-select formControlName="country" tabindex="29">
<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.invalid || control.disabled" [ngSwitch]="control.enabled">
<ng-container *ngSwitchCase="true">

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
@@ -7,9 +7,11 @@ import { CrmCustomerService } from '@domain/crm';
import { CustomerDTO } from '@swagger/crm';
import { UiValidators } from '@ui/common';
import { UiModalService } from '@ui/modal';
import { AddressSelectionModalService } from '../modals/address-selection-modal.service';
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 { CheckoutService } from '@domain/checkout';
@Component({
selector: 'page-customer-create-branch',
@@ -17,7 +19,9 @@ import { validateEmail } from '../validators/email-validator';
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateBranchComponent extends CustomerCreateComponentBase implements OnInit {
export class CustomerCreateBranchComponent extends CustomerCreateComponentBase implements OnInit, OnDestroy {
subscription: Subscription;
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
@@ -26,13 +30,33 @@ export class CustomerCreateBranchComponent extends CustomerCreateComponentBase i
public customerService: CrmCustomerService,
public breadcrumb: BreadcrumbService,
public application: ApplicationService,
public addressSelectionModal: AddressSelectionModalService
public addressSelectionModal: AddressSelectionModalService,
public checkoutService: CheckoutService,
public cdr: ChangeDetectorRef
) {
super();
}
ngOnInit() {
this.init();
this.control.get('shippingAddress').disable();
this.subscription = this.control.get('differentShippingAddress').valueChanges.subscribe((isChecked) => {
if (isChecked) {
this.control.get('shippingAddress').enable();
this.control.updateValueAndValidity();
} else {
this.control.get('shippingAddress').disable();
this.control.updateValueAndValidity();
}
});
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
createControl(customer?: CustomerDTO): FormGroup {
@@ -60,6 +84,26 @@ export class CustomerCreateBranchComponent extends CustomerCreateComponentBase i
phone: fb.control(customer?.communicationDetails?.phone),
mobile: fb.control(customer?.communicationDetails?.mobile),
}),
differentShippingAddress: fb.control(false),
shippingAddress: fb.group({
organisation: fb.group({
name: fb.control(''),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(undefined, [Validators.required]),
title: fb.control(''),
firstName: fb.control('', [Validators.required]),
lastName: fb.control('', [Validators.required]),
address: fb.group({
street: fb.control('', [Validators.required]),
streetNumber: fb.control('', [Validators.required]),
zipCode: fb.control('', [Validators.required]),
city: fb.control('', [Validators.required]),
info: fb.control(''),
country: fb.control('DEU', [Validators.required]),
}),
}),
});
}
@@ -68,16 +112,38 @@ export class CustomerCreateBranchComponent extends CustomerCreateComponentBase i
return;
}
this.control.disable();
this.control.disable({ emitEvent: false });
let address = await this.addressSelectionModal.validateAddress(this.control.value.address);
if (address) {
this.control.patchValue({ address });
}
if (this.control.value.differentShippingAddress) {
const shippingAddress = await this.addressSelectionModal.validateAddress(this.control.value.shippingAddress.address);
if (shippingAddress) {
this.control.patchValue({ shippingAddress });
}
}
try {
const response = await this.customerService.createBranchCustomer(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) {
this.control.enable();
console.error(error);
return;
}
}
if (response.error) {
throw new Error(response.message);
} else {

View File

@@ -5,7 +5,11 @@
gerne ein Onlinekonto an. Dort können <br />
Sie Ihre Bestellungen einsehen.
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<page-customer-type-selector
[(ngModel)]="type"
(ngModelChange)="setType($event)"
[disabledOptions]="disabledSelectOptions$ | async"
></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
<div class="control-row">
@@ -100,6 +104,85 @@
<input type="text" formControlName="dateOfBirth" uiDateInput tabindex="15" />
</ui-form-control>
<ui-form-control class="different-shipping-address" label="Die Lieferadresse weicht von der Rechnungsadresse ab">
<input uiInput type="checkbox" formControlName="differentShippingAddress" tabindex="16" />
</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="17" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="18" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="19" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="false" requiredMark="*">
<ui-select formControlName="gender" tabindex="20">
<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">
<ui-select formControlName="title" tabindex="21">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="22" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="23" />
</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="24" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="25" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="26" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="27" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="28" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="false" requiredMark="*">
<ui-select formControlName="country" tabindex="29">
<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.invalid || control.disabled" [ngSwitch]="control.enabled">
<ng-container *ngSwitchCase="true">

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
@@ -7,9 +7,11 @@ import { CrmCustomerService } from '@domain/crm';
import { CustomerDTO } from '@swagger/crm';
import { UiValidators } from '@ui/common';
import { UiModalService } from '@ui/modal';
import { AddressSelectionModalService } from '../modals/address-selection-modal.service';
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 { CheckoutService } from '@domain/checkout';
@Component({
selector: 'page-customer-create-guest',
@@ -17,7 +19,9 @@ import { validateEmail } from '../validators/email-validator';
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateGuestComponent extends CustomerCreateComponentBase implements OnInit {
export class CustomerCreateGuestComponent extends CustomerCreateComponentBase implements OnInit, OnDestroy {
subscription: Subscription;
constructor(
public activatedRoute: ActivatedRoute,
public router: Router,
@@ -26,13 +30,33 @@ export class CustomerCreateGuestComponent extends CustomerCreateComponentBase im
public customerService: CrmCustomerService,
public breadcrumb: BreadcrumbService,
public application: ApplicationService,
public addressSelectionModal: AddressSelectionModalService
public addressSelectionModal: AddressSelectionModalService,
public checkoutService: CheckoutService,
public cdr: ChangeDetectorRef
) {
super();
}
ngOnInit(): void {
this.init();
this.control.get('shippingAddress').disable();
this.subscription = this.control.get('differentShippingAddress').valueChanges.subscribe((isChecked) => {
if (isChecked) {
this.control.get('shippingAddress').enable();
this.control.updateValueAndValidity();
} else {
this.control.get('shippingAddress').disable();
this.control.updateValueAndValidity();
}
});
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
createControl(customer?: CustomerDTO): FormGroup {
@@ -60,6 +84,26 @@ export class CustomerCreateGuestComponent extends CustomerCreateComponentBase im
phone: fb.control(customer?.communicationDetails?.phone),
mobile: fb.control(customer?.communicationDetails?.mobile),
}),
differentShippingAddress: fb.control(false),
shippingAddress: fb.group({
organisation: fb.group({
name: fb.control(''),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(undefined, [Validators.required]),
title: fb.control(''),
firstName: fb.control('', [Validators.required]),
lastName: fb.control('', [Validators.required]),
address: fb.group({
street: fb.control('', [Validators.required]),
streetNumber: fb.control('', [Validators.required]),
zipCode: fb.control('', [Validators.required]),
city: fb.control('', [Validators.required]),
info: fb.control(''),
country: fb.control('DEU', [Validators.required]),
}),
}),
});
}
@@ -68,16 +112,38 @@ export class CustomerCreateGuestComponent extends CustomerCreateComponentBase im
return;
}
this.control.disable();
this.control.disable({ emitEvent: false });
let address = await this.addressSelectionModal.validateAddress(this.control.value.address);
if (address) {
this.control.patchValue({ address });
}
if (this.control.value.differentShippingAddress) {
const shippingAddress = await this.addressSelectionModal.validateAddress(this.control.value.shippingAddress.address);
if (shippingAddress) {
this.control.patchValue({ shippingAddress });
}
}
try {
const response = await this.customerService.createGuestCustomer(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) {
this.control.enable();
console.error(error);
return;
}
}
if (response.error) {
throw new Error(response.message);
} else {

View File

@@ -5,7 +5,11 @@
gerne ein Onlinekonto an. Dort können <br />
Sie Ihre Bestellungen einsehen.
</p>
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
<page-customer-type-selector
[(ngModel)]="type"
(ngModelChange)="setType($event)"
[disabledOptions]="disabledSelectOptions$ | async"
></page-customer-type-selector>
<div class="router-outlet-wrapper">
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
@@ -97,6 +101,85 @@
<input uiDateInput type="text" formControlName="dateOfBirth" tabindex="15" />
</ui-form-control>
<ui-form-control class="different-shipping-address" label="Die Lieferadresse weicht von der Rechnungsadresse ab">
<input uiInput type="checkbox" formControlName="differentShippingAddress" tabindex="16" />
</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="17" />
</ui-form-control>
<div class="control-row">
<ui-form-control label="Abteilung">
<input uiInput type="text" formControlName="department" tabindex="18" />
</ui-form-control>
<ui-form-control label="USt-ID">
<input uiInput type="text" formControlName="vatId" tabindex="19" />
</ui-form-control>
</div>
</ng-container>
<div class="control-row">
<ui-form-control label="Anrede" [clearable]="false" requiredMark="*">
<ui-select formControlName="gender" tabindex="20">
<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">
<ui-select formControlName="title" tabindex="21">
<ui-select-option value="Dr." label="Dr."></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>
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="Nachname" requiredMark="*">
<input uiInput type="text" formControlName="lastName" tabindex="22" />
</ui-form-control>
<ui-form-control label="Vorname" requiredMark="*">
<input uiInput type="text" formControlName="firstName" tabindex="23" />
</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="24" />
</ui-form-control>
<ui-form-control label="Hausnummer" requiredMark="*">
<input uiInput type="text" formControlName="streetNumber" tabindex="25" />
</ui-form-control>
</div>
<div class="control-row">
<ui-form-control label="PLZ" requiredMark="*">
<input uiInput type="text" formControlName="zipCode" tabindex="26" />
</ui-form-control>
<ui-form-control label="Ort" requiredMark="*">
<input uiInput type="text" formControlName="city" tabindex="27" />
</ui-form-control>
</div>
<ui-form-control label="Adresszusatz" [clearable]="false">
<input uiInput type="text" formControlName="info" tabindex="28" />
</ui-form-control>
<ui-form-control label="Land" [clearable]="false" requiredMark="*">
<ui-select formControlName="country" tabindex="29">
<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.invalid || control.disabled" [ngSwitch]="control.enabled">
<ng-container *ngSwitchCase="true">

View File

@@ -1,17 +1,18 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { CheckoutService } from '@domain/checkout';
import { CrmCustomerService } from '@domain/crm';
import { CustomerDTO } from '@swagger/crm';
import { UiValidators } from '@ui/common';
import { UiModalService } from '@ui/modal';
import { Observable } from 'rxjs';
import { filter, map, shareReplay, switchMap, take } from 'rxjs/operators';
import { AddressSelectionModalService } from '../modals/address-selection-modal.service';
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';
@Component({
selector: 'customer-create-online',
@@ -19,9 +20,10 @@ import { CustomerCreateComponentBase } from './customer-create.component';
styleUrls: ['customer-create.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase implements OnInit {
export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase implements OnInit, OnDestroy {
upgradeCustomerId: number;
upgradeCustomer: CustomerDTO;
subscription: Subscription;
constructor(
public activatedRoute: ActivatedRoute,
@@ -31,7 +33,9 @@ export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase i
public customerService: CrmCustomerService,
public breadcrumb: BreadcrumbService,
public application: ApplicationService,
public addressSelectionModal: AddressSelectionModalService
public addressSelectionModal: AddressSelectionModalService,
public checkoutService: CheckoutService,
public cdr: ChangeDetectorRef
) {
super();
}
@@ -45,13 +49,31 @@ export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase i
this.customerService
.getCustomer(this.upgradeCustomerId, 2)
.pipe(map((response) => response.result))
.subscribe((custoemr) => {
this.upgradeCustomer = custoemr;
this.control.patchValue(custoemr);
.subscribe((customer) => {
this.upgradeCustomer = customer;
this.control.patchValue(customer);
this.control.updateValueAndValidity();
this.control.markAllAsTouched();
});
}
this.control.get('shippingAddress').disable();
this.subscription = this.control.get('differentShippingAddress').valueChanges.subscribe((isChecked) => {
if (isChecked) {
this.control.get('shippingAddress').enable();
this.control.updateValueAndValidity();
} else {
this.control.get('shippingAddress').disable();
this.control.updateValueAndValidity();
}
});
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
createControl(customer?: CustomerDTO): FormGroup {
@@ -79,6 +101,26 @@ export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase i
phone: fb.control(customer?.communicationDetails?.phone),
mobile: fb.control(customer?.communicationDetails?.mobile),
}),
differentShippingAddress: fb.control(false),
shippingAddress: fb.group({
organisation: fb.group({
name: fb.control(''),
department: fb.control(''),
vatId: fb.control(''),
}),
gender: fb.control(undefined, [Validators.required]),
title: fb.control(''),
firstName: fb.control('', [Validators.required]),
lastName: fb.control('', [Validators.required]),
address: fb.group({
street: fb.control('', [Validators.required]),
streetNumber: fb.control('', [Validators.required]),
zipCode: fb.control('', [Validators.required]),
city: fb.control('', [Validators.required]),
info: fb.control(''),
country: fb.control('DEU', [Validators.required]),
}),
}),
});
}
async submit(): Promise<void> {
@@ -86,13 +128,20 @@ export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase i
return;
}
this.control.disable();
this.control.disable({ emitEvent: false });
let address = await this.addressSelectionModal.validateAddress(this.control.value.address);
if (address) {
this.control.patchValue({ address });
}
if (this.control.value.differentShippingAddress) {
const shippingAddress = await this.addressSelectionModal.validateAddress(this.control.value.shippingAddress.address);
if (shippingAddress) {
this.control.patchValue({ shippingAddress });
}
}
try {
let newCustomer: CustomerDTO = this.control.value;
if (this.upgradeCustomer) {
@@ -101,6 +150,21 @@ export class CustomerCreateOnlineComponent extends CustomerCreateComponentBase i
const response = await this.customerService.createOnlineCustomer(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) {
this.control.enable();
console.error(error);
return;
}
}
if (response.error) {
throw new Error(response.message);
} else {

View File

@@ -67,3 +67,11 @@ page-customer-type-selector {
.spin {
@apply animate-spin;
}
.different-shipping-address {
@apply flex-col justify-around text-cta-l mt-8;
input {
@apply text-cta-l;
}
}

View File

@@ -8,7 +8,9 @@ import { UiModalService } from '@ui/modal';
import { StringDictionary } from '@cmf/core';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { AddressSelectionModalService } from '../modals/address-selection-modal.service';
import { AddressSelectionModalService } from '../modals/address-selection-modal/address-selection-modal.service';
import { CheckoutService } from '@domain/checkout';
import { ChangeDetectorRef } from '@angular/core';
export abstract class CustomerCreateComponentBase {
emailExistsValidator: AsyncValidatorFn = async (control): Promise<ValidationErrors | null> => {
@@ -33,6 +35,10 @@ export abstract class CustomerCreateComponentBase {
abstract breadcrumb: BreadcrumbService;
abstract application: ApplicationService;
abstract addressSelectionModal: AddressSelectionModalService;
abstract checkoutService: CheckoutService;
abstract cdr: ChangeDetectorRef;
disabledSelectOptions$: Observable<StringDictionary<boolean>>;
init() {
this.type = this.activatedRoute.snapshot?.routeConfig?.path;
@@ -47,9 +53,28 @@ export abstract class CustomerCreateComponentBase {
tags: ['customer', 'create'],
params: {},
});
this.disabledSelectOptions$ = this.checkoutService.getSetableCustomerTypes(this.application.activatedProcessId).pipe(
map((setableTypes) => {
const disabledTypes: StringDictionary<boolean> = {
webshop: false,
b2b: false,
store: false,
guest: false,
};
for (const key in disabledTypes) {
if (!setableTypes.includes(key)) {
disabledTypes[key] = true;
}
}
return disabledTypes;
})
);
}
setType(type: string = 'branch') {
setType(type: string = 'store') {
this.router.navigate(['../', type], {
relativeTo: this.activatedRoute,
queryParams: this.createCustomerDataQuery(this.control.value),

View File

@@ -11,7 +11,8 @@ import { CustomerCreateBranchComponent } from './customer-create-branch.componen
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.module';
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';
@@ -26,6 +27,7 @@ import { UiCheckboxModule } from '@ui/checkbox';
UiSelectModule,
UiIconModule,
AddressSelectionModalModule,
CantAddCustomerToCartModalModule,
UiCheckboxModule,
],
exports: [CustomerCreateBranchComponent, CustomerCreateGuestComponent, CustomerCreateOnlineComponent, CustomerCreateB2BComponent],

View File

@@ -1,12 +1,19 @@
<ui-checkbox id="Gastkunde" [ngModel]="value" (ngModelChange)="setValue($event)" [disabled]="true" name="customerType" value="guest">
<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]="!(customerType$ | async).includes('online')"
[disabled]="disabledOptions?.webshop"
id="Onlinekonto"
value="online"
value="webshop"
name="customerType"
>
Onlinekonto
@@ -14,7 +21,7 @@
<ui-checkbox
[ngModel]="value"
(ngModelChange)="setValue($event)"
[disabled]="!(customerType$ | async).includes('b2b')"
[disabled]="disabledOptions?.b2b"
id="B2B Kunde"
value="b2b"
name="customerType"

View File

@@ -1,9 +1,7 @@
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, forwardRef, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ApplicationService } from '@core/application';
import { CartService } from '@domain/cart';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
type AvailableTypes = 'guest' | 'online' | 'b2b' | 'branch';
@Component({
selector: 'page-customer-type-selector',
@@ -20,24 +18,23 @@ import { take } from 'rxjs/operators';
})
export class CustomerTypeSelectorComponent implements OnInit, ControlValueAccessor {
@Input()
value: 'guest' | 'online' | 'b2b' = 'guest';
value: AvailableTypes = 'guest';
@Output()
valueChange = new EventEmitter<'guest' | 'online' | 'b2b'>();
valueChange = new EventEmitter<AvailableTypes>();
@Input()
disabled: boolean;
customerType$: Observable<('guest' | 'online' | 'b2b' | 'branch')[]>;
@Input()
disabledOptions: { [key: string]: boolean } = {};
private onChange = (value: 'guest' | 'online' | 'b2b') => {};
private onChange = (value: AvailableTypes) => {};
private onTouched = () => {};
constructor(private cdr: ChangeDetectorRef, private cartService: CartService, private application: ApplicationService) {}
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit(): void {
this.customerType$ = this.cartService.getRequiredCustomerTypes(this.application.activatedProcessId).pipe(take(1));
}
ngOnInit(): void {}
writeValue(obj: any): void {
this.value = obj;

View File

@@ -6,7 +6,7 @@ import { CrmCustomerService } from '@domain/crm';
import { CountryDTO } from '@swagger/crm';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AddressSelectionModalService } from '../../../modals/address-selection-modal.service';
import { AddressSelectionModalService } from '../../../modals/address-selection-modal/address-selection-modal.service';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';

View File

@@ -6,7 +6,7 @@ import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, CountryDTO, PayerDTO } from '@swagger/crm';
import { combineLatest, Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { AddressSelectionModalService } from '../../../modals/address-selection-modal.service';
import { AddressSelectionModalService } from '../../../modals/address-selection-modal/address-selection-modal.service';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { AssignedPayerHelper } from '@domain/crm';

View File

@@ -6,7 +6,7 @@ import { CrmCustomerService } from '@domain/crm';
import { CountryDTO } from '@swagger/crm';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AddressSelectionModalService } from '../../../modals/address-selection-modal.service';
import { AddressSelectionModalService } from '../../../modals/address-selection-modal/address-selection-modal.service';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';

View File

@@ -6,7 +6,7 @@ import { CrmCustomerService } from '@domain/crm';
import { CountryDTO, ShippingAddressDTO } from '@swagger/crm';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AddressSelectionModalService } from '../../../modals/address-selection-modal.service';
import { AddressSelectionModalService } from '../../../modals/address-selection-modal/address-selection-modal.service';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { ShippingAddressHelper } from '@domain/crm';

View File

@@ -0,0 +1,37 @@
<ng-container *ngIf="customer$ | async; let customer">
<a [routerLink]="['/customer', customer?.id]" class="card-customer-details">
<span class="title">Kundendetails</span>
</a>
<a class="card-customer-orders">
<span class="title">Bestellungen</span>
</a>
<div class="card-customer-card">
<div class="header-container">
<h1 class="title">Kundenkarte</h1>
</div>
<div class="content">
<p class="info">
Alle Infos zu Ihrer Kundenkarte <br />
und allen Partnerkarten.
</p>
<div class="customer-card">
<page-card-template
*ngIf="customerCard$ | async; let customerCard"
[cardDetails]="customerCard"
[isCustomerCard]="true"
></page-card-template>
</div>
<div class="partner-cards">
<p class="partner-cards-headline">Partnerkarten</p>
<page-card-template
*ngFor="let partnerCard of partnerCards$ | async"
[cardDetails]="partnerCard"
[isCustomerCard]="false"
></page-card-template>
</div>
<!-- <div class="cancel-customer-card">
<button (click)="onCancelCard()" class="cancel-message">Kundenkarte kündigen</button>
</div> -->
</div>
</div>
</ng-container>

View File

@@ -0,0 +1,62 @@
:host {
@apply flex flex-col box-border overflow-scroll;
height: calc(100vh - 293px);
}
.title {
@apply text-page-heading font-bold;
}
.card-customer-orders,
.card-customer-card,
.card-customer-details {
@apply bg-white p-4 text-center;
box-shadow: 0 -2px 24px 0 #dce2e9;
}
.card-customer-card {
@apply p-0 relative;
}
.content {
@apply relative overflow-x-hidden overflow-y-scroll;
scroll-behavior: smooth;
max-height: calc(100vh - 400px);
}
.header-container {
@apply bg-white w-full absolute z-sticky;
left: 0;
box-shadow: 0px 19px 25px -10px white;
}
a {
@apply no-underline;
}
.info {
@apply text-2xl mb-px-35;
padding-top: 70px;
}
.partner-cards-headline {
@apply text-xl mb-px-15 font-bold;
}
.card-customer-orders .title,
.card-customer-details .title {
@apply text-inactive-customer text-xl;
}
.cancel-customer-card {
@apply mb-px-50;
}
.cancel-message {
@apply text-card-sub bg-transparent text-brand border-none font-bold pr-0 no-underline;
}
.customer-card,
.partner-cards {
@apply box-border flex flex-col items-center justify-items-center;
}

View File

@@ -1,12 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CrmCustomerService } from '@domain/crm';
import { BonusCardInfoDTO, CustomerDTO } from '@swagger/crm';
import { Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
@Component({
selector: 'page-customer-card',
templateUrl: 'customer-card.component.html',
styleUrls: ['customer-card.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerCardComponent implements OnInit {
constructor() {}
customer$: Observable<CustomerDTO>;
customerId$: Observable<number>;
ngOnInit() {}
customerCard$: Observable<BonusCardInfoDTO>;
partnerCards$: Observable<BonusCardInfoDTO[]>;
constructor(private route: ActivatedRoute, private customerService: CrmCustomerService) {}
ngOnInit() {
this.customerId$ = this.route.params.pipe(map((p) => Number(p['customerId'])));
this.customer$ = this.customerId$.pipe(
switchMap((id) => this.customerService.getCustomer(id, 2)),
map((cr) => cr.result)
);
this.customerCard$ = this.customerId$.pipe(
switchMap((id) => this.customerService.getCustomerCard(id)),
map((response) => response.result?.find((card) => card.isPrimary))
);
this.partnerCards$ = this.customerId$.pipe(
switchMap((id) => this.customerService.getCustomerCard(id)),
map((response) => response.result?.filter((card) => !card.isPrimary))
);
}
onCancelCard(): void {}
}

View File

@@ -2,7 +2,7 @@
<a [routerLink]="['/customer', customer?.id, 'orders']" class="card-customer-orders">
<span class="title">Bestellungen</span>
</a>
<a [routerLink]="['/customer', customer?.id, 'card']" class="card-customer-card">
<a *ngIf="hasCustomerCard$ | async" [routerLink]="['/customer', customer?.id, 'card']" class="card-customer-card">
<span class="title">Kundenkarte</span>
</a>
<div class="card-customer-details">

View File

@@ -10,6 +10,8 @@ import { UiDebugModalComponent, UiModalService } from '@ui/modal';
import { ShippingAddressHelper } from 'apps/domain/crm/src/lib/helpers/shipping-address.helper';
import { Observable } from 'rxjs';
import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { CantAddCustomerToCartModalComponent } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.component';
import { CantAddCustomerToCartData } from '../modals/cant-add-customer-to-cart-modal/cant-add-customer-to-cart.data';
@Component({
selector: 'page-customer-details',
@@ -31,6 +33,7 @@ export class CustomerDetailsComponent implements OnInit {
isB2b$: Observable<boolean>;
cartExists$: Observable<boolean>;
hasCustomerCard$: Observable<boolean>;
private currentBreadcrumb: Breadcrumb;
@@ -87,6 +90,10 @@ export class CustomerDetailsComponent implements OnInit {
this.customerFeatures$ = this.customer$.pipe(map((customer: CustomerDTO) => Object.values(customer.features).filter((f) => f.enabled)));
this.hasCustomerCard$ = this.customerFeatures$.pipe(
map((features) => !!features.find((feature) => feature.description === 'Kundenkarte'))
);
this.customerType$ = this.customerFeatures$.pipe(
map((features: KeyValueDTOOfStringAndString[]) => features[0].key as 'store' | 'onlineshop' | 'b2b' | 'guestaccount')
);
@@ -129,15 +136,24 @@ export class CustomerDetailsComponent implements OnInit {
async continue() {
const customer = await this.customer$.pipe(first()).toPromise();
// Check if customer can be added to the checkout
const canSetCustomer = await this.checkoutService.canSetCustomer(this.application.activatedProcessId, customer).toPromise();
if (!canSetCustomer.ok) {
this.modal.open({
content: CantAddCustomerToCartModalComponent,
data: {
filter: canSetCustomer.filter,
message: canSetCustomer.message,
} as CantAddCustomerToCartData,
});
return;
}
// Set Process Name
this.process.updateName(this.application.activatedProcessId, customer.lastName);
// Set Customer For Process
const canAdd = await this.checkoutService.setCustomer(this.application.activatedProcessId, customer);
if (!canAdd) {
return;
}
await this.checkoutService.setCustomer(this.application.activatedProcessId, customer).toPromise();
// Set Invoice Address If Selected
if (!!this.selectedPayer) {

View File

@@ -23,6 +23,7 @@ import { CustomerDataEditB2CComponent } from './customer-data-edit/customer-data
import { CustomerDataEditB2BComponent } from './customer-data-edit/customer-data-edit-b2b.component';
import { UiRadioModule } from '@ui/radio';
import { CustomerCardComponent } from './customer-card/customer-card.component';
import { CardTemplateComponent } from './shared/card-template/card-template.component';
import { CustomerOrdersComponent } from './customer-orders/customer-orders.component';
import { UiCommonModule } from '@ui/common';
@NgModule({
@@ -55,6 +56,7 @@ import { UiCommonModule } from '@ui/common';
ShippingEditB2BComponent,
ShippingEditB2CComponent,
CustomerCardComponent,
CardTemplateComponent,
CustomerOrdersComponent,
],
})

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { CrmCustomerService } from '@domain/crm';
import { map, take } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CustomerCardGuard implements CanActivate {
constructor(private router: Router, private customerService: CrmCustomerService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const id = Number(route.params?.customerId);
return this.customerService.getCustomer(id, 2).pipe(
take(1),
map((customer) => {
if (!customer.result.features.find((feature) => feature.description === 'Kundenkarte')) {
this.router.navigate(['/customer', id]);
}
return true;
})
);
}
}

View File

@@ -0,0 +1,38 @@
<div class="wrapper">
<div *ngIf="cardDetails" class="card-main">
<div class="icons">
<div *ngIf="isCustomerCard && frontside" class="icon-barcode">
<ui-icon (click)="frontside = !frontside" size="35px" icon="barcode"></ui-icon>
</div>
<div *ngIf="isCustomerCard && !frontside" class="icon-back">
<ui-icon (click)="frontside = !frontside" size="25px" icon="refresh"></ui-icon>
</div>
<!-- <div *ngIf="!isCustomerCard" class="icon-delete"><ui-icon (click)="onDeletePartnerCard()" size="25px" icon="trash"></ui-icon></div> -->
</div>
<div class="headline">
<p *ngIf="isCustomerCard && frontside">Ihre Lesepunkte</p>
<p *ngIf="isCustomerCard && !frontside">Kartennummer</p>
<p *ngIf="!isCustomerCard">Partnerkartennummer</p>
</div>
<div>
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="card-number">{{ cardDetails.code }}</div>
<div *ngIf="isCustomerCard && frontside" class="points">{{ cardDetails.totalPoints | number }}</div>
</div>
<div class="barcode-button">
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="barcode-field">
<img class="barcode" src="/assets/images/barcode.png" alt="Barcode" />
</div>
<div *ngIf="isCustomerCard && frontside">
<button class="button" (click)="onRewardShop()">Zum Prämienshop</button>
</div>
</div>
</div>
<div class="card-bottom">
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="customer-name">
<p>{{ cardDetails.firstName }} {{ cardDetails.lastName }}</p>
</div>
<div *ngIf="isCustomerCard && frontside" class="logo">
<img class="logo-picture" src="/assets/images/Hugendubel_Logo.png" alt="Hugendubel Logo" />
</div>
</div>
</div>

View File

@@ -0,0 +1,82 @@
.wrapper {
@apply box-border rounded-customerCard relative flex flex-col;
width: 421px;
height: 266px;
margin-bottom: 42px;
box-shadow: 0px 21px 64px 0px #4e4e4e;
perspective: 1000px;
}
ui-icon {
color: #f70400;
}
.icons {
@apply absolute;
right: 21px;
top: 19px;
}
.headline {
@apply text-wild-blue-yonder mt-px-15 text-sm font-bold;
}
.card-number,
.points {
@apply text-white -mt-px-15 font-bold;
font-size: 3.5rem;
}
.card-number {
@apply -mt-px-10 text-4xl;
}
.button {
@apply border-none outline-none bg-brand text-white font-bold text-cta-l px-px-25 py-px-15 rounded-full mt-px-15;
}
.barcode-field {
@apply bg-white mt-px-10 box-border;
width: 201px;
height: 80px;
}
.barcode {
@apply mt-px-15;
}
.barcode-button {
@apply box-border w-full flex flex-col items-center justify-items-center;
}
.customer-name {
@apply w-full text-left font-bold ml-px-20;
}
.logo {
@apply w-full text-right;
margin-top: 13px;
margin-right: 16px;
margin-bottom: 14px;
}
.logo-picture {
width: 101px;
height: 22px;
}
.card-main {
@apply box-border rounded-t-customerCard flex flex-col bg-onyx;
width: 421px;
height: 217px;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.card-bottom {
@apply box-border rounded-b-customerCard flex flex-row items-center text-black;
width: 421px;
height: 49px;
transition: transform 0.8s;
transform-style: preserve-3d;
}

View File

@@ -0,0 +1,27 @@
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { BonusCardInfoDTO } from '@swagger/crm';
@Component({
selector: 'page-card-template',
templateUrl: 'card-template.component.html',
styleUrls: ['card-template.component.scss'],
})
export class CardTemplateComponent implements OnInit {
@Input() cardDetails: BonusCardInfoDTO;
@Input() isCustomerCard: boolean;
frontside: boolean;
constructor() {}
ngOnInit() {
this.frontside = true;
}
switchBacksideFrontside(): void {
this.frontside = !this.frontside;
}
onRewardShop(): void {}
onDeletePartnerCard(): void {}
}

View File

@@ -1,6 +1,7 @@
<page-customer-searchbox (searchStarted)="applyFilters(false)"></page-customer-searchbox>
<ui-selected-filter-options [(value)]="selectedFilters" (valueChange)="updateFilter($event)"> </ui-selected-filter-options>
<ui-selected-filter-options [(value)]="selectedFilters" [initialFilter]="initialFilter" (valueChange)="updateFilter($event)">
</ui-selected-filter-options>
<ui-filter-group [(value)]="selectedFilters" (valueChange)="updateFilter($event)"></ui-filter-group>

View File

@@ -1,71 +1,30 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { CartService } from '@domain/cart';
import { map, take } from 'rxjs/operators';
import { CheckoutService } from '@domain/checkout';
import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CustomerCreateGuard implements CanActivateChild {
constructor(private router: Router, private cartService: CartService, private application: ApplicationService) {}
readonly typeMappings = {
guest: 'guest',
online: 'webshop',
b2b: 'b2b',
branch: 'store',
};
constructor(private router: Router, private checkoutService: CheckoutService, private application: ApplicationService) {}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const currentRoute = route.url[0].path as 'guest' | 'online' | 'b2b' | 'branch';
return this.cartService.getRequiredCustomerTypes(this.application.activatedProcessId).pipe(
take(1),
map((types) => {
// If current route is not available in types -> navigate to next available route
// Cycle => 'branch' -> 'online' -> 'guest' -> 'b2b'
if (!types.includes(currentRoute)) {
if (currentRoute === 'branch' && types.includes('online')) {
this.router.navigate(['/customer', 'create', 'online']);
return false;
}
if (currentRoute === 'branch' && types.includes('guest')) {
this.router.navigate(['/customer', 'create', 'guest']);
return false;
}
if (currentRoute === 'branch' && types.includes('b2b')) {
this.router.navigate(['/customer', 'create', 'b2b']);
return false;
}
if (currentRoute === 'online' && types.includes('guest')) {
this.router.navigate(['/customer', 'create', 'guest']);
return false;
}
if (currentRoute === 'online' && types.includes('b2b')) {
this.router.navigate(['/customer', 'create', 'b2b']);
return false;
}
if (currentRoute === 'online' && types.includes('branch')) {
this.router.navigate(['/customer', 'create', 'branch']);
return false;
}
if (currentRoute === 'guest' && types.includes('b2b')) {
this.router.navigate(['/customer', 'create', 'b2b']);
return false;
}
if (currentRoute === 'guest' && types.includes('branch')) {
this.router.navigate(['/customer', 'create', 'branch']);
return false;
}
if (currentRoute === 'guest' && types.includes('online')) {
this.router.navigate(['/customer', 'create', 'online']);
return false;
}
if (currentRoute === 'b2b' && types.includes('branch')) {
this.router.navigate(['/customer', 'create', 'branch']);
return false;
}
if (currentRoute === 'b2b' && types.includes('guest')) {
this.router.navigate(['/customer', 'create', 'guest']);
return false;
}
if (currentRoute === 'b2b' && types.includes('online')) {
this.router.navigate(['/customer', 'create', 'online']);
return false;
}
const currentRoute = route.url[0].path;
return this.checkoutService.getSetableCustomerTypes(this.application.activatedProcessId).pipe(
map((selectables) => {
const result = selectables.includes(currentRoute);
if (!result) {
this.router.navigate(['/customer/create', selectables[0]], { queryParams: route.queryParams });
}
return true;
return result;
})
);
}

View File

@@ -1,6 +1,6 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { AddressDTO } from '@swagger/crm';
import { UiModalRef } from 'apps/ui/modal/src/lib/defs';
import { UiModalRef } from '@ui/modal';
@Component({
selector: 'page-address-selection-modal',

View File

@@ -0,0 +1,18 @@
<div class="wrapper">
<div class="actions">
<button class="close-btn" (click)="ref.close()">
<ui-icon icon="close" size="21px"></ui-icon>
</button>
</div>
<h2>Warenkorb kann dem Kunden nicht zugewiesen werden</h2>
<p>
<!-- Der Kundentyp ist mit der gewählten Kaufoption nicht kombinierbar. -->
{{ ref.data.message }}
</p>
<div class="cta-wrapper">
<a class="search-btn" [routerLink]="['/customer/search']" [queryParams]="ref.data.filter" (click)="ref.close()">Zur Kundensuche</a>
<a class="create-btn" [routerLink]="['/customer/create', createCustomerType]" (click)="ref.close()">Kundendaten erfassen</a>
</div>
</div>

View File

@@ -0,0 +1,40 @@
:host {
@apply bg-white;
}
.actions {
@apply flex flex-row justify-end;
}
.close-btn {
@apply bg-transparent border-none text-ucla-blue;
}
h2 {
@apply mt-0;
}
h2,
p {
@apply text-center text-card-sub font-bold;
}
h2 {
@apply font-bold text-card-sub;
}
p {
@apply text-dark-goldenrod;
}
.cta-wrapper {
@apply flex flex-row gap-8 my-8 justify-center;
}
.search-btn {
@apply border-4 border-solid border-brand outline-none bg-white text-brand font-bold text-cta-l px-px-25 py-px-15 rounded-full no-underline;
}
.create-btn {
@apply border-none outline-none bg-brand text-white font-bold text-cta-l px-px-25 py-px-15 rounded-full no-underline;
}

View File

@@ -0,0 +1,20 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UiModalRef } from '@ui/modal';
import { CantAddCustomerToCartData } from './cant-add-customer-to-cart.data';
@Component({
selector: 'pages-cant-add-customer-to-cart-modal',
templateUrl: 'cant-add-customer-to-cart.component.html',
styleUrls: ['cant-add-customer-to-cart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CantAddCustomerToCartModalComponent {
get createCustomerType() {
if (this.ref?.data?.filter) {
return this.ref?.data?.filter?.customertype;
}
return '';
}
constructor(public ref: UiModalRef<CantAddCustomerToCartData>) {}
}

View File

@@ -0,0 +1,6 @@
import { StringDictionary } from '@cmf/core';
export interface CantAddCustomerToCartData {
message: string;
filter: StringDictionary<string>;
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CantAddCustomerToCartModalComponent } from './cant-add-customer-to-cart.component';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
@NgModule({
imports: [CommonModule, UiIconModule, RouterModule],
exports: [CantAddCustomerToCartModalComponent],
declarations: [CantAddCustomerToCartModalComponent],
})
export class CantAddCustomerToCartModalModule {}

View File

@@ -16,6 +16,7 @@ import { CustomerCardComponent } from './customer-details/customer-card/customer
import { CustomerDataEditB2BComponent } from './customer-details/customer-data-edit/customer-data-edit-b2b.component';
import { CustomerDataEditB2CComponent } from './customer-details/customer-data-edit/customer-data-edit-b2c.component';
import { CustomerDetailsComponent } from './customer-details/customer-details.component';
import { CustomerCardGuard } from './customer-details/guards/customer-card.guard';
import { CustomerOrdersComponent } from './customer-details/customer-orders/customer-orders.component';
import { CustomerSearchComponent } from './customer-search/customer-search.component';
import { CustomerSearchMainComponent } from './customer-search/search-main/search-main.component';
@@ -41,11 +42,11 @@ const routes: Routes = [
path: 'create',
canActivateChild: [CustomerCreateGuard],
children: [
{ path: 'branch', component: CustomerCreateBranchComponent },
{ path: 'online', component: CustomerCreateOnlineComponent },
{ path: 'store', component: CustomerCreateBranchComponent },
{ path: 'webshop', component: CustomerCreateOnlineComponent },
{ path: 'b2b', component: CustomerCreateB2BComponent },
{ path: 'guest', component: CustomerCreateGuestComponent },
{ path: '', pathMatch: 'full', redirectTo: 'branch' },
{ path: '', pathMatch: 'full', redirectTo: 'store' },
],
},
{
@@ -54,6 +55,7 @@ const routes: Routes = [
},
{
path: ':customerId/card',
canActivate: [CustomerCardGuard],
component: CustomerCardComponent,
},
{

View File

@@ -1,5 +1,4 @@
import { ChangeDetectorRef, Pipe, PipeTransform } from '@angular/core';
import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, CommunicationDetailsDTO, OrganisationDTO } from '@swagger/crm';
export interface AddressData {
@@ -20,7 +19,7 @@ export class AddressPipe implements PipeTransform {
private result: string;
private data: AddressData;
constructor(private customerService: CrmCustomerService, private cdr: ChangeDetectorRef) {}
constructor(private cdr: ChangeDetectorRef) {}
transform(data: AddressData): string {
this.data = data;
@@ -32,10 +31,15 @@ export class AddressPipe implements PipeTransform {
async getResult() {
let result = undefined;
if (!!this.data?.address?.street && !!this.data?.address?.city && !!this.data?.address?.zipCode && !!this.data?.address?.country) {
const countries = await this.customerService.getCountries().toPromise();
const country = countries.result.find((c) => c.isO3166_A_3 === this.data?.address.country)?.name || this.data?.address.country;
result = `${this.data?.address?.street} ${this.data?.address?.streetNumber}, ${this.data?.address?.zipCode} ${this.data?.address?.city}, ${country}`;
let parts = [
this.data?.organisation?.name || '',
[this.data?.lastName, this.data?.firstName].filter((f) => !!f).join(', '),
`${this.data?.address?.street || ''} ${this.data?.address?.streetNumber || ''}`,
`${this.data?.address?.zipCode || ''} ${this.data?.address?.city || ''}`,
]
.filter((value) => value != '')
.filter((value) => value.trim());
result = parts.join(' | ');
}
if (result !== this.result) {

View File

@@ -2,5 +2,5 @@ import { FormControl, ValidationErrors } from '@angular/forms';
const EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
export function validateEmail(c: FormControl): ValidationErrors | null {
return c.value === '' || EMAIL_REGEXP.test(c.value) ? null : { invalid: 'E-Mail ist ungültig' };
return c.value == null || c.value === '' || EMAIL_REGEXP.test(c.value) ? null : { invalid: 'E-Mail ist ungültig' };
}

View File

@@ -26,6 +26,8 @@ import { CustomerFeatures } from 'apps/sales/src/app/core/models/customer-featur
import { CartService } from '../services/cart.service';
import { BranchInfoDTO } from '@swagger/isa';
import { AppState } from 'apps/sales/src/app/core/store/state/app.state';
import { CheckoutService } from '@domain/checkout';
import { ApplicationService } from '@core/application';
export interface CartReviewItem extends BookData {
cartEntryId: number;
@@ -121,7 +123,9 @@ export class CartReviewComponent implements OnInit, OnDestroy {
private router: Router,
private printer: PrinterService,
private errorService: ErrorService,
private cartService: CartService
private cartService: CartService,
private checkoutService: CheckoutService,
private applicationService: ApplicationService
) {
this.deliveryCount$ = this.cartData$.pipe(
startWith(-1),
@@ -359,17 +363,20 @@ export class CartReviewComponent implements OnInit, OnDestroy {
this.total.price = totalSum;
}
next() {
async next() {
if (this.customer) {
// Crete order
this.continueBtn.startLoading();
this.payMethodDialog.directOrder();
} else {
const response = await this.checkoutService.canSetCustomer(this.applicationService.activatedProcessId, undefined).toPromise();
console.log(response);
// No customer redirect to search
let newBread: Breadcrumb;
newBread = {
name: 'Kundensuche',
path: 'customer/search',
queryParams: response.filter,
};
this.navigate(newBread, 'customer');
}
@@ -409,7 +416,7 @@ export class CartReviewComponent implements OnInit, OnDestroy {
private navigate(bread: Breadcrumb, menu: string) {
this.store.dispatch(new ChangeCurrentRoute(bread.path));
this.store.dispatch(new AddBreadcrumb(bread, menu));
this.router.navigate([bread.path]);
this.router.navigate([bread.path], { queryParams: bread.queryParams });
}
enableDisableOrder(disable: boolean) {

View File

@@ -124,50 +124,60 @@
<div class="product-information">
<span class="book-title">{{ book.product.contributors }} - {{ book.product.name }}</span>
<span class="can-add-hint" *ngIf="!canAddItem"
>Leider können wir den Service mit den bereits ausgewählten Services im Warenkorb nicht kombinieren.</span
>
<span class="book-format">
<lib-icon class="order-book-icon" width="18px" height="18px" name="Icon_{{ book.product.format }}" alt="book-icon"></lib-icon>
{{ book.product.formatDetail }}
</span>
<span class="can-add-hint" *ngIf="!canAddItem">{{ availabilityMessage }}</span>
<span class="price">{{ selectedPrice | bookPrice }} {{ currency }}</span>
<div class="product-details">
<div class="product-column">
<span>
<lib-icon
class="order-book-icon"
width="18px"
height="18px"
name="Icon_{{ book.product.format }}"
alt="book-icon"
></lib-icon>
{{ book.product.formatDetail }}
</span>
<span
class="order-details-delivery-info"
*ngIf="
deliveryType === 'Donwload' ||
deliveryType === 'Rücklage' ||
deliveryType === 'Versand' ||
!currentDeliveryDate ||
deliveryType === 'Abholung' ||
!currentPickUpDate
"
></span>
<span class="order-details-delivery-info" *ngIf="deliveryType === deliveryOptions.delivery && currentDeliveryDate"
>Versanddatum {{ currentDeliveryDate }}</span
>
<span class="order-details-delivery-info" *ngIf="deliveryType === deliveryOptions.deliveryB2b"
>Versanddatum {{ b2bDeliveryDate | date: 'dd.MM.yy' }}</span
>
<span class="price">{{ selectedPrice | bookPrice }} {{ currency }}</span>
<span class="order-details-delivery-info" *ngIf="deliveryType === 'Abholung' && currentPickUpDate"
>Abholung ab {{ currentPickUpDate }}</span
>
<span
class="order-details-delivery-info"
*ngIf="
deliveryType === 'Donwload' ||
deliveryType === 'Rücklage' ||
deliveryType === 'Versand' ||
!currentDeliveryDate ||
deliveryType === 'Abholung' ||
!currentPickUpDate
"
></span>
<span class="order-details-delivery-info" *ngIf="deliveryType === deliveryOptions.delivery && currentDeliveryDate"
>Versanddatum {{ currentDeliveryDate }}</span
>
<span class="order-details-delivery-info" *ngIf="deliveryType === deliveryOptions.deliveryB2b"
>Versanddatum {{ b2bDeliveryDate | date: 'dd.MM.yy' }}</span
>
<span class="order-details-delivery-info" *ngIf="deliveryType === 'Abholung' && currentPickUpDate"
>Abholung ab {{ currentPickUpDate }}</span
>
</div>
<div class="product-column">
<app-delete-dropdown
[selected]="currentNumberOfItems"
[options]="possibleItems"
(valueChanges)="setNumberOfItems($event)"
(delete)="deleteProduct()"
(switch)="switchSteps(true)"
[ddload]="true"
[notUpdatable]="isDownload"
#deleteDropdown
></app-delete-dropdown>
</div>
</div>
</div>
<!-- TODO: populate here with delivery info like DHL I Lieferung 18.01.-->
<app-delete-dropdown
[selected]="currentNumberOfItems"
[options]="possibleItems"
(valueChanges)="setNumberOfItems($event)"
(delete)="deleteProduct()"
(switch)="switchSteps(true)"
[ddload]="true"
[notUpdatable]="isDownload"
#deleteDropdown
></app-delete-dropdown>
</div>
<div class="secondary-info">
<div class="errors">
@@ -196,12 +206,15 @@
Artikel scannen
</button>
<button class="btn-outline" (click)="updateCart()" [disabled]="(addItemsToCartDisabled || loading) && !isDownload">
<button class="btn-outline" (click)="continueSearch()" [disabled]="(addItemsToCartDisabled || loading) && !isDownload">
Weiter suchen
</button>
<app-button [primary]="true" [disabled]="(addItemsToCartDisabled || loading) && !isDownload" (action)="itemsConfirmed()">{{
confirmationBtnText
}}</app-button>
<app-button
[primary]="true"
[disabled]="(addItemsToCartDisabled || loading) && !isDownload"
(action)="canAddItem ? itemsConfirmed() : redirectRoute()"
>{{ confirmationBtnText }}</app-button
>
</div>
</div>
</app-modal>

View File

@@ -239,15 +239,22 @@
font-weight: bold;
}
.product-details {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 25px;
}
.product-column {
display: flex;
flex-direction: column;
}
.book-title {
font-size: 16px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.book-format {
margin-top: 25px;
}
.can-add-hint {

View File

@@ -8,7 +8,7 @@ import { ChangeCurrentRoute } from '../../../../core/store/actions/process.actio
import { SetCartEntry } from '../../../../core/store/actions/cart-entry.actions';
import { Subject, Observable, of } from 'rxjs';
import { tap, map, filter, take } from 'rxjs/operators';
import { AddBreadcrumb } from '../../../../core/store/actions/breadcrumb.actions';
import { AddBreadcrumb, PopLastBreadcrumbs } from '../../../../core/store/actions/breadcrumb.actions';
import { Breadcrumb } from '../../../../core/models/breadcrumb.model';
import { BranchSelectors } from '../../../../core/store/selectors/branch.selector';
import { ProductAvailability } from '../../../../core/models/product-availability.model';
@@ -28,6 +28,8 @@ import { allowedAvailabilityStatusCodes } from 'apps/sales/src/app/core/utils/pr
import { OrderService } from '@swagger/oms';
import { CartService } from '@domain/cart';
import { UiDebugModalComponent, UiModalService } from '@ui/modal';
import { ApplicationService } from '@core/application';
import { CheckoutService } from '@domain/checkout';
@Component({
selector: 'app-checkout',
@@ -89,6 +91,7 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
confirmationBtnText = 'Fortfahren';
canAddItem: boolean = true;
availabilityMessage: string;
// Trigger other functionality if needed
@Output() closed: EventEmitter<boolean> = new EventEmitter();
@@ -226,7 +229,9 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
private datePipe: DatePipe,
private shoppingCartService: CartService,
private uiModal: UiModalService,
private cdrf: ChangeDetectorRef
private cdrf: ChangeDetectorRef,
private applicationService: ApplicationService,
private checkoutService: CheckoutService
) {}
ngOnInit() {
@@ -600,8 +605,21 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
}
async checkCanAdd() {
// TODO: Hindernissmeldung
this.canAddItem = await this.shoppingCartService.canAddItem(undefined, this.currentAvailability);
let result = await this.shoppingCartService.canAddItem(undefined, this.currentAvailability);
if (typeof result === 'string') {
this.canAddItem = false;
this.confirmationBtnText = 'Ohne Artikel fortfahren';
this.availabilityMessage = result;
} else {
this.canAddItem = true;
this.availabilityMessage = undefined;
this.confirmationBtnText = 'Fortfahren';
}
}
continueSearch() {
this.store.dispatch(new PopLastBreadcrumbs(''));
this.router.navigate(['/product/results']);
}
updateCart() {
@@ -639,15 +657,17 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
this.closeModal();
}
private redirectRoute() {
async redirectRoute() {
if (this.customerAlredySet) {
this.store.dispatch(new AddBreadcrumb(this.generateBreadcrumb(), 'shoppingCart', true));
this.store.dispatch(new ChangeCurrentRoute('/cart/review'));
this.router.navigate(['/cart/review']);
} else {
const response = await this.checkoutService.canSetCustomer(this.applicationService.activatedProcessId, undefined).toPromise();
this.store.dispatch(new AddBreadcrumb(this.generateBreadcrumb(), 'customer', true));
this.store.dispatch(new ChangeCurrentRoute('/customer/search'));
this.router.navigate(['/customer/search']);
this.router.navigate(['/customer/search'], { queryParams: response.filter });
}
this.closeModal();

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { CartService, CartItem } from '@domain/cart';
import { Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { map, switchMap, tap, first } from 'rxjs/operators';
import { map, switchMap, first, filter } from 'rxjs/operators';
import { SharedSelectors } from '../core/store/selectors/shared.selectors';
import { ProcessSelectors } from '../core/store/selectors/process.selectors';
import { OLAAvailabilityDTO, StoreCheckoutService } from '@swagger/checkout';
@@ -11,7 +11,6 @@ import { StringDictionary } from '@cmf/core';
import { ApplicationService } from '@core/application';
import { CrmCustomerService } from '@domain/crm';
import { CustomerDTO } from '@swagger/crm';
import { UiDebugModalComponent, UiModalService } from '@ui/modal';
@Injectable()
export class CartRefactImp implements CartService {
@@ -19,8 +18,7 @@ export class CartRefactImp implements CartService {
private store: Store,
private checkoutService: StoreCheckoutService,
private applicationService: ApplicationService,
private customerService: CrmCustomerService,
private uiModal: UiModalService
private customerService: CrmCustomerService
) {}
getItems(processId: number): Observable<CartItem[]> {
@@ -52,58 +50,61 @@ export class CartRefactImp implements CartService {
}
getCartId(processId: number): Observable<number> {
return this.store.select(ProcessSelectors.getCurrentProcess).pipe(map((process) => process.cartId));
}
getRequiredCustomerTypes(processId: number): Observable<any> {
// return this.getCartId(processId).pipe(
// switchMap((cartId) => {
// return this.checkoutService
// .StoreCheckoutCanAddBuyer({ shoppingCartId: cartId, payload: { customerFeatures: {} } })
// .pipe(tap((res) => console.log(res)));
// })
// );
return this.getItems(processId).pipe(
map((items) => {
const itemsSet = new Set(items.map((i) => i.target));
const itemsArr = Array.from(itemsSet);
if (itemsArr.length === 1) {
switch (itemsArr[0]) {
// TODO bei Einzelprodukt im Warenkorb: Versand Artikel wenn Abholung auch möglich
case 'reserve':
case 'pickup':
return ['branch', 'online', 'b2b'];
case 'shipping':
return ['guest', 'online'];
case 'download':
return ['online'];
case 'shippingB2B':
return ['b2b'];
}
}
// TODO bei Mischkörben: Versand Artikel wenn Abholung auch möglich
// TODO Mischkörbe Sonderfall: reserve wird nicht gehandled
if (itemsSet.has('shipping') && itemsSet.has('download')) {
return ['online'];
}
if (itemsSet.has('shipping') && itemsSet.has('shippingB2B')) {
return ['b2b'];
}
if (itemsSet.has('pickup') && itemsSet.has('shipping')) {
return ['guest', 'online'];
}
if (itemsSet.has('pickup') && itemsSet.has('shippingB2B')) {
return ['b2b'];
}
return ['branch', 'online', 'b2b'];
})
return this.store.select(ProcessSelectors.getCurrentProcess).pipe(
filter((f) => !!f),
map((process) => process.cartId)
);
}
async canAddItem(processId: number, availability: OLAAvailabilityDTO): Promise<boolean> {
// getRequiredCustomerTypes(processId: number): Observable<any> {
// // return this.getCartId(processId).pipe(
// // switchMap((cartId) => {
// // return this.checkoutService
// // .StoreCheckoutCanAddBuyer({ shoppingCartId: cartId, payload: { customerFeatures: {} } })
// // .pipe(tap((res) => console.log(res)));
// // })
// // );
// return this.getItems(processId).pipe(
// map((items) => {
// const itemsSet = new Set(items.map((i) => i.target));
// const itemsArr = Array.from(itemsSet);
// if (itemsArr.length === 1) {
// switch (itemsArr[0]) {
// // TODO bei Einzelprodukt im Warenkorb: Versand Artikel wenn Abholung auch möglich
// case 'reserve':
// case 'pickup':
// return ['branch', 'online', 'b2b'];
// case 'shipping':
// return ['guest', 'online'];
// case 'download':
// return ['online'];
// case 'shippingB2B':
// return ['b2b'];
// }
// }
// // TODO bei Mischkörben: Versand Artikel wenn Abholung auch möglich
// // TODO Mischkörbe Sonderfall: reserve wird nicht gehandled
// if (itemsSet.has('shipping') && itemsSet.has('download')) {
// return ['online'];
// }
// if (itemsSet.has('shipping') && itemsSet.has('shippingB2B')) {
// return ['b2b'];
// }
// if (itemsSet.has('pickup') && itemsSet.has('shipping')) {
// return ['guest', 'online'];
// }
// if (itemsSet.has('pickup') && itemsSet.has('shippingB2B')) {
// return ['b2b'];
// }
// return ['branch', 'online', 'b2b'];
// })
// );
// }
async canAddItem(processId: number, availability: OLAAvailabilityDTO): Promise<true | string> {
const getCustomerIdByProcessIdFn = await this.store.select(CustomerSelectors.getActiveUser).pipe(first()).toPromise();
let customerId: number;
@@ -135,8 +136,10 @@ export class CartRefactImp implements CartService {
.StoreCheckoutCanAddItem({ shoppingCartId: cartId, payload: { customerFeatures, availabilities: [availability] } })
.pipe(
map((res) => {
this.uiModal.open({ content: UiDebugModalComponent, data: res });
return res.result.ok;
if (res.result.ok) {
return true;
}
return res.message;
})
);
})

View File

@@ -1,14 +1,13 @@
import { Injectable } from '@angular/core';
import { Dictionary, StringDictionary } from '@cmf/core';
import { StringDictionary } from '@cmf/core';
import { CartService } from '@domain/cart';
import { CheckoutService } from '@domain/checkout';
import { Store } from '@ngxs/store';
import { PayerDTO, PayerType, ShippingAddressDTO, StoreCheckoutService } from '@swagger/checkout';
import { CustomerDTO, PayerDTO as CrmPayerDTO, ShippingAddressDTO as CrmShippingAddressDTO } from '@swagger/crm';
import { UiDebugModalComponent, UiModalService } from '@ui/modal';
import { Observable } from 'rxjs';
import { first, map, mergeMap } from 'rxjs/operators';
import { first, map, mergeMap, switchMap } from 'rxjs/operators';
import {
RefactSetCustomer,
RefactSetCustomerBillingAddress,
@@ -19,38 +18,19 @@ import { CustomerSelectors } from '../core/store/selectors/customer.selectors';
@Injectable()
export class CheckoutRefactImp implements CheckoutService {
constructor(
private store: Store,
private checkoutService: StoreCheckoutService,
private cartService: CartService,
private uiModal: UiModalService
) {}
constructor(private store: Store, private checkoutService: StoreCheckoutService, private cartService: CartService) {}
async setCustomer(processId: number, customer: CustomerDTO): Promise<boolean> {
const response = await this.cartService
.getCartId(processId)
.pipe(
first(),
mergeMap((shoppingCartId) => {
const customerFeatures: StringDictionary<string> = {};
for (const feature of customer.features) {
customerFeatures[feature.key] = feature.key;
}
setCustomer(processId: number, customer: CustomerDTO): Observable<boolean> {
return this.canSetCustomer(processId, customer).pipe(
map((canSetCustomer) => {
if (canSetCustomer.ok) {
this.store.dispatch(new SetActiveCustomer(customer.id));
this.store.dispatch(new RefactSetCustomer(customer));
}
return this.checkoutService.StoreCheckoutCanAddBuyer({ shoppingCartId, payload: { customerFeatures } });
})
)
.toPromise();
if (response.result.ok) {
this.store.dispatch(new SetActiveCustomer(customer.id));
this.store.dispatch(new RefactSetCustomer(customer));
}
if (!response.result.ok) {
this.uiModal.open({ content: UiDebugModalComponent, data: response });
}
return response.result.ok;
return canSetCustomer.ok;
})
);
}
getCustomer(processId: number): Observable<CustomerDTO> {
@@ -103,4 +83,54 @@ export class CheckoutRefactImp implements CheckoutService {
getSelectedShippingAddress(processId: number): Observable<ShippingAddressDTO> {
throw new Error('Method not implemented.');
}
canSetCustomer(
processId: number,
customer: CustomerDTO
): Observable<{ ok?: boolean; filter?: StringDictionary<string>; message?: string }> {
const cartId$ = this.cartService.getCartId(processId).pipe(first());
const customerFeatures: StringDictionary<string> = {};
if (!!customer?.features) {
for (const feature of customer?.features) {
customerFeatures[feature.key] = feature.key;
}
}
return cartId$.pipe(
switchMap((shoppingCartId) => this.checkoutService.StoreCheckoutCanAddBuyer({ shoppingCartId, payload: { customerFeatures } })),
map(
(response) =>
({
ok: response.result.ok,
filter: response.result.queryToken?.filter || {},
message: response.message,
} as { ok?: boolean; filter?: StringDictionary<string>; message?: string })
)
);
}
getSetableCustomerTypes(processId: number) {
return this.canSetCustomer(processId, undefined).pipe(
map((res) => {
let setableTypes = [];
if (Object.keys(res.filter).length === 0) {
return ['store', 'guest', 'webshop', 'b2b'];
} else {
const cusotmerType = res.filter.customertype;
if (cusotmerType) {
setableTypes = [...setableTypes, ...cusotmerType.split(';')];
}
let customerAttributes = res.filter.customerattributes;
if (customerAttributes) {
setableTypes = [...setableTypes, ...cusotmerType.replace('store', 'branch').split(';')];
}
}
return setableTypes;
})
);
}
}

View File

@@ -1,6 +1,6 @@
<div class="container" *ngIf="value">
<button class="filters-clear" *ngIf="selectedOptions?.length" (click)="unselectAllOptions()">
Alle Filter Entfernen
<button class="filters-clear" *ngIf="compareFilter()" (click)="resetFilter()">
Filter zurücksetzen
</button>
<div

View File

@@ -26,6 +26,9 @@ export class UiSelectedFilterOptionsComponent {
this.cdr.markForCheck();
}
@Input()
initialFilter: Filter[];
@Output()
valueChange = new EventEmitter<Filter[]>();
@@ -37,18 +40,24 @@ export class UiSelectedFilterOptionsComponent {
constructor(private cdr: ChangeDetectorRef) {}
unselectAllOptions() {
function unselectOptions(options: FilterOption[]) {
options?.forEach((option) => {
option.selected = false;
unselectOptions(option.options);
});
}
this.value?.forEach((filter) => unselectOptions(filter.options));
resetFilter() {
this.value = this.initialFilter;
this.valueChange.emit(this.value);
}
compareFilter(): boolean {
let result = false;
this.value.forEach((val) => {
let filter = this.initialFilter.find((f) => f.name === val.name);
val.options.forEach((option) => {
result = result || filter.options.find((o) => o.id === option.id).selected !== option.selected;
});
});
return result;
}
unselectOption(option: FilterOption) {
function unselectOptions(options: FilterOption[]) {
options?.forEach((o) => {
@@ -61,5 +70,6 @@ export class UiSelectedFilterOptionsComponent {
this.value.forEach((filter) => unselectOptions(filter.options));
this.valueChange.emit(this.value);
this.cdr.markForCheck();
}
}

View File

@@ -1,4 +1,5 @@
// start:ng42.barrel
export * from './defs';
export * from './modal.component';
export * from './debug-modal.component';
export * from './modal.module';

View File

@@ -65,6 +65,7 @@ module.exports = {
'cool-grey': '#89949e',
glitter: '#e9f0f8',
munsell: '#edeff0',
onyx: '#171717',
// active: '#1f466c',
// inactive: '#3980c6',
// disabled: '#b0cce8',
@@ -87,6 +88,7 @@ module.exports = {
},
borderRadius: {
card: '5px',
customerCard: '10px',
filter: '20px',
autocomplete: '30px',
},