Merge branch 'feature/customer_pages' into feature/1343-Page-Kundenkarte

This commit is contained in:
Nino Righi
2021-01-25 15:05:05 +01:00
15 changed files with 303 additions and 168 deletions

View File

@@ -13,7 +13,7 @@ export class CartService {
throw new Error('Not Implemented');
}
getCardId(processId: number): Observable<number> {
getCartId(processId: number): Observable<number> {
throw new Error('Not Implemented');
}

View File

@@ -138,9 +138,9 @@ export class CustomerDetailsComponent implements OnInit {
this.process.updateName(this.application.activatedProcessId, customer.lastName);
// Set Customer For Process
const customerAdded: any = await this.checkoutService.setCustomer(this.application.activatedProcessId, customer);
if (!customerAdded.ok) {
this.modal.open({ content: UiDebugModalComponent, data: customerAdded });
const canAdd = await this.checkoutService.setCustomer(this.application.activatedProcessId, customer);
if (!canAdd) {
return;
}

View File

@@ -2,55 +2,96 @@
<div class="modal-step-1">
<div class="header">
<h1>Wie möchten Sie den Artikel erhalten?</h1>
<div (click)="closeModal()">
<lib-icon class="close-icon" height="21px" name="close" alt="close"></lib-icon>
</div>
<lib-icon (click)="closeModal()" class="close-icon" height="21px" name="close" alt="close"></lib-icon>
</div>
<div class="body">
<div class="option" *ngIf="takeNowAvailable | async">
<lib-icon class="img img-now" name="Take_now" alt="take now"></lib-icon>
<h2 class="title-take-now">Rücklage</h2>
<span class="description description-take-now">Möchten Sie den Artikel jetzt gleich mit nach Hause nehmen?</span>
<span class="price price-take-now">{{ price | bookPrice }} {{ currency }}</span>
<app-button [primary]="true" [outline]="true" class="modal-btn" (action)="selectedAction(deliveryOptions.now)"
>Auswählen</app-button
>
<div class="option__top-container">
<lib-icon class="img img-now" name="Take_now" alt="take now" width="47px" height="37px"></lib-icon>
<h2 class="title-take-now">
Rücklage /<br />
Filialentnahme
</h2>
<span class="description description-take-now">Möchten Sie den Artikel zurücklegen lassen oder sofort mitnehmen?</span>
<span class="price price-take-now">{{ price | bookPrice }} {{ currency }}</span>
</div>
<div class="button-wrapper">
<app-button [primary]="true" [outline]="true" class="modal-btn" (action)="selectedAction(deliveryOptions.now)"
>Auswählen</app-button
>
</div>
</div>
<div class="option" *ngIf="(storeAvailable | async) || searchingNewBranch">
<lib-icon class="img" name="Package_Icon" alt="package"></lib-icon>
<h2>Abholung</h2>
<span class="description description-take-away">Möchten Sie den Artikel in einer unserer Filialen abholen?</span>
<lib-search-dropdown
[selected]="currentLocation"
[options]="filteredBranches"
(valueChanges)="selectLocation($event)"
(search)="branchSearch($event)"
class="location location-dropdown"
[left]="!(shippingAvailable | async) ? '-50px' : '0'"
[load]="true"
#branchesdropdown
>
</lib-search-dropdown>
<span class="location location-date-take-away" *ngIf="!branchLoad">Abholung ab {{ currentPickUpDate }}</span>
<span class="price price-take-away">{{ pickUpPrice | bookPrice }} {{ currency }}</span>
<app-button [primary]="true" [outline]="true" class="modal-btn" (action)="selectedAction(deliveryOptions.pick_up)"
>Auswählen
</app-button>
<div class="option__top-container">
<lib-icon class="img" name="Package_Icon" alt="package" width="47px" height="37px"></lib-icon>
<h2>Abholung</h2>
<span class="description description-take-away">Möchten Sie den Artikel in einer unserer Filialen abholen?</span>
<span class="price price-take-away">{{ pickUpPrice | bookPrice }} {{ currency }}</span>
<div class="option__content-container">
<lib-search-dropdown
[selected]="currentLocation"
[options]="filteredBranches"
(valueChanges)="selectLocation($event)"
(search)="branchSearch($event)"
class="location location-dropdown"
[left]="!(shippingAvailable | async) ? '-50px' : '0'"
[load]="true"
#branchesdropdown
>
</lib-search-dropdown>
<span class="location location-date" *ngIf="!branchLoad">
Abholung ab <span>{{ currentPickUpDate }}</span>
</span>
</div>
</div>
<div class="button-wrapper">
<app-button [primary]="true" [outline]="true" class="modal-btn" (action)="selectedAction(deliveryOptions.pick_up)"
>Auswählen
</app-button>
</div>
</div>
<div class="option" *ngIf="shippingAvailable | async">
<lib-icon class="img img-truck" name="truck_Icon" alt="truck"></lib-icon>
<h2>Versand</h2>
<span class="description description-delivery">Möchten Sie den Artikel nach Hause geliefert bekommen?</span>
<div class="delivery">
<lib-icon class="check" name="Check-green" height="13px" width="17px" alt="arrow"></lib-icon>
ohne Versandkosten
<div class="option__top-container">
<lib-icon class="img img-truck" name="truck_Icon" alt="truck" width="47px" height="37px"></lib-icon>
<h2>Versand</h2>
<span class="description description-delivery">Möchten Sie den Artikel geliefert bekommen?</span>
<span class="price price-order">{{ deliverPrice | bookPrice }} {{ currency }}</span>
<br />
<div class="delivery">
Versandkostenfrei
</div>
<span class="delivery-date">
Versanddatum <span>{{ currentDeliveryDate }}</span>
</span>
</div>
<div class="button-wrapper">
<app-button [primary]="true" [outline]="true" class="modal-btn" (action)="selectedAction(deliveryOptions.delivery)"
>Auswählen</app-button
>
</div>
</div>
<div class="option" *ngIf="!(shippingAvailable | async) && storeAvailable | async">
<div class="option__top-container">
<div class="icon-wrapper">
<ui-icon icon="truck_b2b" class="truck_b2b" name="truck_b2b_Icon" alt="truck_b2b"></ui-icon>
</div>
<h2>B2B Versand</h2>
<span class="description description-delivery">Als B2B Kunde können wir Ihnen den Artikel auch liefern.</span>
<span class="price price-order">{{ deliverPrice | bookPrice }} {{ currency }}</span>
<div class="delivery">
Versandkostenfrei
</div>
<span class="delivery-date">
Versanddatum <span>{{ b2bDeliveryDate | date: 'dd.MM.yy' }}</span>
</span>
<div class="button-wrapper">
<app-button [primary]="true" [outline]="true" class="modal-btn" (action)="selectedAction(deliveryOptions.deliveryB2b)"
>Auswählen</app-button
>
</div>
</div>
<span class="delivery-date">Versanddatum {{ currentDeliveryDate }}</span>
<span class="price price-order">{{ deliverPrice | bookPrice }} {{ currency }}</span>
<app-button [primary]="true" [outline]="true" class="modal-btn" (action)="selectedAction(deliveryOptions.delivery)"
>Auswählen</app-button
>
</div>
</div>
</div>

View File

@@ -2,7 +2,7 @@
.modal-step-1 {
font-family: 'Open Sans';
line-height: 21px;
margin: 16px 0;
padding: 19px 19px 30px;
display: flex;
align-items: center;
flex-direction: column;
@@ -10,13 +10,14 @@
h1 {
font-size: 20px;
font-weight: bold;
margin: 0;
}
.header {
.close-icon {
position: absolute;
top: 25px;
right: 25px;
top: 19px;
right: 19px;
}
.close-icon:hover {
@@ -27,11 +28,11 @@
// FIRST STEP DESIGN
.modal-step-1 {
height: 479px;
height: 430px;
justify-content: center;
.header {
padding-top: 20px;
padding-top: 19px;
}
.body {
@@ -44,57 +45,35 @@
.option {
display: flex;
justify-content: center;
justify-content: space-between;
align-items: center;
flex-direction: column;
height: 100%;
width: 202px;
&__top-container {
margin-top: 48px;
}
&__content-container {
margin-top: 7px;
}
.description {
font-size: 16px;
max-width: 193px;
margin-bottom: 25px;
&-take-away {
position: relative;
top: 9px;
}
&-take-now {
position: relative;
top: -19px;
}
&-delivery {
position: relative;
top: 1px;
}
line-height: 21px;
margin: 10px 0;
width: 193px;
display: block;
margin-left: auto;
margin-right: auto;
}
h2 {
font-size: 26px;
font-weight: bold;
position: absolute;
top: 156px;
}
.title-take-now {
width: 153px;
line-height: 35px;
top: 149px;
}
.img {
display: block;
position: absolute;
top: 112px;
}
.img-now {
top: 108px;
}
.img-truck {
top: 117px;
line-height: 36px;
margin: 15px 0 0;
}
.dropdown-icon {
@@ -107,48 +86,33 @@
}
.price {
font-size: 20px;
font-size: 16px;
font-weight: bold;
&-take-away {
position: relative;
top: 28px;
}
&-take-now {
position: relative;
top: 57px;
}
&-order {
position: relative;
top: 33px;
}
}
.modal-btn {
position: absolute;
bottom: 25px;
line-height: 27px;
margin-bottom: 24px;
display: block;
}
.delivery {
display: flex;
justify-content: flex-start;
justify-content: center;
align-items: center;
width: 180px;
font-size: 16px;
text-align: left;
position: relative;
top: -2px;
padding: 5px 0;
padding-left: 6px;
margin-bottom: 6px;
}
&-date {
font-size: 16px;
.delivery-date {
font-size: 18px;
width: 200px;
span {
font-weight: bold;
width: 187px;
position: relative;
top: -1px;
left: 2px;
}
}
@@ -160,28 +124,45 @@
display: block;
}
&-date-take-away {
font-size: 16px;
font-weight: bold;
cursor: default;
width: 180px;
text-align: left;
padding-top: 3px;
position: relative;
left: 3px;
top: -7px;
}
&-dropdown {
display: flex;
justify-content: center;
font-size: 16px;
font-weight: 600;
line-height: 21px;
font-size: 20px;
font-weight: bold;
line-height: 24px;
position: relative;
min-width: 165px;
margin-bottom: 6px;
::ng-deep .dropdown-options {
top: -30px !important;
}
}
}
.location-date {
font-size: 18px;
span {
font-weight: bold;
}
}
.button-wrapper {
margin-top: 25px;
}
.truck_b2b {
@apply text-ucla-blue;
font-size: 5rem;
height: 37px;
margin-top: -20px;
}
.icon-wrapper {
@apply flex flex-row justify-center;
margin-bottom: 38px;
}
}
}
}

View File

@@ -62,6 +62,7 @@ export class ChangeOrderTypeComponent implements OnInit, OnDestroy {
deliveryType = '';
deliveryOptions = {
delivery: DeliveryOption.DELIVERY,
deliveryB2b: DeliveryOption.DELIVERY_B2B,
now: DeliveryOption.TAKE_NOW,
pick_up: DeliveryOption.PICK_UP,
};
@@ -113,6 +114,10 @@ export class ChangeOrderTypeComponent implements OnInit, OnDestroy {
return of(false);
}
get b2bDeliveryDate() {
return this.availability.find((t) => t.type === CheckoutType.deliveryB2b && allowedAvailabilityStatusCodes(t.status))?.av?.at;
}
get book() {
return this._item.book;
}
@@ -292,9 +297,26 @@ export class ChangeOrderTypeComponent implements OnInit, OnDestroy {
})
);
const shippingB2BAvailability = this.productAvailabilityService
.getShippingB2BAvailability(item, ean, branch.id)
.pipe(takeUntil(this.destroy$))
.pipe(
map((resp: any) => {
if (resp.error) {
return undefined;
}
return resp as {
branchId: number;
type: CheckoutType;
av: AvailabilityDTO[];
};
})
);
availabilityObservables.push(storeAvailability);
availabilityObservables.push(shippingAvailability);
availabilityObservables.push(takeNowAvailability);
availabilityObservables.push(shippingB2BAvailability);
return availabilityObservables;
}
@@ -422,9 +444,12 @@ export class ChangeOrderTypeComponent implements OnInit, OnDestroy {
} else if (action === DeliveryOption.PICK_UP) {
this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.store).av;
this.deliveryType = DeliveryOption.PICK_UP;
} else {
} else if (action === DeliveryOption.DELIVERY) {
this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.delivery).av;
this.deliveryType = DeliveryOption.DELIVERY;
} else {
this.deliveryType = DeliveryOption.DELIVERY_B2B;
this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.deliveryB2b).av;
}
this.store.dispatch(

View File

@@ -98,7 +98,8 @@
<div class="modal-step-2" *ngIf="!stepOne || isDownload">
<div class="header">
<h1>Artikel wurde dem Warenkorb hinzugefügt</h1>
<h1 *ngIf="canAddItem">Artikel wurde dem Warenkorb hinzugefügt</h1>
<h1 *ngIf="!canAddItem">Artikel kann dem Warenkorb nicht hinzugefügt werden</h1>
<lib-icon (click)="closeModal()" class="close-icon" height="21px" name="close" alt="close"></lib-icon>
</div>
<div class="modal-body">
@@ -123,8 +124,10 @@
<div class="product-information">
<span class="book-title">{{ book.product.contributors }} - {{ book.product.name }}</span>
<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>

View File

@@ -172,7 +172,7 @@
// SECOND STEP DESIGN
.modal-step-2 {
height: 420px;
height: auto;
justify-content: flex-start;
.header {
@@ -244,7 +244,15 @@
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin-bottom: 25px;
}
.book-format {
margin-top: 25px;
}
.can-add-hint {
@apply text-dark-goldenrod text-card-sub;
margin-top: 8px;
}
.order-book-icon {
@@ -327,7 +335,6 @@
align-items: center;
width: 93%;
margin-top: 25px;
margin-bottom: 20px;
}
.img-truck-b2b {

View File

@@ -88,6 +88,8 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
confirmationBtnText = 'Fortfahren';
canAddItem: boolean = true;
// Trigger other functionality if needed
@Output() closed: EventEmitter<boolean> = new EventEmitter();
@@ -513,13 +515,13 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
map((response) => {
if (response && response.av) {
const preferredSupplier = response.av.find((av) => av && av.preferred === 1);
if (preferredSupplier) {
this.currentAvailability = preferredSupplier;
this.checkCanAdd();
this._pikUpPrice = preferredSupplier.price.value.value;
}
this.shoppingCartService.canAddItem(undefined, this.currentAvailability);
const eligibleAvailabilities = response.av
.filter((availability) => allowedAvailabilityStatusCodes(availability.status) && availability.qty)
.map((availability) => availability.qty);
@@ -546,6 +548,7 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
let preferredQty = 0;
if (preferedAv) {
this.currentAvailability = preferedAv;
this.checkCanAdd();
this._deliveryPrice = preferedAv.price.value.value;
if (allowedAvailabilityStatusCodes(preferedAv.status)) {
preferredQty = preferedAv.qty;
@@ -564,14 +567,43 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
return shippingAvailability;
} else if (this.deliveryType === DeliveryOption.DELIVERY_B2B) {
// TODO: Logik für Verfügbarkeit prüfen
const shippingB2bAvailability = await this.productAvailabilityService
.getShippingB2BAvailability(this.book, this.book.product.ean, +this.selectedBranch.branchNumber, numberOfItems)
.pipe(
map((response) => {
if (response && response.av) {
const preferedAv = response.av.find((t) => t && t.preferred === 1);
let preferredQty = 0;
if (preferedAv) {
this.currentAvailability = preferedAv;
this.checkCanAdd();
this._deliveryPrice = preferedAv.price.value.value;
if (allowedAvailabilityStatusCodes(preferedAv.status)) {
preferredQty = preferedAv.qty;
}
this.detectChanges();
}
if (preferredQty) {
return preferredQty;
}
return 0;
}
return 0;
})
)
.toPromise();
return shippingB2bAvailability;
}
this.shoppingCartService
.canAddItem(undefined, this.currentAvailability)
.then((result) => this.uiModal.open({ content: UiDebugModalComponent, data: { canAddItem: result } }));
return of(numberOfItems).toPromise();
}
async checkCanAdd() {
// TODO: Hindernissmeldung
this.canAddItem = await this.shoppingCartService.canAddItem(undefined, this.currentAvailability);
}
updateCart() {
this.store.dispatch(
new SetCartEntry(

View File

@@ -9,9 +9,19 @@ import { OLAAvailabilityDTO, StoreCheckoutService } from '@swagger/checkout';
import { CustomerSelectors } from '../core/store/selectors/customer.selectors';
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 {
constructor(private store: Store, private checkoutService: StoreCheckoutService, private applicationService: ApplicationService) {}
constructor(
private store: Store,
private checkoutService: StoreCheckoutService,
private applicationService: ApplicationService,
private customerService: CrmCustomerService,
private uiModal: UiModalService
) {}
getItems(processId: number): Observable<CartItem[]> {
return this.store.select(SharedSelectors.getCart).pipe(
@@ -41,12 +51,12 @@ export class CartRefactImp implements CartService {
);
}
getCardId(processId: number): Observable<number> {
getCartId(processId: number): Observable<number> {
return this.store.select(ProcessSelectors.getCurrentProcess).pipe(map((process) => process.cartId));
}
getRequiredCustomerTypes(processId: number): Observable<any> {
// return this.getCardId(processId).pipe(
// return this.getCartId(processId).pipe(
// switchMap((cartId) => {
// return this.checkoutService
// .StoreCheckoutCanAddBuyer({ shoppingCartId: cartId, payload: { customerFeatures: {} } })
@@ -94,37 +104,43 @@ export class CartRefactImp implements CartService {
}
async canAddItem(processId: number, availability: OLAAvailabilityDTO): Promise<boolean> {
const customer$ = await this.store.select(CustomerSelectors.getActiveUser).pipe(first()).toPromise();
const features$ = await this.store.select(CustomerSelectors.getCustomerFeatures).pipe(first()).toPromise();
const getCustomerIdByProcessIdFn = await this.store.select(CustomerSelectors.getActiveUser).pipe(first()).toPromise();
console.log({ processId }, { activatedProcessId: this.applicationService.activatedProcessId });
let customerId: number;
try {
customerId = getCustomerIdByProcessIdFn(processId || this.applicationService.activatedProcessId)?.id;
} catch (error) {}
const customerId = customer$(processId || this.applicationService.activatedProcessId)?.id;
console.log({ customerId });
let customer: CustomerDTO;
if (customerId) {
customer = await this.customerService
.getCustomer(customerId)
.pipe(map((res) => res.result))
.toPromise();
}
const customerFeatures: StringDictionary<string> = {};
if (customerId) {
const features = features$(customerId);
console.log({ features });
for (const feature of features) {
for (const feature of customer.features) {
customerFeatures[feature.key] = feature.key;
}
}
this.getCardId(processId || this.applicationService.activatedProcessId)
return this.getCartId(processId || this.applicationService.activatedProcessId)
.pipe(
first(),
switchMap((cartId) => {
return this.checkoutService
.StoreCheckoutCanAddItem({ shoppingCartId: cartId, payload: { customerFeatures, availabilities: [availability] } })
.pipe(tap((res) => console.log(res)));
.pipe(
map((res) => {
this.uiModal.open({ content: UiDebugModalComponent, data: res });
return res.result.ok;
})
);
})
)
.toPromise();
return false;
}
}

View File

@@ -5,9 +5,10 @@ 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, mergeMap } from 'rxjs/operators';
import { first, map, mergeMap } from 'rxjs/operators';
import {
RefactSetCustomer,
RefactSetCustomerBillingAddress,
@@ -18,11 +19,16 @@ import { CustomerSelectors } from '../core/store/selectors/customer.selectors';
@Injectable()
export class CheckoutRefactImp implements CheckoutService {
constructor(private store: Store, private checkoutService: StoreCheckoutService, private cartService: CartService) {}
constructor(
private store: Store,
private checkoutService: StoreCheckoutService,
private cartService: CartService,
private uiModal: UiModalService
) {}
async setCustomer(processId: number, customer: CustomerDTO): Promise<boolean> {
const response = await this.cartService
.getCardId(processId)
.getCartId(processId)
.pipe(
first(),
mergeMap((shoppingCartId) => {
@@ -39,7 +45,12 @@ export class CheckoutRefactImp implements CheckoutService {
this.store.dispatch(new SetActiveCustomer(customer.id));
this.store.dispatch(new RefactSetCustomer(customer));
}
return response.result as any;
if (!response.result.ok) {
this.uiModal.open({ content: UiDebugModalComponent, data: response });
}
return response.result.ok;
}
getCustomer(processId: number): Observable<CustomerDTO> {

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -3,7 +3,7 @@ import { UiModalRef } from './defs';
@Component({
selector: 'ui-debug-modal',
template: `{{ modalRef.data | json }}`,
template: `<pre> {{ modalRef.data | json }} </pre>`,
})
export class UiDebugModalComponent implements OnInit {
constructor(public modalRef: UiModalRef) {}

View File

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -79,6 +79,7 @@ module.exports = {
'active-branch': '#596470',
'inactive-branch': '#9ca5b0',
'disabled-branch': '#c6cbd2',
'dark-goldenrod': '#be8100',
},
boxShadow: {
input: '0 6px 24px 0 rgba(214, 215, 217, 0.8)',