Merge branch 'develop' into prettier-and-vscode-extensions

This commit is contained in:
Lorenz Hilpert
2020-09-09 16:02:17 +02:00
418 changed files with 14645 additions and 3970 deletions

View File

@@ -27,10 +27,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"libs/ui/tsconfig.lib.json",
"libs/ui/tsconfig.spec.json"
],
"tsConfig": ["libs/ui/tsconfig.lib.json", "libs/ui/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -89,25 +86,7 @@
],
"serviceWorker": true
},
"development": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
],
"serviceWorker": true
}
"development": {}
}
},
"serve": {
@@ -148,20 +127,13 @@
"includePaths": ["apps/sales/src/scss"]
},
"scripts": [],
"assets": [
"apps/sales/src/favicon.ico",
"apps/sales/src/assets",
"apps/sales/src/manifest.webmanifest"
]
"assets": ["apps/sales/src/favicon.ico", "apps/sales/src/assets", "apps/sales/src/manifest.webmanifest"]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/sales/tsconfig.app.json",
"apps/sales/tsconfig.spec.json"
],
"tsConfig": ["apps/sales/tsconfig.app.json", "apps/sales/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -220,10 +192,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"libs/sso/tsconfig.lib.json",
"libs/sso/tsconfig.spec.json"
],
"tsConfig": ["libs/sso/tsconfig.lib.json", "libs/sso/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -253,10 +222,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/availability/tsconfig.lib.json",
"apps/swagger/availability/tsconfig.spec.json"
],
"tsConfig": ["apps/swagger/availability/tsconfig.lib.json", "apps/swagger/availability/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -286,10 +252,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/checkout/tsconfig.lib.json",
"apps/swagger/checkout/tsconfig.spec.json"
],
"tsConfig": ["apps/swagger/checkout/tsconfig.lib.json", "apps/swagger/checkout/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -319,10 +282,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/crm/tsconfig.lib.json",
"apps/swagger/crm/tsconfig.spec.json"
],
"tsConfig": ["apps/swagger/crm/tsconfig.lib.json", "apps/swagger/crm/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -352,10 +312,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/isa/tsconfig.lib.json",
"apps/swagger/isa/tsconfig.spec.json"
],
"tsConfig": ["apps/swagger/isa/tsconfig.lib.json", "apps/swagger/isa/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -385,10 +342,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/oms/tsconfig.lib.json",
"apps/swagger/oms/tsconfig.spec.json"
],
"tsConfig": ["apps/swagger/oms/tsconfig.lib.json", "apps/swagger/oms/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -418,10 +372,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/print/tsconfig.lib.json",
"apps/swagger/print/tsconfig.spec.json"
],
"tsConfig": ["apps/swagger/print/tsconfig.lib.json", "apps/swagger/print/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -451,10 +402,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/cat/tsconfig.lib.json",
"apps/swagger/cat/tsconfig.spec.json"
],
"tsConfig": ["apps/swagger/cat/tsconfig.lib.json", "apps/swagger/cat/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -484,10 +432,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/eis/tsconfig.lib.json",
"apps/swagger/eis/tsconfig.spec.json"
],
"tsConfig": ["apps/swagger/eis/tsconfig.lib.json", "apps/swagger/eis/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
@@ -517,10 +462,7 @@
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/native-container/tsconfig.lib.json",
"apps/native-container/tsconfig.spec.json"
],
"tsConfig": ["apps/native-container/tsconfig.lib.json", "apps/native-container/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}

View File

@@ -8,11 +8,10 @@ import { rootReducer } from './store/root.reducer';
import { EffectsModule } from '@ngrx/effects';
import { SearchEffects } from './store/customer';
import { HistoryEffects } from '@shelf-store/history';
import { DetailsEffects } from '@shelf-store/details';
// TODO: In Service Speichern
export function storeInLocalStorage(
reducer: ActionReducer<any>
): ActionReducer<any> {
export function storeInLocalStorage(reducer: ActionReducer<any>): ActionReducer<any> {
const lsKey = 'ISA_NGRX_STATE';
return function (state, action) {
@@ -28,14 +27,12 @@ export function storeInLocalStorage(
};
}
export const metaReducers: MetaReducer<RootState>[] = !environment.production
? [storeFreeze, storeInLocalStorage]
: [storeInLocalStorage];
export const metaReducers: MetaReducer<RootState>[] = !environment.production ? [storeFreeze, storeInLocalStorage] : [storeInLocalStorage];
@NgModule({
imports: [
StoreModule.forRoot(rootReducer, { metaReducers }),
EffectsModule.forRoot([SearchEffects, HistoryEffects]),
EffectsModule.forRoot([SearchEffects, HistoryEffects, DetailsEffects]),
StoreDevtoolsModule.instrument({ name: 'ISA Ngrx Store' }),
],
})

View File

@@ -55,6 +55,7 @@ import { AppStoreModule } from './app-store.module';
import { ModalDialogueModule } from './core/overlay/component';
import { AppModalModule } from './app-modal.module';
import { OverlaysModule } from './core/overlay/overlays.module';
import { DateAdapter, NativeDateAdapter } from '@isa-ui/core/date';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
@@ -158,6 +159,10 @@ export function remissionModuleOptionsFactory(config: AppConfiguration): Remissi
provide: SsoInterface,
useClass: SsoConfigurationService,
},
{
provide: DateAdapter,
useClass: NativeDateAdapter,
},
],
bootstrap: [AppComponent],
})

View File

@@ -6,13 +6,16 @@ import {
ChangeDetectionStrategy,
ViewChild,
ElementRef,
AfterViewInit
AfterViewInit,
} from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { Breadcrumb } from '../../core/models/breadcrumb.model';
import { Process } from '../../core/models/process.model';
import { Store, Select } from '@ngxs/store';
import { ChangeCurrentRoute, ResetProcessProductFilters } from '../../core/store/actions/process.actions';
import {
ChangeCurrentRoute,
ResetProcessProductFilters,
} from '../../core/store/actions/process.actions';
import { Router } from '@angular/router';
import { takeUntil, switchMap, distinctUntilChanged } from 'rxjs/operators';
import { SharedSelectors } from '../../core/store/selectors/shared.selectors';
@@ -21,8 +24,6 @@ import { AppService } from '../../core/services/app.service';
import { ModuleSwitcher } from '../../core/models/app-switcher.enum';
import { BranchProcess } from '../../core/models/branch-process.model';
import { SetBranchProcessCurrentPath } from '../../core/store/actions/branch-process.actions';
import { SetRemissionStarted, ResetRemissionShippingDocument } from '../../core/store/actions/remission.actions';
import { RemissionSelectors } from '../../core/store/selectors/remission.selectors';
import { AppState } from '../../core/store/state/app.state';
import { FILIALE_LANDING_PAGE } from '../../core/utils/app.constants';
@@ -30,12 +31,17 @@ import { FILIALE_LANDING_PAGE } from '../../core/utils/app.constants';
selector: 'app-breadcrumbs',
templateUrl: './breadcrumbs.component.html',
styleUrls: ['./breadcrumbs.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild('container', { read: ElementRef, static: false }) public container: ElementRef<any>;
@Select(SharedSelectors.getBreadcrumbs) breadcrumbs$: Observable<Breadcrumb[]>;
@Select(SharedSelectors.getCurrentProcess) currentProcess$: Observable<Process>;
@ViewChild('container', { read: ElementRef, static: false })
public container: ElementRef<any>;
@Select(SharedSelectors.getBreadcrumbs) breadcrumbs$: Observable<
Breadcrumb[]
>;
@Select(SharedSelectors.getCurrentProcess) currentProcess$: Observable<
Process
>;
module: ModuleSwitcher;
destroy$ = new Subject();
breadcrumbs: Breadcrumb[] = [];
@@ -65,7 +71,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
}
get backArrow() {
return this.router.url.substring(0, 16) === '/product/details' && this.breadsCount > 1;
return (
this.router.url.substring(0, 16) === '/product/details' &&
this.breadsCount > 1
);
}
get selectedBreadCrumbIndex() {
@@ -78,7 +87,7 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
const endOfRouteWithOutParams = this.currentRoute.indexOf('?');
route = this.currentRoute.substring(0, endOfRouteWithOutParams);
}
return this.breadcrumbs.findIndex(t => t && t.path.indexOf(route) >= 0);
return this.breadcrumbs.findIndex((t) => t && t.path.indexOf(route) >= 0);
}
get lowerMargin() {
@@ -94,7 +103,12 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
return false;
}
constructor(private store: Store, private router: Router, private cdrf: ChangeDetectorRef, private appService: AppService) { }
constructor(
private store: Store,
private router: Router,
private cdrf: ChangeDetectorRef,
private appService: AppService
) {}
getBreadcrumbsFromCurentProcess() {
this.currentProcess$
@@ -106,7 +120,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
}
setTimeout(() => {
this.container.nativeElement.scrollTo({ left: this.container.nativeElement.scrollWidth, behavior: 'smooth' });
this.container.nativeElement.scrollTo({
left: this.container.nativeElement.scrollWidth,
behavior: 'smooth',
});
}, 400);
return this.breadcrumbs$;
}),
@@ -118,16 +135,24 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
this.showBackNavigationArrow = false;
if (breadcrumbs) {
const breadName =
breadcrumbs.length > 0 ? (breadcrumbs[breadcrumbs.length - 1] ? breadcrumbs[breadcrumbs.length - 1].name : '') : '';
this.showBack = !(breadName === 'Bestellbestätigung' || breadName === 'Remission');
breadcrumbs.length > 0
? breadcrumbs[breadcrumbs.length - 1]
? breadcrumbs[breadcrumbs.length - 1].name
: ''
: '';
this.showBack = !(
breadName === 'Bestellbestätigung' || breadName === 'Remission'
);
this.breadcrumbs = breadcrumbs;
this.breadsCount = Object.keys(this.breadcrumbs) ? Object.keys(this.breadcrumbs).length : 0;
this.breadsCount = Object.keys(this.breadcrumbs)
? Object.keys(this.breadcrumbs).length
: 0;
this.cdrf.detectChanges();
if (!this.isIPad) {
setTimeout(() => {
try {
this.initNavigationalArrow();
} catch (error) { }
} catch (error) {}
}, 1000);
}
}
@@ -136,16 +161,6 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
selectBreadcrumb(breadcrumb: Breadcrumb) {
if (breadcrumb && breadcrumb.path) {
if (breadcrumb.path === '/remission/create') {
const shipingDocumentProductCount = this.store.selectSnapshot(RemissionSelectors.getRemissionShippingDocumentProductCount);
if (shipingDocumentProductCount === 0) {
this.store.dispatch(new SetRemissionStarted(false));
this.store.dispatch(new ResetRemissionShippingDocument());
} else {
this.appService.openStartedRemissionLeavingDialog();
return;
}
}
if (breadcrumb.path === '/product/search') {
this.store.dispatch(new ResetFilters());
this.store.dispatch(new ResetProcessProductFilters());
@@ -153,9 +168,13 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
if (this.module === ModuleSwitcher.Customer) {
this.store.dispatch(new ChangeCurrentRoute(breadcrumb.path, true));
} else {
this.store.dispatch(new SetBranchProcessCurrentPath(breadcrumb.path, true));
this.store.dispatch(
new SetBranchProcessCurrentPath(breadcrumb.path, true)
);
}
this.router.navigate([breadcrumb.path], { queryParams: breadcrumb.queryParams ? breadcrumb.queryParams : {} });
this.router.navigate([breadcrumb.path], {
queryParams: breadcrumb.queryParams ? breadcrumb.queryParams : {},
});
}
}
@@ -165,7 +184,9 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
this.store.dispatch(new ChangeCurrentRoute(breadcrumb.path, false));
this.router.navigate(['/product/results']);
} else {
this.store.dispatch(new SetBranchProcessCurrentPath(breadcrumb.path, false));
this.store.dispatch(
new SetBranchProcessCurrentPath(breadcrumb.path, false)
);
this.router.navigate([FILIALE_LANDING_PAGE]);
}
}
@@ -188,7 +209,7 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
setTimeout(() => {
try {
this.initNavigationalArrow();
} catch (error) { }
} catch (error) {}
}, 100);
}
}
@@ -200,7 +221,7 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
addOne() {
this.breadcrumbs.push({
name: 'test' + (this.breadcrumbs ? this.breadcrumbs.length : 0),
path: './i'
path: './i',
});
}
@@ -215,7 +236,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
elementProcessed++;
childrenWidth += this.container.nativeElement.children[i].offsetWidth;
}
if (breadcrumbs === elementProcessed && containerWidth <= childrenWidth) {
if (
breadcrumbs === elementProcessed &&
containerWidth <= childrenWidth
) {
this.showBackNavigationArrow = true;
this.cdrf.detectChanges();
} else {
@@ -233,7 +257,11 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
hideBackNavArrow(event) {
if (event) {
const e = event.toElement || event.relatedTarget;
if (e && e.classList && (e.classList[0] === 'icon' || e.classList[0] === 'ng-star-inserted')) {
if (
e &&
e.classList &&
(e.classList[0] === 'icon' || e.classList[0] === 'ng-star-inserted')
) {
return;
}
this.showBackArrow = false;
@@ -251,7 +279,11 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
hideForwardNavArrow(event) {
if (event) {
const e = event.toElement || event.relatedTarget;
if (e && e.classList && (e.classList[0] === 'icon' || e.classList[0] === 'ng-star-inserted')) {
if (
e &&
e.classList &&
(e.classList[0] === 'icon' || e.classList[0] === 'ng-star-inserted')
) {
return;
}
this.showForrwardArrow = false;
@@ -267,7 +299,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
if (scrollLeft <= 121) {
this.showBackNavigationArrow = false;
}
this.container.nativeElement.scrollTo({ left: scrollLeft - 120, behavior: 'smooth' });
this.container.nativeElement.scrollTo({
left: scrollLeft - 120,
behavior: 'smooth',
});
this.showForwardNavigationalArrow = true;
this.cdrf.detectChanges();
}
@@ -279,7 +314,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
if (scrollLeft + 119 + containerWidth <= scrollWidth) {
this.showForwardNavigationalArrow = false;
}
this.container.nativeElement.scrollTo({ left: scrollLeft + 120, behavior: 'smooth' });
this.container.nativeElement.scrollTo({
left: scrollLeft + 120,
behavior: 'smooth',
});
this.showBackNavigationArrow = true;
this.cdrf.detectChanges();
}

View File

@@ -3,7 +3,6 @@ import { Observable } from 'rxjs';
import { RemissionSelectors } from '../../core/store/selectors/remission.selectors';
import { Select } from '@ngxs/store';
import { ContentHeaderService } from '../../core/services/content-header.service';
import { shareReplay } from 'rxjs/operators';
@Component({
selector: 'app-content-header',

View File

@@ -4,17 +4,13 @@
<div class="menu-item-grid align-center active" (click)="routeToMenu('/product/search', 'productsearch')">
<div>
<lib-icon class="menu-icon" name="{{
router.url === '/product/search' ||
router.url.startsWith('/product/results') ||
router.url.startsWith('/product/details')
router.url === '/product/search' || router.url.startsWith('/product/results') || router.url.startsWith('/product/details')
? 'Icon_Artikelsuche'
: 'Icon_Artikelsuche_inactive'
}}" width="34px" height="24px"></lib-icon>
</div>
<span *ngIf="
router.url === '/product/search' ||
router.url.startsWith('/product/results') ||
router.url.startsWith('/product/details');
router.url === '/product/search' || router.url.startsWith('/product/results') || router.url.startsWith('/product/details');
else articleSearchLabelElse
" class="menu-item selected">Artikelsuche</span>
<ng-template #articleSearchLabelElse>
@@ -44,23 +40,20 @@
</ng-template>
</div>
<div class="menu-item-grid align-center active" (click)="routeToMenu('/shelf/search', 'shelfsearch')">
<!-- <div class="menu-item-grid align-center"> -->
<div>
<lib-icon class="menu-icon" name="{{
router.url === '/shelf/search' ||
router.url.startsWith('/shelf/results') ||
router.url.startsWith('/shelf/details')
router.url === '/shelf/search' || router.url.startsWith('/shelf/results') || router.url.startsWith('/shelf/details')
? 'Icon_Abholfach'
: 'Icon_Abholfach_inactive'
}}" width="34px" height="24px"></lib-icon>
</div>
<span *ngIf="
router.url === '/shelf/search' ||
router.url.startsWith('/shelf/results') ||
router.url.startsWith('/shelf/details');
router.url === '/shelf/search' || router.url.startsWith('/shelf/results') || router.url.startsWith('/shelf/details');
else shelfSearchLabelElse
" class="menu-item selected">Warenausgabe</span>
<ng-template #shelfSearchLabelElse>
<span class="menu-item">Warenausgabe</span>
<span class="menu-item ">Warenausgabe</span>
</ng-template>
</div>
</div>
@@ -68,7 +61,8 @@
<!-- Branch module menus -->
<div class="branch-menu" *ngIf="module === 1">
<div class="menu-grid">
<div class="menu-item-grid align-center active" (click)="routeToMenu('/branch/main', 'branchmain')">
<div class="menu-item-grid align-center">
<!-- <div class="menu-item-grid align-center" (click)="routeToMenu('/branch/main', 'branchmain')"> -->
<div>
<lib-icon class="menu-icon" name="{{
router.url === '/branch/main' ||
@@ -113,17 +107,11 @@
<div class="menu-item-grid align-center active" (click)="routeToMenu('/remission/create', 'remission')">
<div>
<lib-icon class="menu-icon" name="{{
router.url === '/remission/create' ||
router.url === '/remission/started'
? 'Icon_Remission'
: 'Icon_Remission_inactive'
router.url === '/remission/create' || router.url === '/remission/started' ? 'Icon_Remission' : 'Icon_Remission_inactive'
}}" width="34px" height="24px"></lib-icon>
</div>
<span *ngIf="
router.url === '/remission/create' ||
router.url === '/remission/started';
else remissionLabelElse
" class="branch-menu-item branch-selected">Remission</span>
<span *ngIf="router.url === '/remission/create' || router.url === '/remission/started'; else remissionLabelElse"
class="branch-menu-item branch-selected">Remission</span>
<ng-template #remissionLabelElse>
<span class="branch-menu-item">Remission</span>
</ng-template>

View File

@@ -1,11 +1,4 @@
import {
Component,
OnInit,
Output,
EventEmitter,
OnDestroy,
ViewChild
} from '@angular/core';
import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core';
import { PrinterService } from '../../core/services/printer.service';
import { ModalService, ButtonComponent } from '@libs/ui';
import { Subject } from 'rxjs';
@@ -16,7 +9,7 @@ import { PRINT_ERROR_MSG } from './printer-selection.constants';
@Component({
selector: 'app-printer-selection',
templateUrl: './printer-selection.component.html',
styleUrls: ['./printer-selection.component.scss']
styleUrls: ['./printer-selection.component.scss'],
})
export class PrinterSelectionComponent implements OnInit, OnDestroy {
id = 'printer-modal';
@@ -31,20 +24,16 @@ export class PrinterSelectionComponent implements OnInit, OnDestroy {
printingRequested = false;
submited = false;
@Output() print: EventEmitter<string> = new EventEmitter();
@Output() closed = new EventEmitter();
@ViewChild('printBtn', { static: false }) printBtn: ButtonComponent;
constructor(
private printerService: PrinterService,
private modalService: ModalService
) { }
constructor(private printerService: PrinterService, private modalService: ModalService, private cdr: ChangeDetectorRef) {}
ngOnInit() { }
ngOnInit() {}
printerSelected(value: string | number) {
this.selected = value;
this.selectedPrinterValue = this.printers.find(
t => t.text === this.selected
).key;
this.selectedPrinterValue = this.printers.find((t) => t.text === this.selected).key;
}
emitPrint() {
@@ -63,7 +52,7 @@ export class PrinterSelectionComponent implements OnInit, OnDestroy {
this.printerService
.getAvailablePrinters()
.pipe(takeUntil(this.destroy$))
.subscribe(response => {
.subscribe((response) => {
if ((response as { error: string }).error) {
const errorResponse = response as { error: string };
this.error = true;
@@ -71,17 +60,13 @@ export class PrinterSelectionComponent implements OnInit, OnDestroy {
return;
}
const result = response as Printer[];
this.printers = result.map(t => {
this.printers = result.map((t) => {
return { key: t.key, text: t.description, selected: t.selected };
});
const selectedPrinter = this.printers.find(printer => printer.selected);
this.options = this.printers.map(t => t.text);
this.selectedPrinterValue = selectedPrinter
? selectedPrinter.key
: this.printers[0].key;
this.selected = selectedPrinter
? selectedPrinter.text
: this.options[0];
const selectedPrinter = this.printers.find((printer) => printer.selected);
this.options = this.printers.map((t) => t.text);
this.selectedPrinterValue = selectedPrinter ? selectedPrinter.key : this.printers[0].key;
this.selected = selectedPrinter ? selectedPrinter.text : this.options[0];
this.error = false;
this.loaded = true;
if (this.printBtn) {
@@ -91,6 +76,7 @@ export class PrinterSelectionComponent implements OnInit, OnDestroy {
this.error = true;
this.errorMessage = 'No available printers';
}
this.cdr.markForCheck();
});
}
@@ -104,6 +90,7 @@ export class PrinterSelectionComponent implements OnInit, OnDestroy {
if (this.printBtn) {
this.printBtn.stopLoading();
}
this.closed.emit();
}
setError(errorMessage?: string) {

View File

@@ -0,0 +1,81 @@
import { Organisation, User } from '../models/user.model';
import { CustomerMapping } from './customer.mapping';
import { TestBed } from '@angular/core/testing';
import { DatePipe } from '@angular/common';
fdescribe('CustomerMapping', () => {
let mapper: CustomerMapping;
const mockUser: User = {
id: 123,
first_name: 'Vorname',
last_name: 'Nachname',
title: 'Dr',
gender: 'Herr',
email: 'test@test.de',
phone_number: '017012345678',
mobile_number: '019998765432',
organisation: {
name: 'Mock Org',
department: 'Mock Dept.',
vatId: '123',
},
delivery_addres: {
id: 456,
first_name: 'Vorname Addresse',
last_name: 'Nachname Addresse',
street: 'Test Strasse',
streetNo: 12,
zip: '80636',
city: 'Munich',
country: 'Deutschland',
},
};
beforeEach(() => {
TestBed.configureTestingModule({
providers: [DatePipe, CustomerMapping],
});
mapper = TestBed.get(CustomerMapping);
});
it('should be created', () => {
expect(mapper instanceof CustomerMapping).toBeTruthy();
});
describe('fromOrganisationToOrganisationDTO', () => {
const mockOrganisation: Organisation = mockUser.organisation;
it('should map the provided organisation (Organisation) to OrganisationDTO', () => {
const result = mapper.fromOrganisationToOrganisationDTO(mockOrganisation);
expect(result.vatId).toEqual(mockOrganisation.vatId);
expect(result.department).toEqual(mockOrganisation.department);
expect(result.name).toEqual(mockOrganisation.name);
});
});
describe('fromUserToCommunicationDetails', () => {
it('should map the provided user (User) to communication details (CommunicationDetailsDTO)', () => {
const result = mapper.fromUserToCommunicationDetails(mockUser);
expect(result.phone).toEqual(mockUser.phone_number);
expect(result.mobile).toEqual(mockUser.mobile_number);
expect(result.email).toEqual(mockUser.email);
});
});
describe('fromUserToShippingAddressDTO', () => {
it('should map the provided user (User) to shipping address (ShippingAddressDTO)', () => {
const result = mapper.fromUserToShippingAddressDTO(mockUser);
expect(result.title).toEqual(mockUser.delivery_addres.title);
expect(result.gender).toEqual(2);
expect(result.firstName).toEqual(mockUser.delivery_addres.first_name);
expect(result.lastName).toEqual(mockUser.delivery_addres.last_name);
expect(result.source).toEqual(mockUser.delivery_addres.id);
});
});
});

View File

@@ -9,12 +9,15 @@ import {
CommunicationDetailsDTO,
AssignedPayerDTO,
CustomerInfoDTO,
ShippingAddressDTO,
PayerDTO,
CustomerDTO,
ShippingAddressDTO,
} from '@swagger/crm';
import { NotificationChannels } from '../models/notification-channels.enum';
import { EntityDTOContainerOfBranchDTO } from '@swagger/checkout';
import {
EntityDTOContainerOfBranchDTO,
ShippingAddressDTO as CheckoutShippingAddressDTO,
} from '@swagger/checkout';
import { DatePipe } from '@angular/common';
import { BACKEND_API_TIMESTAMP_FORMAT } from '../utils/app.formats';
import { KeyValueDTOOfStringAndString } from '@swagger/cat';
@@ -60,7 +63,9 @@ export class CustomerMapping {
zipCode: customer.delivery_addres.zip + '',
street: customer.delivery_addres.street,
streetNumber: customer.delivery_addres.streetNo + '',
info: customer.delivery_addres.note ? customer.delivery_addres.note : null,
info: customer.delivery_addres.note
? customer.delivery_addres.note
: null,
};
}
@@ -71,7 +76,9 @@ export class CustomerMapping {
zipCode: customer.invoice_address.zip + '',
street: customer.invoice_address.street,
streetNumber: customer.invoice_address.streetNo + '',
info: customer.invoice_address.note ? customer.invoice_address.note : null,
info: customer.invoice_address.note
? customer.invoice_address.note
: null,
};
}
@@ -84,14 +91,20 @@ export class CustomerMapping {
};
}
if (Array.isArray(customer.poossible_delivery_addresses) && customer.poossible_delivery_addresses.length > 0) {
if (
Array.isArray(customer.poossible_delivery_addresses) &&
customer.poossible_delivery_addresses.length > 0
) {
customer.poossible_delivery_addresses.forEach((adrs: Address) => {
addresses.push(this.addressesItem(adrs));
shippingAddresses.push(this.addressesItem(adrs));
});
}
if (Array.isArray(customer.poossible_invoice_addresses) && customer.poossible_invoice_addresses.length > 0) {
if (
Array.isArray(customer.poossible_invoice_addresses) &&
customer.poossible_invoice_addresses.length > 0
) {
customer.poossible_invoice_addresses.forEach((adrs: Address) => {
if (adrs) {
addresses.push(this.addressesItem(adrs));
@@ -178,7 +191,9 @@ export class CustomerMapping {
}
}
private addressesItem(address: Address): EntityDTOContainerOfShippingAddressDTO {
private addressesItem(
address: Address
): EntityDTOContainerOfShippingAddressDTO {
if (!address) {
return;
}
@@ -214,7 +229,9 @@ export class CustomerMapping {
customerInfoDTOtoUser(customerInfo: CustomerInfoDTO): User {
if (isNullOrUndefined(customerInfo)) {
throw new Error('argument customerInfo:CustomerInfoDTO is null or undefined.');
throw new Error(
'argument customerInfo:CustomerInfoDTO is null or undefined.'
);
}
const id = customerInfo.id;
@@ -252,18 +269,22 @@ export class CustomerMapping {
customerInfo.notificationChannels === NotificationChannels.EmailSms;
}
let address: Address;
let baseAddres: Address = {} as Address;
if (!!customerInfo.address) {
address = {
baseAddres = {
id: Date.now() + Math.random(),
title: customerInfo.title,
first_name: customerInfo.firstName,
last_name: customerInfo.lastName,
city: customerInfo.address.city ? customerInfo.address.city : undefined,
zip: customerInfo.address.zipCode ? customerInfo.address.zipCode : undefined,
zip: customerInfo.address.zipCode
? customerInfo.address.zipCode
: undefined,
country: customerInfo.address.country,
street: customerInfo.address.street,
streetNo: customerInfo.address.streetNumber ? +customerInfo.address.streetNumber : undefined,
streetNo: customerInfo.address.streetNumber
? +customerInfo.address.streetNumber
: undefined,
};
}
@@ -310,7 +331,8 @@ export class CustomerMapping {
payement_method: null, // missing info from API
tolino: false, // missing info from API
title: null, // missing info from API
delivery_addres: address,
base_addres: baseAddres,
hasOnlineAccount: customerInfo.hasOnlineAccount,
isGuestAccount: customerInfo.isGuestAccount,
features: features,
changed: customerInfo.changed,
@@ -334,7 +356,9 @@ export class CustomerMapping {
customerDTOtoUser(customerDto: CustomerDTO): User {
if (isNullOrUndefined(customerDto)) {
throw new Error('argument customerDto:CustomerInfoDTO is null or undefined.');
throw new Error(
'argument customerDto:CustomerInfoDTO is null or undefined.'
);
}
const id = customerDto.id;
@@ -365,15 +389,16 @@ export class CustomerMapping {
let notificationEmail = false;
if (customerDto.notificationChannels) {
notificationSms =
customerDto.notificationChannels === NotificationChannels.Sms || customerDto.notificationChannels === NotificationChannels.EmailSms;
customerDto.notificationChannels === NotificationChannels.Sms ||
customerDto.notificationChannels === NotificationChannels.EmailSms;
notificationEmail =
customerDto.notificationChannels === NotificationChannels.Email ||
customerDto.notificationChannels === NotificationChannels.EmailSms;
}
let address: Address;
let baseAddres: Address;
if (!!customerDto.address) {
address = {
baseAddres = {
id: Date.now() + Math.random(),
title: customerDto.title,
first_name: customerDto.firstName,
@@ -422,7 +447,9 @@ export class CustomerMapping {
mobile_number: mobileNumber,
newUser: newUser,
customer_card: false,
createdInBranch: customerDto.createdInBranch ? customerDto.createdInBranch.id : null,
createdInBranch: customerDto.createdInBranch
? customerDto.createdInBranch.id
: null,
newsletter: false, // missing info from API
shop: false,
notificationSms: notificationSms,
@@ -430,21 +457,22 @@ export class CustomerMapping {
payement_method: null, // missing info from API
tolino: false, // missing info from API
title: null, // missing info from API
delivery_addres: address,
invoice_address: address,
poossible_delivery_addresses: [address],
poossible_invoice_addresses: [address],
features: features,
changed: customerDto.changed,
created: customerDto.created,
customerNumber: customerDto.customerNumber,
organisation: organisation,
hasOnlineAccount: customerDto.hasOnlineAccount,
};
}
fromShippingAddressDtoToAddress(shippingAddress: ShippingAddressDTO): Address {
fromShippingAddressDtoToAddress(
shippingAddress: ShippingAddressDTO
): Address {
if (isNullOrUndefined(shippingAddress)) {
throw new Error('argument shippingAddress:ShippingAddressDTO is null or undefined.');
throw new Error(
'argument shippingAddress:ShippingAddressDTO is null or undefined.'
);
}
let gender = '';
@@ -497,7 +525,10 @@ export class CustomerMapping {
};
}
fromAddressToShippingAddressDTO(address: Address, forUpdate: boolean): ShippingAddressDTO {
fromAddressToShippingAddressDTO(
address: Address,
forUpdate: boolean
): ShippingAddressDTO {
if (isNullOrUndefined(address)) {
throw new Error('argument address:Address is null or undefined.');
}
@@ -529,7 +560,10 @@ export class CustomerMapping {
lastName: address.last_name,
gender: this.getGender(address.gender),
status: 1,
isDefault: this.datePipe.transform(new Date(), BACKEND_API_TIMESTAMP_FORMAT),
isDefault: this.datePipe.transform(
new Date(),
BACKEND_API_TIMESTAMP_FORMAT
),
organisation: <OrganisationDTO>{
name: address.company_name,
department: address.company_department,
@@ -584,7 +618,10 @@ export class CustomerMapping {
lastName: address.last_name,
gender: this.getGender(address.gender),
status: 1,
isDefault: this.datePipe.transform(new Date(), BACKEND_API_TIMESTAMP_FORMAT),
isDefault: this.datePipe.transform(
new Date(),
BACKEND_API_TIMESTAMP_FORMAT
),
address: _address,
payerNumber: address.payer_number,
payerStatus: address.payer_status,
@@ -599,7 +636,9 @@ export class CustomerMapping {
fromAssignedPayerDtoToAddress(assignedPayers: AssignedPayerDTO): Address {
if (isNullOrUndefined(assignedPayers)) {
throw new Error('argument shippingAddress:ShippingAddressDTO is null or undefined.');
throw new Error(
'argument shippingAddress:ShippingAddressDTO is null or undefined.'
);
}
let id = null;
@@ -612,7 +651,11 @@ export class CustomerMapping {
}
let gender = '';
if (!!assignedPayers.payer && !!assignedPayers.payer.data && !!assignedPayers.payer.data.gender) {
if (
!!assignedPayers.payer &&
!!assignedPayers.payer.data &&
!!assignedPayers.payer.data.gender
) {
gender = this.getGenderFromCode(assignedPayers.payer.data.gender);
}
@@ -621,7 +664,11 @@ export class CustomerMapping {
let street = '';
let streetNo = null;
let zip: string = null;
if (!!assignedPayers.payer && !!assignedPayers.payer.data && !!assignedPayers.payer.data.address) {
if (
!!assignedPayers.payer &&
!!assignedPayers.payer.data &&
!!assignedPayers.payer.data.address
) {
country = assignedPayers.payer.data.address.country;
city = assignedPayers.payer.data.address.city;
street = assignedPayers.payer.data.address.street;
@@ -643,7 +690,11 @@ export class CustomerMapping {
let company_name = null;
let company_department = null;
let company_tax_number = null;
if (!!assignedPayers.payer && !!assignedPayers.payer.data && !!assignedPayers.payer.data.organisation) {
if (
!!assignedPayers.payer &&
!!assignedPayers.payer.data &&
!!assignedPayers.payer.data.organisation
) {
company_name = assignedPayers.payer.data.organisation.name;
company_department = assignedPayers.payer.data.organisation.department;
company_tax_number = assignedPayers.payer.data.organisation.vatId;
@@ -706,7 +757,9 @@ export class CustomerMapping {
zipCode: customer.delivery_addres.zip + '',
street: customer.delivery_addres.street,
streetNumber: customer.delivery_addres.streetNo + '',
info: customer.delivery_addres.note ? customer.delivery_addres.note : null,
info: customer.delivery_addres.note
? customer.delivery_addres.note
: null,
};
}
@@ -767,4 +820,44 @@ export class CustomerMapping {
? NotificationChannels.Sms
: NotificationChannels.NotSet;
}
fromOrganisationToOrganisationDTO(
organisation: Organisation
): OrganisationDTO {
if (!organisation) {
return {};
}
return {
name: organisation.name,
department: organisation.department,
vatId: organisation.vatId,
};
}
fromUserToCommunicationDetails(user: User): CommunicationDetailsDTO {
return {
phone: user.phone_number,
mobile: user.mobile_number,
email: user.email,
};
}
fromUserToShippingAddressDTO(user: User): CheckoutShippingAddressDTO {
const deliveryDetails = user.delivery_addres;
if (!deliveryDetails) {
throw Error('No Delivery Address set.');
}
return {
title: deliveryDetails.title,
gender: this.getGender(user.gender),
firstName: deliveryDetails.first_name,
lastName: deliveryDetails.last_name,
organisation: this.fromOrganisationToOrganisationDTO(user.organisation),
communicationDetails: this.fromUserToCommunicationDetails(user),
address: this.fromAddressToAddressDTO(deliveryDetails),
source: deliveryDetails.id,
};
}
}

View File

@@ -0,0 +1,9 @@
export interface ActionResult<T> {
error?: boolean;
errorReasons?: { [key: string]: string };
http?: {
code: number;
};
message?: string;
result?: T;
}

View File

@@ -12,6 +12,7 @@ export interface User {
email?: string;
mobile_number?: string;
phone_number?: string;
base_addres?: Address;
delivery_addres?: Address;
invoice_address?: Address;
shop?: boolean;
@@ -26,6 +27,7 @@ export interface User {
notificationEmail?: boolean;
createdInBranch?: number;
customerId?: string;
hasOnlineAccount?: boolean;
isGuestAccount?: boolean;
features?: Features[];
changed?: string;

View File

@@ -1,4 +1,5 @@
// start:ng42.barrel
export * from './overlay-close-event.model';
export * from './overlay-content.model';
export * from './modal-content.model';
// end:ng42.barrel

View File

@@ -0,0 +1 @@
export type OverlayContent<T> = T;

View File

@@ -5,5 +5,5 @@ export * from './isa-overlay-ref';
export * from './modal-dialog-data';
export * from './modal-service';
export * from './overlay.animations';
export * from './overlay-content.token';
// end:ng42.barrel

View File

@@ -0,0 +1,6 @@
import { OverlayContent } from './defs';
import { InjectionToken } from '@angular/core';
export const OVERLAY_DATA = new InjectionToken<
OverlayContent<{ queryString: string }>
>('OVERLAY_DATA');

View File

@@ -0,0 +1,12 @@
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class TransactionIdProvider {
private transactionId = 0;
constructor() {}
next() {
return this.transactionId++;
}
}

View File

@@ -14,6 +14,7 @@ import {
SupplierDTO,
QueryTokenDTO,
AutocompleteTokenDTO,
AbholfachService,
} from '@swagger/oms';
import { map, filter } from 'rxjs/operators';
import { Observable } from 'rxjs';
@@ -31,6 +32,7 @@ import { CollectingShelfSelectors } from '../store/selectors/collecting-shelf.se
export class CollectingShelfService {
constructor(
private omsService: OrderService,
private abholfach: AbholfachService,
private mapper: ShelfMapping,
public datepipe: DatePipe,
private receiptService: ReceiptService,
@@ -52,17 +54,13 @@ export class CollectingShelfService {
take: take,
};
return this.omsService.OrderWarenausgabe(params).pipe(
return this.abholfach.AbholfachWarenausgabe(params).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return {
result: this.mapper.fromOrderItemListItemDTOArrayToCollectingShelfOrder(
response.result,
this,
skip === 0
),
result: this.mapper.fromOrderItemListItemDTOArrayToCollectingShelfOrder(response.result, this, skip === 0),
hits: response.hits,
};
})
@@ -70,22 +68,17 @@ export class CollectingShelfService {
}
searchShelfCached() {
return this.store
.select(CollectingShelfSelectors.getCachedResults)
.pipe(filter((data) => !isNullOrUndefined(data)));
return this.store.select(CollectingShelfSelectors.getCachedResults).pipe(filter((data) => !isNullOrUndefined(data)));
}
searchShelfHasResults(
input: string,
branchNumber: string
): Observable<number> {
searchShelfHasResults(input: string, branchNumber: string): Observable<number> {
const params = <QueryTokenDTO>{
input: {
qs: input,
},
branchNumber,
};
return this.omsService.OrderWarenausgabe(params).pipe(
return this.abholfach.AbholfachWarenausgabe(params).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
@@ -113,7 +106,7 @@ export class CollectingShelfService {
hitsOnly,
branchNumber,
};
return this.omsService.OrderWarenausgabe(params).pipe(
return this.abholfach.AbholfachWarenausgabe(params).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
@@ -124,7 +117,7 @@ export class CollectingShelfService {
}
searchWarenausgabeAutocomplete(autocompleteTokenDTO: AutocompleteTokenDTO) {
return this.omsService.OrderWarenausgabeAutocomplete(autocompleteTokenDTO);
return this.abholfach.AbholfachWarenausgabeAutocomplete(autocompleteTokenDTO);
}
getOrderByOrderId(orderId: number): Observable<OrderDTO> {
@@ -153,9 +146,7 @@ export class CollectingShelfService {
);
}
getOrderItemSubsetByOrderItemSubsetId(
orderItemSubsetId: number
): Observable<OrderItemSubsetDTO> {
getOrderItemSubsetByOrderItemSubsetId(orderItemSubsetId: number): Observable<OrderItemSubsetDTO> {
return this.omsService.OrderGetOrderItemSubset(orderItemSubsetId).pipe(
map((response) => {
if (response.error) {
@@ -166,18 +157,13 @@ export class CollectingShelfService {
);
}
getOrdersByCompartmentNumber(
compartmentNumbers: string[],
orderId: number
): Observable<OrderDTO> {
getOrdersByCompartmentNumber(compartmentNumbers: string[], orderId: number): Observable<OrderDTO> {
return this.omsService.OrderGetOrdersByCompartment(compartmentNumbers).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return response.result
? response.result.find((o) => +o.id === +orderId)
: null;
return response.result ? response.result.find((o) => +o.id === +orderId) : null;
})
);
}
@@ -236,16 +222,8 @@ export class CollectingShelfService {
);
}
extendPickUpPeriod(
orderId: number,
orderItemId: number,
orderItemSubsetId: number,
date: Date
): Observable<OrderItemSubsetDTO> {
const compartmentStop = this.datepipe.transform(
date,
BACKEND_API_TIMESTAMP_FORMAT
);
extendPickUpPeriod(orderId: number, orderItemId: number, orderItemSubsetId: number, date: Date): Observable<OrderItemSubsetDTO> {
const compartmentStop = this.datepipe.transform(date, BACKEND_API_TIMESTAMP_FORMAT);
const params = <OrderService.OrderPatchOrderItemSubsetParams>{
orderId,
orderItemId,
@@ -264,16 +242,8 @@ export class CollectingShelfService {
);
}
updateEstimatedShippingDate(
orderId: number,
orderItemId: number,
orderItemSubsetId: number,
date: Date
): Observable<OrderItemSubsetDTO> {
const estimatedShippingDate = this.datepipe.transform(
date,
BACKEND_API_TIMESTAMP_FORMAT
);
updateEstimatedShippingDate(orderId: number, orderItemId: number, orderItemSubsetId: number, date: Date): Observable<OrderItemSubsetDTO> {
const estimatedShippingDate = this.datepipe.transform(date, BACKEND_API_TIMESTAMP_FORMAT);
const params = <OrderService.OrderPatchOrderItemSubsetParams>{
orderId,
orderItemId,
@@ -292,11 +262,7 @@ export class CollectingShelfService {
);
}
patchOrderItem(
orderId: number,
orderItemId: number,
orderItem: OrderItemDTO
) {
patchOrderItem(orderId: number, orderItemId: number, orderItem: OrderItemDTO) {
const params = <OrderService.OrderPatchOrderItemParams>{
orderId,
orderItemId,
@@ -381,9 +347,7 @@ export class CollectingShelfService {
if (response.error) {
throw new Error(response.message);
}
return response.result && response.result[0]
? response.result[0].supplierDescription
: null;
return response.result && response.result[0] ? response.result[0].supplierDescription : null;
})
);
}
@@ -410,23 +374,14 @@ export class CollectingShelfService {
);
}
processOrderCompartmentNumber(
orders: OrderItemListItemDTO[],
order: OrderItemListItemDTO,
index: number,
firstBatch: boolean
) {
processOrderCompartmentNumber(orders: OrderItemListItemDTO[], order: OrderItemListItemDTO, index: number, firstBatch: boolean) {
const tempOrders = [...orders];
const prevOrders = tempOrders.slice(0, index);
let hideCompartmentNumber = false;
if (index === 0 && !firstBatch) {
const olderOrders = this.store.selectSnapshot(
CollectingShelfSelectors.getShelfOrders
);
const olderOrders = this.store.selectSnapshot(CollectingShelfSelectors.getShelfOrders);
if (olderOrders && olderOrders.length > 0) {
const filteredOrders = olderOrders.filter(
(t) => t.orderId === order.orderId
);
const filteredOrders = olderOrders.filter((t) => t.orderId === order.orderId);
if (
order.compartmentCode &&
order.processingStatus &&
@@ -435,10 +390,8 @@ export class CollectingShelfService {
filteredOrders[filteredOrders.length - 1] &&
filteredOrders[filteredOrders.length - 1].compartmentCode &&
filteredOrders[filteredOrders.length - 1].processingStatus &&
filteredOrders[filteredOrders.length - 1].compartmentCode ===
order.compartmentCode &&
filteredOrders[filteredOrders.length - 1].processingStatus ===
order.processingStatus
filteredOrders[filteredOrders.length - 1].compartmentCode === order.compartmentCode &&
filteredOrders[filteredOrders.length - 1].processingStatus === order.processingStatus
) {
hideCompartmentNumber = true;
}
@@ -451,10 +404,8 @@ export class CollectingShelfService {
prevOrders[prevOrders.length - 1] &&
prevOrders[prevOrders.length - 1].compartmentCode &&
prevOrders[prevOrders.length - 1].processingStatus &&
prevOrders[prevOrders.length - 1].compartmentCode ===
order.compartmentCode &&
prevOrders[prevOrders.length - 1].processingStatus ===
order.processingStatus
prevOrders[prevOrders.length - 1].compartmentCode === order.compartmentCode &&
prevOrders[prevOrders.length - 1].processingStatus === order.processingStatus
) {
hideCompartmentNumber = true;
}

View File

@@ -22,7 +22,10 @@ export class ContentHeaderService {
breadcrumbs: [],
filter: ['/remission/create', '/remission/started', '/shelf'],
};
blackList = { breadcrumbs: ['/dashboard', '/branch/main'], filter: [] };
blackList = {
breadcrumbs: ['/dashboard', '/branch/main'],
filter: ['/history', 'edit'],
};
constructor(
private locationService: LocationService,
@@ -115,7 +118,11 @@ export class ContentHeaderService {
(whitelistetUrl) =>
whitelistetUrl.includes(url) || url.includes(whitelistetUrl)
);
return isOnWhiteList;
const isOnBlacklist = applicableBlacklist.find(
(blackListUrl) =>
blackListUrl.includes(url) || url.includes(blackListUrl)
);
return isOnWhiteList && !isOnBlacklist;
}
if (type === 'breadcrumbs') {

View File

@@ -1,7 +1,5 @@
import { TestBed } from '@angular/core/testing';
import { CustomerService } from './customer.service';
describe('CustomerService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
});

View File

@@ -1,6 +1,9 @@
import { Injectable } from '@angular/core';
import { QueryTokenDTO } from '@swagger/cat';
import { CustomerService as CustomerApiService, PayerService } from '@swagger/crm';
import {
CustomerService as CustomerApiService,
PayerService,
} from '@swagger/crm';
import { map, switchMap, catchError } from 'rxjs/operators';
import { Observable, of, combineLatest, BehaviorSubject } from 'rxjs';
import { CustomerMapping } from '../mappings/customer.mapping';
@@ -28,7 +31,11 @@ import { CustomerFilters } from '../../modules/customer';
})
export class CustomerService {
addAddressError$ = new BehaviorSubject<any>(null);
constructor(private customerService: CustomerApiService, private mapper: CustomerMapping, private payerService: PayerService) {}
constructor(
private customerService: CustomerApiService,
private mapper: CustomerMapping,
private payerService: PayerService
) {}
createCustomer(_customer: User): Observable<User> {
const customer: CustomerDTO = this.mapper.fromUser(_customer);
@@ -97,7 +104,9 @@ export class CustomerService {
filter?: CustomerFilters
): Observable<CustomerSearchResponse> {
const noFilters = filter
? isNullOrUndefined(filter.bonuscard) && isNullOrUndefined(filter.guestaccount) && isNullOrUndefined(filter.onlineshop)
? isNullOrUndefined(filter.bonuscard) &&
isNullOrUndefined(filter.guestaccount) &&
isNullOrUndefined(filter.onlineshop)
? true
: false
: false;
@@ -112,9 +121,15 @@ export class CustomerService {
filter:
filter && !noFilters
? {
['bonuscard']: !isNullOrUndefined(filter.bonuscard) ? String(filter.bonuscard) : undefined,
['guestaccount']: !isNullOrUndefined(filter.guestaccount) ? String(filter.guestaccount) : undefined,
['onlineshop']: !isNullOrUndefined(filter.onlineshop) ? String(filter.onlineshop) : undefined,
['bonuscard']: !isNullOrUndefined(filter.bonuscard)
? String(filter.bonuscard)
: undefined,
['guestaccount']: !isNullOrUndefined(filter.guestaccount)
? String(filter.guestaccount)
: undefined,
['onlineshop']: !isNullOrUndefined(filter.onlineshop)
? String(filter.onlineshop)
: undefined,
}
: undefined,
};
@@ -125,7 +140,9 @@ export class CustomerService {
throw new Error(response.message);
}
return {
customers: response.result.map((t) => this.mapper.customerInfoDTOtoUser(t)),
customers: response.result.map((t) =>
this.mapper.customerInfoDTOtoUser(t)
),
hits: response.hits,
message: response.message,
};
@@ -133,47 +150,61 @@ export class CustomerService {
);
}
getCustomer(customer$: Observable<ResponseArgsOfCustomerDTO>): Observable<User> {
getCustomer(
customer$: Observable<ResponseArgsOfCustomerDTO>
): Observable<User> {
return customer$.pipe(
map((response: ResponseArgsOfCustomerDTO) => {
if (response.error) {
throw new Error(response.message);
}
return {
shippingAddresses: response.result.shippingAddresses.map((addressEnity) => addressEnity.id),
shippingAddresses: response.result.shippingAddresses.map(
(addressEnity) => addressEnity.id
),
user: this.mapper.customerDTOtoUser(response.result),
};
}),
switchMap((customerResult) => {
const user = customerResult.user;
if (!customerResult.shippingAddresses || customerResult.shippingAddresses.length === 0) {
if (
!customerResult.shippingAddresses ||
customerResult.shippingAddresses.length === 0
) {
return of(customerResult.user);
}
const observablesOfShippingAddresses = customerResult.shippingAddresses.map((t) => {
return this.customerService.CustomerGetShippingaddress(t).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return response.result;
})
);
});
const shippingAddresses$ = combineLatest(observablesOfShippingAddresses);
const observablesOfShippingAddresses = customerResult.shippingAddresses.map(
(t) => {
return this.customerService.CustomerGetShippingaddress(t).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return response.result;
})
);
}
);
const shippingAddresses$ = combineLatest(
observablesOfShippingAddresses
);
return shippingAddresses$.pipe(
switchMap((address: ShippingAddressDTO[]) => {
const updatedUser = user;
if (!!address) {
const addresses = address.map((t) => {
return { ...this.mapper.fromShippingAddressDtoToAddress(t), synced: true };
return {
...this.mapper.fromShippingAddressDtoToAddress(t),
synced: true,
};
});
const defaultAddresses = addresses.reduce((ad1, ad2) => {
const defaultAddress = addresses.reduce((ad1, ad2) => {
if (ad1.defaultSince >= ad2.defaultSince) {
return ad1;
}
return ad2;
});
updatedUser.delivery_addres = defaultAddresses;
updatedUser.delivery_addres = defaultAddress;
updatedUser.poossible_delivery_addresses = addresses;
}
return of(updatedUser);
@@ -182,30 +213,39 @@ export class CustomerService {
}),
switchMap((customer: User) => {
const updateCustomer = customer;
return this.customerService.CustomerGetAssignedPayersByCustomerId(customer.id).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return { customer: customer, assignedPayers: response.result };
}),
switchMap((response) => {
if (Array.isArray(response.assignedPayers) && response.assignedPayers.length > 0) {
const invoiceAddresses = response.assignedPayers.map((t) => {
return { ...this.mapper.fromAssignedPayerDtoToAddress(t), synced: true };
});
const defaultAddresses = invoiceAddresses.reduce((ad1, ad2) => {
if (ad1.defaultSince >= ad2.defaultSince) {
return ad1;
}
return ad2;
});
updateCustomer.poossible_invoice_addresses = invoiceAddresses;
updateCustomer.invoice_address = defaultAddresses;
}
return of(updateCustomer);
})
);
return this.customerService
.CustomerGetAssignedPayersByCustomerId(customer.id)
.pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return { customer: customer, assignedPayers: response.result };
}),
switchMap((response) => {
if (
Array.isArray(response.assignedPayers) &&
response.assignedPayers.length > 0
) {
const invoiceAddresses = response.assignedPayers.map((t) => {
return {
...this.mapper.fromAssignedPayerDtoToAddress(t),
synced: true,
};
});
const defaultAddress = invoiceAddresses.reduce((ad1, ad2) => {
if (ad1.defaultSince >= ad2.defaultSince) {
return ad1;
}
return ad2;
});
updateCustomer.poossible_invoice_addresses = invoiceAddresses;
updateCustomer.invoice_address = defaultAddress;
}
return of(updateCustomer);
})
);
})
);
}
@@ -223,7 +263,12 @@ export class CustomerService {
if (response.error) {
throw new Error(response.message);
}
return { customers: response.result.map((t) => this.mapper.customerInfoDTOtoUser(t)), hits: 0 };
return {
customers: response.result.map((t) =>
this.mapper.customerInfoDTOtoUser(t)
),
hits: 0,
};
})
);
}
@@ -249,25 +294,32 @@ export class CustomerService {
isDefault: true,
};
if (shippingAddress.synced === true) {
return this.customerService.CustomerModifyShippingAddressFlag(params).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return response.result;
})
);
return this.customerService
.CustomerModifyShippingAddressFlag(params)
.pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return response.result;
})
);
} else {
return this.addShippingAddress(customerId, { ...shippingAddress, id: null }).pipe(
return this.addShippingAddress(customerId, {
...shippingAddress,
id: null,
}).pipe(
switchMap(() => {
return this.customerService.CustomerModifyShippingAddressFlag(params).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return response.result;
})
);
return this.customerService
.CustomerModifyShippingAddressFlag(params)
.pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
return response.result;
})
);
})
);
}
@@ -292,7 +344,10 @@ export class CustomerService {
})
);
} else {
return this.addInvoiceAddress(customerId, { ...invoiceAddress, id: null }).pipe(
return this.addInvoiceAddress(customerId, {
...invoiceAddress,
id: null,
}).pipe(
switchMap((payerDto: PayerDTO) => {
const params = {
payerId: payerDto.id,
@@ -316,7 +371,10 @@ export class CustomerService {
if (!isNullOrUndefined(shippingAddress.id)) {
const params = {
shippingAddressId: shippingAddress.id,
shippingAddress: this.mapper.fromAddressToShippingAddressDTO(shippingAddress, !isNullOrUndefined(shippingAddress.id)),
shippingAddress: this.mapper.fromAddressToShippingAddressDTO(
shippingAddress,
!isNullOrUndefined(shippingAddress.id)
),
customerId: customerId,
};
return this.customerService.CustomerUpdateShippingAddress(params).pipe(
@@ -344,7 +402,10 @@ export class CustomerService {
);
} else {
const params = {
shippingAddress: this.mapper.fromAddressToShippingAddressDTO(shippingAddress, !isNullOrUndefined(shippingAddress.id)),
shippingAddress: this.mapper.fromAddressToShippingAddressDTO(
shippingAddress,
!isNullOrUndefined(shippingAddress.id)
),
customerId: customerId,
};
return this.customerService.CustomerCreateShippingAddress(params).pipe(
@@ -404,15 +465,17 @@ export class CustomerService {
payerId: payerResponse.result.id,
isDefault: true,
};
return this.customerService.CustomerAddPayerReference(referenceParamsparams).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
this.addAddressError$.next(undefined);
return response.result.payer.data;
})
);
return this.customerService
.CustomerAddPayerReference(referenceParamsparams)
.pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);
}
this.addAddressError$.next(undefined);
return response.result.payer.data;
})
);
})
);
} else {
@@ -450,7 +513,10 @@ export class CustomerService {
if (response.error) {
throw new Error(response.message);
}
return response.result.map((t, index) => <Country>{ index, id: t.id, key: t.isO3166_A_3, value: t.name });
return response.result.map(
(t, index) =>
<Country>{ index, id: t.id, key: t.isO3166_A_3, value: t.name }
);
})
);
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { OrderService, QueryTokenDTO } from '@swagger/oms';
import { OrderService, QueryTokenDTO, AbholfachService } from '@swagger/oms';
import { map } from 'rxjs/operators';
import { Store } from '@ngxs/store';
import { GoodsInSelectors } from '../store/selectors/goods-in.selectors';
@@ -8,14 +8,9 @@ import { GoodsInSelectors } from '../store/selectors/goods-in.selectors';
providedIn: 'root',
})
export class GoodsInService {
constructor(private omsService: OrderService, private store: Store) {}
constructor(private abholfach: AbholfachService, private store: Store) {}
search(
input: string,
branchNumber: string,
skip: number = 0,
take: number = 0
) {
search(input: string, branchNumber: string, skip: number = 0, take: number = 0) {
const params = <QueryTokenDTO>{
input: {
qs: input,
@@ -24,7 +19,7 @@ export class GoodsInService {
skip,
take,
};
return this.omsService.OrderWareneingangsuche(params).pipe(
return this.abholfach.AbholfachWareneingangsuche(params).pipe(
map((response) => {
if (response.error) {
throw new Error(response.message);

View File

@@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { SessionStorageService } from './session-storage.service';
export interface ScrollPosition {
top?: number;
left?: number;
}
@Injectable({ providedIn: 'root' })
export class ScrollPositionService {
constructor(private storageService: SessionStorageService) {}
getPosition(key: string): ScrollPosition {
return this.storageService.get(key);
}
setPosition(key: string, position: ScrollPosition): void {
this.storageService.set(key, position);
}
deletePosition(key: string): void {
this.storageService.delete(key);
}
}

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { StorageService } from './storage.service';
@Injectable({ providedIn: 'root' })
export class SessionStorageService extends StorageService {
constructor() {
super();
}
get<T>(key: string): T {
let result: T;
try {
result = JSON.parse(sessionStorage.getItem(key));
} catch (error) {
console.error(`Error getting ${key}`);
}
return result;
}
set<T>(key: string, data: T): void {
try {
sessionStorage.setItem(key, JSON.stringify(data));
} catch (error) {
console.error(`Error saving ${key}`);
}
}
delete(key: string): void {
try {
sessionStorage.removeItem(key);
} catch (error) {
console.error(`Error deleting ${key}`);
}
}
}

View File

@@ -0,0 +1,7 @@
export abstract class StorageService {
abstract get<T>(key: string): T;
abstract set<T>(key: string, data: T): void;
abstract delete(key: string): void;
}

View File

@@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { OrderService, VATType, VATDTO } from '@swagger/oms';
import { map } from 'rxjs/operators';
import { BehaviorSubject, ReplaySubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class VatService {
private getVatsSubject: ReplaySubject<VATDTO[]>;
constructor(private orderService: OrderService) {}
getVats() {
if (!this.getVatsSubject) {
this.getVatsSubject = new ReplaySubject();
this.orderService
.OrderGetVATs({})
.pipe(map((response) => response.result))
.subscribe(this.getVatsSubject);
}
return this.getVatsSubject.asObservable();
}
}

View File

@@ -25,17 +25,31 @@ export class ProcessSelectors {
}
@Selector([AppState, ProcessState])
static getCurrentProcess(state: AppStateModel, processState: ProcessStateModel): Process {
static getCurrentProcess(
state: AppStateModel,
processState: ProcessStateModel
): Process {
return processState.processes[state.currentProcesssId];
}
@Selector([AppState, ProcessState])
static getCurrentProcessId(
state: AppStateModel,
processState: ProcessStateModel
): number {
return processState.processes[state.currentProcesssId].id;
}
@Selector([ProcessState])
static getRecentProducts(state: ProcessStateModel) {
return state.recentArticles;
}
@Selector([AppState, ProcessState])
static getSelectedProduct(state: AppStateModel, processState: ProcessStateModel) {
static getSelectedProduct(
state: AppStateModel,
processState: ProcessStateModel
) {
const currentProcess = processState.processes[state.currentProcesssId];
if (currentProcess) {
return currentProcess.selectedItem;
@@ -43,7 +57,10 @@ export class ProcessSelectors {
}
@Selector([AppState, ProcessState])
static getCurrentProcessCustomerNotificationFlag(state: AppStateModel, processState: ProcessStateModel) {
static getCurrentProcessCustomerNotificationFlag(
state: AppStateModel,
processState: ProcessStateModel
) {
const currentProcess = processState.processes[state.currentProcesssId];
if (currentProcess) {
return currentProcess.customerNotificationFlag;
@@ -51,7 +68,10 @@ export class ProcessSelectors {
}
@Selector([AppState, ProcessState])
static getScrollPositionForProduct(state: AppStateModel, processState: ProcessStateModel) {
static getScrollPositionForProduct(
state: AppStateModel,
processState: ProcessStateModel
) {
const currentProcess = processState.processes[state.currentProcesssId];
if (currentProcess) {
return currentProcess.productScrollTo;
@@ -59,7 +79,10 @@ export class ProcessSelectors {
}
@Selector([AppState, ProcessState])
static getActiveUserId(state: AppStateModel, processState: ProcessStateModel) {
static getActiveUserId(
state: AppStateModel,
processState: ProcessStateModel
) {
const currentProcess = processState.processes[state.currentProcesssId];
if (currentProcess) {
return currentProcess.activeCustomer;
@@ -67,15 +90,25 @@ export class ProcessSelectors {
}
@Selector([AppState, ProcessState])
static getDetailsUserId(state: AppStateModel, processState: ProcessStateModel) {
static getDetailsUserId(
state: AppStateModel,
processState: ProcessStateModel
) {
const currentProcess = processState.processes[state.currentProcesssId];
if (currentProcess && (!currentProcess.onlineCustomerCreationError || currentProcess.onlineCustomerCreationError.error === false)) {
if (
currentProcess &&
(!currentProcess.onlineCustomerCreationError ||
currentProcess.onlineCustomerCreationError.error === false)
) {
return currentProcess.detailsCustomer;
}
}
@Selector([AppState, ProcessState])
static getUniqueIdentifier(state: AppStateModel, processState: ProcessStateModel) {
static getUniqueIdentifier(
state: AppStateModel,
processState: ProcessStateModel
) {
const currentProcess = processState.processes[state.currentProcesssId];
if (currentProcess) {
return currentProcess.uniqueIdentifier;
@@ -83,7 +116,10 @@ export class ProcessSelectors {
}
@Selector([AppState, ProcessState])
static getCustomerSearch(state: AppStateModel, processState: ProcessStateModel) {
static getCustomerSearch(
state: AppStateModel,
processState: ProcessStateModel
) {
return processState.processes[state.currentProcesssId].customerSearch;
}
@@ -98,27 +134,46 @@ export class ProcessSelectors {
}
@Selector([AppState, ProcessState])
static getFinishedOrders(state: AppStateModel, processState: ProcessStateModel) {
static getFinishedOrders(
state: AppStateModel,
processState: ProcessStateModel
) {
return processState.processes[state.currentProcesssId].finishedOrder;
}
@Selector([AppState, ProcessState])
static getArticleSearchErrorStatus(state: AppStateModel, processState: ProcessStateModel) {
return processState.processes[state.currentProcesssId].articleSearchErrorStatus;
static getArticleSearchErrorStatus(
state: AppStateModel,
processState: ProcessStateModel
) {
return processState.processes[state.currentProcesssId]
.articleSearchErrorStatus;
}
@Selector([AppState, ProcessState])
static getPreviousRoute(state: AppStateModel, processState: ProcessStateModel) {
static getPreviousRoute(
state: AppStateModel,
processState: ProcessStateModel
) {
return processState.processes[state.currentProcesssId].previousRoute;
}
@Selector([AppState, ProcessState])
static getCurrentRoute(state: AppStateModel, processState: ProcessStateModel) {
return getProperty(processState, `processes.${state.currentProcesssId}.currentRoute`);
static getCurrentRoute(
state: AppStateModel,
processState: ProcessStateModel
) {
return getProperty(
processState,
`processes.${state.currentProcesssId}.currentRoute`
);
}
@Selector([AppState, ProcessState])
static getProductFilters(state: AppStateModel, processState: ProcessStateModel) {
static getProductFilters(
state: AppStateModel,
processState: ProcessStateModel
) {
return processState.processes[state.currentProcesssId].productSearchFilters;
}

View File

@@ -10,7 +10,10 @@ import {
import { LoadBranches, LoadUserBranch } from '../actions/branch.actions';
import { BranchSelectors } from '../selectors/branch.selector';
import { isNullOrUndefined } from 'util';
import { RemoveProcessNewState, ReloadProcessData } from '../actions/process.actions';
import {
RemoveProcessNewState,
ReloadProcessData,
} from '../actions/process.actions';
import { UserStateService } from '../../services/user-state.service';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { ReloadCustomersData } from '../actions/customer.actions';
@@ -20,7 +23,7 @@ import { ReloadProductsData } from '../actions/product.actions';
import { ReloadBreadcrumbsData } from '../actions/breadcrumb.actions';
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap, tap } from 'rxjs/operators';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { ModuleSwitcher } from '../../models/app-switcher.enum';
import { ModuleSwitcherService } from '../../services/module-switcher.service';
import { ReloadFiltersData } from '../actions/filter.actions';
@@ -31,7 +34,7 @@ import { ReloadRemission } from '../actions/remission.actions';
import { ReloadFormState } from '../actions/forms.actions';
import { FILIALE_LANDING_PAGE } from '../../utils/app.constants';
export const SYNC_DATA_VERSION = 210;
export const SYNC_DATA_VERSION = 211;
export class AppStateModel {
currentProcesssId: number;
@@ -101,9 +104,13 @@ export class AppState {
}
@Action(AppSetCurrentProcess)
appSetCurrentProcess(ctx: StateContext<AppStateModel>, { payload }: AppSetCurrentProcess) {
appSetCurrentProcess(
ctx: StateContext<AppStateModel>,
{ payload }: AppSetCurrentProcess
) {
const state = ctx.getState();
const processExists = state.processIds.findIndex((t) => t === payload) !== -1;
const processExists =
state.processIds.findIndex((t) => t === payload) !== -1;
if (processExists) {
ctx.patchState({
currentProcesssId: payload,
@@ -115,18 +122,23 @@ export class AppState {
@Action(AppAddProcess)
appAddProcess(ctx: StateContext<AppStateModel>, { payload }: AppAddProcess) {
const state = ctx.getState();
const processExists = state.processIds.findIndex((t) => t === payload) !== -1;
const processExists =
state.processIds.findIndex((t) => t === payload) !== -1;
if (!processExists) {
const processIds = [...state.processIds, payload];
const currentProcesssId = payload;
ctx.patchState({ currentProcesssId, processIds });
const branchesLoaded = this.store.selectSnapshot(BranchSelectors.getBranches);
const branchesLoaded = this.store.selectSnapshot(
BranchSelectors.getBranches
);
if (!branchesLoaded || Object.keys(branchesLoaded).length === 0) {
this.store.dispatch(new LoadBranches());
}
const userBranch = this.store.selectSnapshot(BranchSelectors.getUserBranch);
const userBranch = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
if (isNullOrUndefined(userBranch)) {
this.store.dispatch(new LoadUserBranch());
}
@@ -136,7 +148,10 @@ export class AppState {
}
@Action(AppDeleteProcess)
appDeleteProcess(ctx: StateContext<AppStateModel>, { payload }: AppDeleteProcess) {
appDeleteProcess(
ctx: StateContext<AppStateModel>,
{ payload }: AppDeleteProcess
) {
const state = ctx.getState();
const currentIds = state.processIds;
if (currentIds) {
@@ -147,7 +162,10 @@ export class AppState {
}
@Action(AppSwitchModule)
appSwitchModule(ctx: StateContext<AppStateModel>, { payload }: AppSwitchModule) {
appSwitchModule(
ctx: StateContext<AppStateModel>,
{ payload }: AppSwitchModule
) {
const state = ctx.getState();
ctx.patchState({ activeModule: payload });
this.syncApiState(state.processIds, payload);
@@ -157,7 +175,10 @@ export class AppState {
* Save store data on backend
*/
@Action(AppUserDataSync)
appUserDataSynced(ctx: StateContext<AppStateModel>, { data, sync }: AppUserDataSync) {
appUserDataSynced(
ctx: StateContext<AppStateModel>,
{ data, sync }: AppUserDataSync
) {
const state = ctx.getState();
let currentUserData: UserStateSyncData = {};
@@ -184,7 +205,10 @@ export class AppState {
* Initial store loading from API, triggered once on page load
*/
@Action(ReloadSavedState)
reloadSavedState(ctx: StateContext<AppStateModel>, { data, sync }: ReloadSavedState) {
reloadSavedState(
ctx: StateContext<AppStateModel>,
{ data, sync }: ReloadSavedState
) {
const state = ctx.getState();
let currentUserData: UserStateSyncData = {};
@@ -208,7 +232,10 @@ export class AppState {
if (sync && syncedData.version === SYNC_DATA_VERSION) {
this.reloadDataFromAPI(syncedData);
if (syncedData.currentProcesssId || syncedData.activeModule === ModuleSwitcher.Branch) {
if (
syncedData.currentProcesssId ||
syncedData.activeModule === ModuleSwitcher.Branch
) {
ctx.patchState({
...state,
synced: true,
@@ -235,11 +262,19 @@ export class AppState {
return;
}
if (data.customers) {
this.store.dispatch(new ReloadCustomersData(data.customers, data.lastCreatedCustomerId, data.cachedCustomerSearch));
this.store.dispatch(
new ReloadCustomersData(
data.customers,
data.lastCreatedCustomerId,
data.cachedCustomerSearch
)
);
}
if (data.products) {
this.store.dispatch(new ReloadProductsData(data.products, data.cachedProductResults));
this.store.dispatch(
new ReloadProductsData(data.products, data.cachedProductResults)
);
}
if (data.carts) {
@@ -259,7 +294,9 @@ export class AppState {
}
if (data.processes) {
this.store.dispatch(new ReloadProcessData(data.processes, data.recentArticles));
this.store.dispatch(
new ReloadProcessData(data.processes, data.recentArticles)
);
}
if (data.formsState) {
@@ -267,11 +304,20 @@ export class AppState {
}
if (data.activeModule === ModuleSwitcher.Customer) {
if (data.processes && data.currentProcesssId && data.processes[data.currentProcesssId]) {
if (
data.processes &&
data.currentProcesssId &&
data.processes[data.currentProcesssId]
) {
const currentProcesssId = data.currentProcesssId;
const currentRoute = data.processes[currentProcesssId].currentRoute;
if (currentRoute && currentRoute.length > 0) {
this.routingAvailableAction(data, currentProcesssId, currentRoute, data.activeModule);
this.routingAvailableAction(
data,
currentProcesssId,
currentRoute,
data.activeModule
);
} else {
this.router.navigate(['/dashboard']);
}
@@ -283,7 +329,12 @@ export class AppState {
const currentProcesssId = -1;
const currentRoute = data.branchProcess.currentRoute;
if (currentRoute && currentRoute.length > 0) {
this.routingAvailableAction(data, currentProcesssId, currentRoute, data.activeModule);
this.routingAvailableAction(
data,
currentProcesssId,
currentRoute,
data.activeModule
);
} else {
this.router.navigate([FILIALE_LANDING_PAGE]);
}
@@ -297,11 +348,24 @@ export class AppState {
}
if (data.activeModule === ModuleSwitcher.Branch) {
this.moduleSwitcherService.switch(ModuleSwitcher.Branch, data.branchProcess);
this.moduleSwitcherService.switch(
ModuleSwitcher.Branch,
data.branchProcess
);
}
if (data.processesBreadcrumbs && data.activeCrumbs && (data.currentProcesssId || data.activeModule === ModuleSwitcher.Branch)) {
this.store.dispatch(new ReloadBreadcrumbsData(data.processesBreadcrumbs, data.activeCrumbs, data.previusMenuPath));
if (
data.processesBreadcrumbs &&
data.activeCrumbs &&
(data.currentProcesssId || data.activeModule === ModuleSwitcher.Branch)
) {
this.store.dispatch(
new ReloadBreadcrumbsData(
data.processesBreadcrumbs,
data.activeCrumbs,
data.previusMenuPath
)
);
}
if (data.filters && data.processesSelectedFilters && data.dropdownFilters) {
@@ -327,16 +391,28 @@ export class AppState {
);
}
private routingAvailableAction(data: UserStateSyncData, currentProcesssId: number, currentRoute: string, module: ModuleSwitcher) {
const hasActiveCrumbsAvailableForProcess = data.activeCrumbs && data.activeCrumbs[currentProcesssId];
private routingAvailableAction(
data: UserStateSyncData,
currentProcesssId: number,
currentRoute: string,
module: ModuleSwitcher
) {
const hasActiveCrumbsAvailableForProcess =
data.activeCrumbs && data.activeCrumbs[currentProcesssId];
const activeCrumbs = data.activeCrumbs[currentProcesssId];
const hasProcessBreadcrumbsAvailableForProcess = data.processesBreadcrumbs && data.processesBreadcrumbs[activeCrumbs];
const hasProcessBreadcrumbsAvailableForProcess =
data.processesBreadcrumbs && data.processesBreadcrumbs[activeCrumbs];
const breadcrumb = hasProcessBreadcrumbsAvailableForProcess
? data.processesBreadcrumbs[activeCrumbs].find((t) => t.processId === currentProcesssId)
? data.processesBreadcrumbs[activeCrumbs].find(
(t) => t.processId === currentProcesssId
)
: null;
const breadcrumbPath = breadcrumb && breadcrumb.breadcrumbs ? breadcrumb.breadcrumbs.find((t) => t && t.path === currentRoute) : null;
const breadcrumbPath =
breadcrumb && breadcrumb.breadcrumbs
? breadcrumb.breadcrumbs.find((t) => t && t.path === currentRoute)
: null;
if (
hasActiveCrumbsAvailableForProcess &&

View File

@@ -1,21 +1,8 @@
import {
State,
Action,
StateContext,
Store,
StateOperator,
Selector,
} from '@ngxs/store';
import { State, Action, StateContext, Store, StateOperator, Selector } from '@ngxs/store';
import { ProcessBreadcrumbs } from '../../models/process-breadcrumbs.model';
import * as actions from '../actions/breadcrumb.actions';
import { Breadcrumb } from '../../models/breadcrumb.model';
import {
patch,
removeItem,
iif,
updateItem,
append,
} from '@ngxs/store/operators';
import { patch, removeItem, iif, updateItem, append } from '@ngxs/store/operators';
import { AppState } from './app.state';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { AppUserDataSync } from '../actions/app.actions';
@@ -59,10 +46,7 @@ export class BreadcrumbsStateModel {
export class BreadcrumbsState {
@Selector()
static hasRemissionBreadcrumbs(breadcrumbsState: BreadcrumbsStateModel) {
return (
breadcrumbsState.processesBreadcrumbs.remission &&
breadcrumbsState.processesBreadcrumbs.remission.length > 0
);
return breadcrumbsState.processesBreadcrumbs.remission && breadcrumbsState.processesBreadcrumbs.remission.length > 0;
}
editAddaddressPageFilter = (path: string) =>
@@ -73,66 +57,37 @@ export class BreadcrumbsState {
// tslint:disable-next-line: semicolon
path.includes('/delivery/add/');
constructor(
private store: Store,
private router: Router,
private moduleSwitcherService: ModuleSwitcherService
) {}
constructor(private store: Store, private router: Router, private moduleSwitcherService: ModuleSwitcherService) {}
@Action(actions.AddBreadcrumb)
addBreadcrumb(
ctx: StateContext<BreadcrumbsStateModel>,
{ payload, breadcrumbsId, isMainBreadcrumb }: actions.AddBreadcrumb
) {
addBreadcrumb(ctx: StateContext<BreadcrumbsStateModel>, { payload, breadcrumbsId, isMainBreadcrumb }: actions.AddBreadcrumb) {
const state = ctx.getState();
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
const currentProcessBreadcrumbs: ProcessBreadcrumbs[] =
state.processesBreadcrumbs[breadcrumbsId];
const currentProcessBreadcrumbs: ProcessBreadcrumbs[] = state.processesBreadcrumbs[breadcrumbsId];
const pathData = payload.path.split('/');
if (!currentProcessBreadcrumbs || !currentProcessId) {
return;
}
const processBreadcrumbsFound =
currentProcessBreadcrumbs.find(
(t) => t.processId === currentProcessId
) !== undefined;
const processBreadcrumbsFound = currentProcessBreadcrumbs.find((t) => t.processId === currentProcessId) !== undefined;
let processesBreadcrumbs: ProcessBreadcrumbs[];
if (processBreadcrumbsFound) {
processesBreadcrumbs = currentProcessBreadcrumbs.map(
(pb: ProcessBreadcrumbs) => {
if (pb.processId === currentProcessId) {
return this.updateProcessBreadcrumb(
payload,
isMainBreadcrumb,
pathData.length > 0 ? pathData[pathData.length - 1] : ''
)(pb);
} else {
return pb;
}
processesBreadcrumbs = currentProcessBreadcrumbs.map((pb: ProcessBreadcrumbs) => {
if (pb.processId === currentProcessId) {
return this.updateProcessBreadcrumb(payload, isMainBreadcrumb, pathData.length > 0 ? pathData[pathData.length - 1] : '')(pb);
} else {
return pb;
}
);
});
} else {
processesBreadcrumbs = [
...currentProcessBreadcrumbs,
{ processId: currentProcessId, breadcrumbs: [payload] },
];
processesBreadcrumbs = [...currentProcessBreadcrumbs, { processId: currentProcessId, breadcrumbs: [payload] }];
}
const updatedCrumbs = this.udpatedBreadcrumbsObject(
state.processesBreadcrumbs,
breadcrumbsId,
processesBreadcrumbs
);
const updatedCrumbs = this.udpatedBreadcrumbsObject(state.processesBreadcrumbs, breadcrumbsId, processesBreadcrumbs);
if (
(pathData.length > 0 && pathData[pathData.length - 1] === 'billing') ||
pathData[pathData.length - 1] === 'delivery'
) {
if ((pathData.length > 0 && pathData[pathData.length - 1] === 'billing') || pathData[pathData.length - 1] === 'delivery') {
pathData.pop();
const newPath = pathData.join('/');
this.store.dispatch(new SetPreviusPath(newPath));
@@ -152,12 +107,7 @@ export class BreadcrumbsState {
}
}
const previousMenuPath = this.updatePreviousMenuPath(
currentProcessId,
state.previusMenuPath,
state.activeCrumbs,
payload
);
const previousMenuPath = this.updatePreviousMenuPath(currentProcessId, state.previusMenuPath, state.activeCrumbs, payload);
ctx.setState({
processesBreadcrumbs: {
@@ -173,64 +123,39 @@ export class BreadcrumbsState {
activeCrumbs: currentProcessActiveCrumbs,
previusMenuPath: previousMenuPath,
});
this.syncApiState(
ctx.getState().processesBreadcrumbs,
currentProcessActiveCrumbs,
previousMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, currentProcessActiveCrumbs, previousMenuPath);
}
@Action(actions.AddBreadcrumbNoDub)
addBreadcrumbNoDub(
ctx: StateContext<BreadcrumbsStateModel>,
{ payload, breadcrumbsId }: actions.AddBreadcrumbNoDub
) {
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
addBreadcrumbNoDub(ctx: StateContext<BreadcrumbsStateModel>, { payload, breadcrumbsId }: actions.AddBreadcrumbNoDub) {
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
const state = ctx.getState();
const currentProcessBreadcrumbs: ProcessBreadcrumbs[] =
state.processesBreadcrumbs[breadcrumbsId];
const currentProcessBreadcrumbs: ProcessBreadcrumbs[] = state.processesBreadcrumbs[breadcrumbsId];
const pathData = payload.path.split('/');
if (!currentProcessBreadcrumbs) {
return;
}
const processBreadcrumbsFound =
currentProcessBreadcrumbs.find(
(t) => t.processId === currentProcessId
) !== undefined;
const processBreadcrumbsFound = currentProcessBreadcrumbs.find((t) => t.processId === currentProcessId) !== undefined;
let processesBreadcrumbs: ProcessBreadcrumbs[];
if (processBreadcrumbsFound) {
const currentBreadcrumb = currentProcessBreadcrumbs.find(
(t) => t.processId === currentProcessId
).breadcrumbs;
const currentBreadcrumb = currentProcessBreadcrumbs.find((t) => t.processId === currentProcessId).breadcrumbs;
if (currentBreadcrumb) {
const lastPath = currentBreadcrumb[currentBreadcrumb.length - 1];
if (lastPath && lastPath.path !== payload.path) {
return;
}
}
processesBreadcrumbs = currentProcessBreadcrumbs.map(
(pb: ProcessBreadcrumbs) => {
if (pb.processId === currentProcessId) {
return this.updateProcessBreadcrumbFoNoDub(
payload,
false,
pathData.length > 0 ? pathData[pathData.length - 1] : ''
)(pb);
} else {
return pb;
}
processesBreadcrumbs = currentProcessBreadcrumbs.map((pb: ProcessBreadcrumbs) => {
if (pb.processId === currentProcessId) {
return this.updateProcessBreadcrumbFoNoDub(payload, false, pathData.length > 0 ? pathData[pathData.length - 1] : '')(pb);
} else {
return pb;
}
);
});
const updatedCrumbs = this.udpatedBreadcrumbsObject(
state.processesBreadcrumbs,
breadcrumbsId,
processesBreadcrumbs
);
const updatedCrumbs = this.udpatedBreadcrumbsObject(state.processesBreadcrumbs, breadcrumbsId, processesBreadcrumbs);
ctx.patchState({
processesBreadcrumbs: {
@@ -244,20 +169,14 @@ export class BreadcrumbsState {
// taskCalendar: updatedCrumbs['tasksCalendar']
},
});
this.syncApiState(
ctx.getState().processesBreadcrumbs,
state.activeCrumbs,
state.previusMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, state.activeCrumbs, state.previusMenuPath);
}
}
@Action(actions.DeleteLastPreviousPath)
deleteLastPreviousPath(ctx: StateContext<BreadcrumbsStateModel>) {
const state = ctx.getState();
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
if (!state || !currentProcessId) {
return;
}
@@ -266,10 +185,7 @@ export class BreadcrumbsState {
if (previousMenuPath && previousMenuPath[currentProcessId]) {
const updatedPrevousPaths = {
...previousMenuPath,
[currentProcessId]: previousMenuPath[currentProcessId].slice(
0,
previousMenuPath[currentProcessId].length - 1
),
[currentProcessId]: previousMenuPath[currentProcessId].slice(0, previousMenuPath[currentProcessId].length - 1),
};
ctx.patchState({ previusMenuPath: updatedPrevousPaths });
}
@@ -278,16 +194,9 @@ export class BreadcrumbsState {
@Action(actions.ResetBreadcrumbsTo)
resetBreadCrumbs(
ctx: StateContext<BreadcrumbsStateModel>,
{
payload,
breadcrumbsId,
isMainBreadcrumb,
isBranchView,
}: actions.ResetBreadcrumbsTo
{ payload, breadcrumbsId, isMainBreadcrumb, isBranchView }: actions.ResetBreadcrumbsTo
) {
const currentProcessId = isBranchView
? -1
: this.store.selectSnapshot(AppState.getCurrentProcessId);
const currentProcessId = isBranchView ? -1 : this.store.selectSnapshot(AppState.getCurrentProcessId);
const state = ctx.getState();
const currentProcessBreadcrumbs = state.processesBreadcrumbs[breadcrumbsId];
@@ -297,42 +206,24 @@ export class BreadcrumbsState {
return;
}
const processBreadcrumbsFound =
currentProcessBreadcrumbs.find(
(t) => t.processId === currentProcessId
) !== undefined;
const processBreadcrumbsFound = currentProcessBreadcrumbs.find((t) => t.processId === currentProcessId) !== undefined;
let processesBreadcrumbs: ProcessBreadcrumbs[];
if (processBreadcrumbsFound) {
processesBreadcrumbs = currentProcessBreadcrumbs.map(
(pb: ProcessBreadcrumbs) => {
if (pb.processId === currentProcessId) {
return this.updateProcessBreadcrumb(
payload,
isMainBreadcrumb,
pathData.length > 0 ? pathData[pathData.length - 1] : ''
)(pb);
} else {
return pb;
}
processesBreadcrumbs = currentProcessBreadcrumbs.map((pb: ProcessBreadcrumbs) => {
if (pb.processId === currentProcessId) {
return this.updateProcessBreadcrumb(payload, isMainBreadcrumb, pathData.length > 0 ? pathData[pathData.length - 1] : '')(pb);
} else {
return pb;
}
);
});
} else {
processesBreadcrumbs = [
...currentProcessBreadcrumbs,
{ processId: currentProcessId, breadcrumbs: [payload] },
];
processesBreadcrumbs = [...currentProcessBreadcrumbs, { processId: currentProcessId, breadcrumbs: [payload] }];
}
const updatedCrumbs = this.udpatedBreadcrumbsObject(
state.processesBreadcrumbs,
breadcrumbsId,
processesBreadcrumbs
);
const updatedCrumbs = this.udpatedBreadcrumbsObject(state.processesBreadcrumbs, breadcrumbsId, processesBreadcrumbs);
if (updatedCrumbs[breadcrumbsId].length === 0) {
updatedCrumbs[breadcrumbsId] = [
{ processId: currentProcessId, breadcrumbs: [payload] },
];
updatedCrumbs[breadcrumbsId] = [{ processId: currentProcessId, breadcrumbs: [payload] }];
}
let currentProcessActiveCrumbs = {};
@@ -349,12 +240,7 @@ export class BreadcrumbsState {
}
}
const previousMenuPath = this.updatePreviousMenuPath(
currentProcessId,
state.previusMenuPath,
state.activeCrumbs,
payload
);
const previousMenuPath = this.updatePreviousMenuPath(currentProcessId, state.previusMenuPath, state.activeCrumbs, payload);
ctx.setState({
processesBreadcrumbs: {
@@ -371,11 +257,7 @@ export class BreadcrumbsState {
previusMenuPath: previousMenuPath,
});
this.syncApiState(
ctx.getState().processesBreadcrumbs,
currentProcessActiveCrumbs,
previousMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, currentProcessActiveCrumbs, previousMenuPath);
}
private updatePreviousMenuPath(
@@ -388,26 +270,19 @@ export class BreadcrumbsState {
[id: number]: { crumb: string; path: string }[];
} = {};
const activeModule = this.store.selectSnapshot(AppState.activeModule);
const defaultCrumb =
activeModule === ModuleSwitcher.Customer ? 'dashboard' : 'goodsin';
const defaultPath =
activeModule === ModuleSwitcher.Customer
? '/dashboard'
: FILIALE_LANDING_PAGE;
const defaultCrumb = activeModule === ModuleSwitcher.Customer ? 'dashboard' : 'goodsin';
const defaultPath = activeModule === ModuleSwitcher.Customer ? '/dashboard' : FILIALE_LANDING_PAGE;
if (currentProcessId) {
const route = this.router.url;
const currentProcessCrumb = crumbs[currentProcessId];
const isCustomeOrderDetails = /^[/]customer[/][0-9]{1,}[/]order[/][0-9]{1,}$/;
if (route) {
if (
(route === '/cart/confirmation' &&
breadcrumb &&
!isCustomeOrderDetails.test(breadcrumb.path)) ||
(route === '/cart/confirmation' && breadcrumb && !isCustomeOrderDetails.test(breadcrumb.path)) ||
(menuPath &&
menuPath[currentProcessId] &&
menuPath[currentProcessId][menuPath[currentProcessId].length - 1] &&
menuPath[currentProcessId][menuPath[currentProcessId].length - 1]
.path === '/cart/confirmation')
menuPath[currentProcessId][menuPath[currentProcessId].length - 1].path === '/cart/confirmation')
) {
previousMenuPath = {
...menuPath,
@@ -415,21 +290,15 @@ export class BreadcrumbsState {
};
} else {
if (menuPath[currentProcessId]) {
const lastPath =
menuPath[currentProcessId][menuPath[currentProcessId].length - 1];
const isOntheSamePage = lastPath
? lastPath.crumb === currentProcessCrumb &&
lastPath.path === route
: false;
const lastPath = menuPath[currentProcessId][menuPath[currentProcessId].length - 1];
const isOntheSamePage = lastPath ? lastPath.crumb === currentProcessCrumb && lastPath.path === route : false;
if (!isOntheSamePage) {
previousMenuPath = {
...menuPath,
[currentProcessId]: [
...menuPath[currentProcessId],
{
crumb: currentProcessCrumb
? currentProcessCrumb
: defaultCrumb,
crumb: currentProcessCrumb ? currentProcessCrumb : defaultCrumb,
path: currentProcessCrumb ? route : defaultPath,
},
],
@@ -442,9 +311,7 @@ export class BreadcrumbsState {
...menuPath,
[currentProcessId]: [
{
crumb: currentProcessCrumb
? currentProcessCrumb
: defaultCrumb,
crumb: currentProcessCrumb ? currentProcessCrumb : defaultCrumb,
path: defaultPath,
},
],
@@ -478,8 +345,7 @@ export class BreadcrumbsState {
const updatedBreadcrumbs = updateItem<Breadcrumb>(
(breadcrumb) =>
breadcrumb.name === payload.name ||
(breadcrumb.path.substring(0, 16) ===
payload.path.substring(0, 16) &&
(breadcrumb.path.substring(0, 16) === payload.path.substring(0, 16) &&
pathEnding !== 'billing' &&
pathEnding !== 'delivery' &&
this.editAddaddressPageFilter(payload.path)),
@@ -495,11 +361,7 @@ export class BreadcrumbsState {
(item: Breadcrumb[]) => append<Breadcrumb>([payload])(item)
)(updatedBreadcrumbs);
const isCustomeOrderDetails = /^[/]customer[/][0-9]{1,}[/]order[/][0-9]{1,}$/;
if (
breadcrumbs &&
breadcrumbs[0].path === 'cart/confirmation' &&
!isCustomeOrderDetails.test(payload.path)
) {
if (breadcrumbs && breadcrumbs[0].path === 'cart/confirmation' && !isCustomeOrderDetails.test(payload.path)) {
breadcrumbs = [breadcrumbs[0]];
}
@@ -523,14 +385,11 @@ export class BreadcrumbsState {
breadcrumbs: [payload],
};
} else {
const updatedBreadcrumbs = updateItem<Breadcrumb>(
(breadcrumb) => breadcrumb.path === payload.path,
{
name: payload.name,
path: payload.path,
queryParams: payload.queryParams,
}
)(processBreadcrumbs.breadcrumbs);
const updatedBreadcrumbs = updateItem<Breadcrumb>((breadcrumb) => breadcrumb.path === payload.path, {
name: payload.name,
path: payload.path,
queryParams: payload.queryParams,
})(processBreadcrumbs.breadcrumbs);
return <ProcessBreadcrumbs>{
...processBreadcrumbs,
@@ -555,10 +414,8 @@ export class BreadcrumbsState {
const updatedBreadcrumbs = updateItem<Breadcrumb>(
(breadcrumb) =>
breadcrumb.name === payload.name ||
((breadcrumb.path.substring(0, 16) ===
payload.path.substring(0, 16) ||
breadcrumb.path.substring(0, 14) ===
payload.path.substring(0, 14)) &&
((breadcrumb.path.substring(0, 16) === payload.path.substring(0, 16) ||
breadcrumb.path.substring(0, 14) === payload.path.substring(0, 14)) &&
pathEnding !== 'billing' &&
pathEnding !== 'delivery' &&
!this.editAddaddressPageFilter(payload.path)),
@@ -578,33 +435,20 @@ export class BreadcrumbsState {
}
@Action(actions.PopBreadcrumbsAfterCurrent)
popBreadcrumbs(
ctx: StateContext<BreadcrumbsStateModel>,
{ path }: actions.PopBreadcrumbsAfterCurrent
) {
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
popBreadcrumbs(ctx: StateContext<BreadcrumbsStateModel>, { path }: actions.PopBreadcrumbsAfterCurrent) {
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
const state = ctx.getState();
const previusMenuPath = { ...state.previusMenuPath };
if (previusMenuPath && currentProcessId) {
const currentPreviousMenuPaths = previusMenuPath[currentProcessId];
if (currentPreviousMenuPaths) {
const pathIndex = currentPreviousMenuPaths.findIndex(
(p) => p && p.path === path
);
const pathIndex = currentPreviousMenuPaths.findIndex((p) => p && p.path === path);
if (pathIndex !== -1) {
const updatedMenuPaths = [
...currentPreviousMenuPaths.slice(0, pathIndex),
];
const updatedMenuPaths = [...currentPreviousMenuPaths.slice(0, pathIndex)];
previusMenuPath[currentProcessId] = updatedMenuPaths;
ctx.patchState({ previusMenuPath });
this.syncApiState(
state.processesBreadcrumbs,
state.activeCrumbs,
previusMenuPath
);
this.syncApiState(state.processesBreadcrumbs, state.activeCrumbs, previusMenuPath);
}
}
}
@@ -612,9 +456,7 @@ export class BreadcrumbsState {
deleteBreadcrumbsByIndex(index: number): StateOperator<ProcessBreadcrumbs> {
return (processBreadcrumbs: Readonly<ProcessBreadcrumbs>) => {
const breadcrumbs = removeItem<Breadcrumb>(
(b) => processBreadcrumbs.breadcrumbs.indexOf(b) > index
)(processBreadcrumbs.breadcrumbs);
const breadcrumbs = removeItem<Breadcrumb>((b) => processBreadcrumbs.breadcrumbs.indexOf(b) > index)(processBreadcrumbs.breadcrumbs);
return <ProcessBreadcrumbs>{
...processBreadcrumbs,
breadcrumbs: breadcrumbs,
@@ -623,14 +465,9 @@ export class BreadcrumbsState {
}
@Action(actions.PopBreadcrumbsBeforeCurrent)
popBreadcrumbsBefore(
ctx: StateContext<BreadcrumbsStateModel>,
{ breadcrumbsId }: actions.PopBreadcrumbsBeforeCurrent
) {
popBreadcrumbsBefore(ctx: StateContext<BreadcrumbsStateModel>, { breadcrumbsId }: actions.PopBreadcrumbsBeforeCurrent) {
const state = ctx.getState();
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
const currentProcessBreadcrumbs = state.processesBreadcrumbs[breadcrumbsId];
let currentProcessActiveCrumbs = {};
@@ -662,20 +499,12 @@ export class BreadcrumbsState {
previusMenuPath: {},
});
this.syncApiState(
ctx.getState().processesBreadcrumbs,
currentProcessActiveCrumbs,
state.previusMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, currentProcessActiveCrumbs, state.previusMenuPath);
}
leaveOnlyLastBreadcrumb(): StateOperator<ProcessBreadcrumbs> {
return (processBreadcrumbs: Readonly<ProcessBreadcrumbs>) => {
const breadcrumbs = [
processBreadcrumbs.breadcrumbs[
processBreadcrumbs.breadcrumbs.length - 1
],
];
const breadcrumbs = [processBreadcrumbs.breadcrumbs[processBreadcrumbs.breadcrumbs.length - 1]];
return <ProcessBreadcrumbs>{
...processBreadcrumbs,
breadcrumbs: breadcrumbs,
@@ -693,14 +522,9 @@ export class BreadcrumbsState {
}
@Action(actions.PopLastBreadcrumbs)
popLastBreadcrumb(
ctx: StateContext<BreadcrumbsStateModel>,
{ path }: actions.PopLastBreadcrumbs
) {
popLastBreadcrumb(ctx: StateContext<BreadcrumbsStateModel>, { path }: actions.PopLastBreadcrumbs) {
const state = ctx.getState();
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
const activeCrumbs = state.activeCrumbs[currentProcessId];
if (!activeCrumbs) {
return;
@@ -708,9 +532,7 @@ export class BreadcrumbsState {
if (
state.previusMenuPath[currentProcessId] &&
state.previusMenuPath[currentProcessId].length > 0 &&
state.previusMenuPath[currentProcessId][
state.previusMenuPath[currentProcessId].length - 1
].crumb !== activeCrumbs
state.previusMenuPath[currentProcessId][state.previusMenuPath[currentProcessId].length - 1].crumb !== activeCrumbs
) {
return;
}
@@ -718,19 +540,10 @@ export class BreadcrumbsState {
const processesBreadcrumbs = updateItem<ProcessBreadcrumbs>(
(item) => item.processId === currentProcessId,
(item) =>
this.deleteLastBreadcrumbForProcess(
path,
currentProcessId,
state.previusMenuPath
)(item)
(item) => this.deleteLastBreadcrumbForProcess(path, currentProcessId, state.previusMenuPath)(item)
)(currentProcessBreadcrumbs);
const updatedCrumbs = this.udpatedBreadcrumbsObject(
state.processesBreadcrumbs,
activeCrumbs,
processesBreadcrumbs
);
const updatedCrumbs = this.udpatedBreadcrumbsObject(state.processesBreadcrumbs, activeCrumbs, processesBreadcrumbs);
ctx.setState({
processesBreadcrumbs: {
@@ -746,11 +559,7 @@ export class BreadcrumbsState {
activeCrumbs: state.activeCrumbs,
previusMenuPath: state.previusMenuPath,
});
this.syncApiState(
ctx.getState().processesBreadcrumbs,
state.activeCrumbs,
state.previusMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, state.activeCrumbs, state.previusMenuPath);
}
deleteLastBreadcrumbForProcess(
@@ -772,13 +581,7 @@ export class BreadcrumbsState {
previusMenuPath[processId].length &&
previusMenuPath[processId][previusMenuPath[processId].length - 1]
) {
this.store.dispatch(
new ChangeCurrentRoute(
previusMenuPath[processId][
previusMenuPath[processId].length - 1
].path
)
);
this.store.dispatch(new ChangeCurrentRoute(previusMenuPath[processId][previusMenuPath[processId].length - 1].path));
}
}
@@ -802,11 +605,8 @@ export class BreadcrumbsState {
if (!currentProcessBreadcrumbs) {
return;
}
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
const breadcrumbs: ProcessBreadcrumbs[] =
currentProcessBreadcrumbs[breadcrumbId];
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
const breadcrumbs: ProcessBreadcrumbs[] = currentProcessBreadcrumbs[breadcrumbId];
if (!currentProcessId || !breadcrumbs) {
return;
}
@@ -838,11 +638,7 @@ export class BreadcrumbsState {
ctx.patchState({
processesBreadcrumbs,
});
this.syncApiState(
processesBreadcrumbs,
state.activeCrumbs,
state.previusMenuPath
);
this.syncApiState(processesBreadcrumbs, state.activeCrumbs, state.previusMenuPath);
}
@Action(actions.UpdateCurrentBreadcrumbName)
@@ -854,9 +650,7 @@ export class BreadcrumbsState {
if (!state) {
return;
}
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
if (!currentProcessId) {
return;
}
@@ -867,41 +661,24 @@ export class BreadcrumbsState {
const currentProcessBreadcrumbs = state.processesBreadcrumbs[activeCrumbs];
const pathData = payload.split('/');
const processBreadcrumbsFound =
currentProcessBreadcrumbs.find(
(t) => t.processId === currentProcessId
) !== undefined;
const processBreadcrumbsFound = currentProcessBreadcrumbs.find((t) => t.processId === currentProcessId) !== undefined;
let processesBreadcrumbs: ProcessBreadcrumbs[];
if (processBreadcrumbsFound) {
processesBreadcrumbs = currentProcessBreadcrumbs.map(
(pb: ProcessBreadcrumbs) => {
if (
pb.processId === currentProcessId &&
pb.breadcrumbs &&
pb.breadcrumbs.length > 0
) {
const newBreadcrumb = {
...pb.breadcrumbs[pb.breadcrumbs.length - 1],
name: payload,
queryParams: queryParams,
};
return this.updateNameAndQueryParams(
newBreadcrumb,
false,
pathData.length > 0 ? pathData[pathData.length - 1] : ''
)(pb);
} else {
return pb;
}
processesBreadcrumbs = currentProcessBreadcrumbs.map((pb: ProcessBreadcrumbs) => {
if (pb.processId === currentProcessId && pb.breadcrumbs && pb.breadcrumbs.length > 0) {
const newBreadcrumb = {
...pb.breadcrumbs[pb.breadcrumbs.length - 1],
name: payload,
queryParams: queryParams,
};
return this.updateNameAndQueryParams(newBreadcrumb, false, pathData.length > 0 ? pathData[pathData.length - 1] : '')(pb);
} else {
return pb;
}
);
});
}
const updatedCrumbs = this.udpatedBreadcrumbsObject(
state.processesBreadcrumbs,
activeCrumbs,
processesBreadcrumbs
);
const updatedCrumbs = this.udpatedBreadcrumbsObject(state.processesBreadcrumbs, activeCrumbs, processesBreadcrumbs);
ctx.setState({
processesBreadcrumbs: {
@@ -917,18 +694,10 @@ export class BreadcrumbsState {
activeCrumbs: state.activeCrumbs,
previusMenuPath: state.previusMenuPath,
});
this.syncApiState(
ctx.getState().processesBreadcrumbs,
state.activeCrumbs,
state.previusMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, state.activeCrumbs, state.previusMenuPath);
}
updateBreadcrumbsForProcess(
payload: string,
currentPath: string,
queryParams: any
): StateOperator<Readonly<ProcessBreadcrumbs>> {
updateBreadcrumbsForProcess(payload: string, currentPath: string, queryParams: any): StateOperator<Readonly<ProcessBreadcrumbs>> {
return (processBreadcrumbs: Readonly<ProcessBreadcrumbs>) => {
const breadcrumbs = updateItem<Breadcrumb>(
(item) => item.path === currentPath,
@@ -946,48 +715,17 @@ export class BreadcrumbsState {
}
@Action(actions.DeleteBreadcrumbsForProcess)
deleteBreadcrumbsForProcess(
ctx: StateContext<BreadcrumbsStateModel>,
{ payload }: actions.DeleteBreadcrumbsForProcess
) {
deleteBreadcrumbsForProcess(ctx: StateContext<BreadcrumbsStateModel>, { payload }: actions.DeleteBreadcrumbsForProcess) {
const state = ctx.getState();
if (state.processesBreadcrumbs) {
const customer = this.deleteBreadsForMenu(
state.processesBreadcrumbs,
'customer',
payload
);
const product = this.deleteBreadsForMenu(
state.processesBreadcrumbs,
'product',
payload
);
const shelf = this.deleteBreadsForMenu(
state.processesBreadcrumbs,
'shelf',
payload
);
const shoppingCart = this.deleteBreadsForMenu(
state.processesBreadcrumbs,
'shoppingCart',
payload
);
const checkout = this.deleteBreadsForMenu(
state.processesBreadcrumbs,
'checkout',
payload
);
const goodsin = this.deleteBreadsForMenu(
state.processesBreadcrumbs,
'goodsin',
payload
);
const remission = this.deleteBreadsForMenu(
state.processesBreadcrumbs,
'remission',
payload
);
const customer = this.deleteBreadsForMenu(state.processesBreadcrumbs, 'customer', payload);
const product = this.deleteBreadsForMenu(state.processesBreadcrumbs, 'product', payload);
const shelf = this.deleteBreadsForMenu(state.processesBreadcrumbs, 'shelf', payload);
const shoppingCart = this.deleteBreadsForMenu(state.processesBreadcrumbs, 'shoppingCart', payload);
const checkout = this.deleteBreadsForMenu(state.processesBreadcrumbs, 'checkout', payload);
const goodsin = this.deleteBreadsForMenu(state.processesBreadcrumbs, 'goodsin', payload);
const remission = this.deleteBreadsForMenu(state.processesBreadcrumbs, 'remission', payload);
/* const taskCalendar = this.deleteBreadsForMenu(
state.processesBreadcrumbs,
'taskCalendar',
@@ -1019,35 +757,20 @@ export class BreadcrumbsState {
activeCrumbs: activeCrumbs,
previusMenuPath: previousMenuPath,
});
this.syncApiState(
ctx.getState().processesBreadcrumbs,
activeCrumbs,
previousMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, activeCrumbs, previousMenuPath);
}
}
private deleteBreadsForMenu(
breds: ProcessBreadcrumb,
menu: string,
processId: number
) {
private deleteBreadsForMenu(breds: ProcessBreadcrumb, menu: string, processId: number) {
const menuBreadcrumbs = breds[menu];
const processesBreadcrumbs = removeItem<ProcessBreadcrumbs>(
(item) => item.processId === processId
)(menuBreadcrumbs);
const processesBreadcrumbs = removeItem<ProcessBreadcrumbs>((item) => item.processId === processId)(menuBreadcrumbs);
return processesBreadcrumbs;
}
@Action(actions.UpdateActiveCrumb)
updateActiveCrumb(
ctx: StateContext<BreadcrumbsStateModel>,
{ crumb }: actions.UpdateActiveCrumb
) {
updateActiveCrumb(ctx: StateContext<BreadcrumbsStateModel>, { crumb }: actions.UpdateActiveCrumb) {
const state = ctx.getState();
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
if (state && currentProcessId) {
let currentProcessActiveCrumbs = {};
@@ -1065,22 +788,14 @@ export class BreadcrumbsState {
}
ctx.patchState({ activeCrumbs: currentProcessActiveCrumbs });
this.syncApiState(
ctx.getState().processesBreadcrumbs,
currentProcessActiveCrumbs,
state.previusMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, currentProcessActiveCrumbs, state.previusMenuPath);
}
}
@Action(actions.ReloadBreadcrumbsData)
reloadBreadcrumbs(
ctx: StateContext<BreadcrumbsStateModel>,
{
processesBreadcrumbs,
activeCrumbs,
previusMenuPath,
}: actions.ReloadBreadcrumbsData
{ processesBreadcrumbs, activeCrumbs, previusMenuPath }: actions.ReloadBreadcrumbsData
) {
const crumbs = {
processesBreadcrumbs: {
@@ -1097,14 +812,8 @@ export class BreadcrumbsState {
previusMenuPath: previusMenuPath,
};
console.log('reloadBreadcrumbs [ctxState]', crumbs);
ctx.setState({ ...crumbs });
this.syncApiState(
ctx.getState().processesBreadcrumbs,
activeCrumbs,
previusMenuPath
);
this.syncApiState(ctx.getState().processesBreadcrumbs, activeCrumbs, previusMenuPath);
}
private syncApiState(
@@ -1118,17 +827,11 @@ export class BreadcrumbsState {
previusMenuPath,
};
console.log('[syncApiState] userSyncData', userSyncData);
const userData = JSON.stringify(userSyncData);
this.store.dispatch(new AppUserDataSync(userData));
}
private udpatedBreadcrumbsObject(
data: ProcessBreadcrumb,
breadcrumbsId: string,
newCrumbs: ProcessBreadcrumbs[]
) {
private udpatedBreadcrumbsObject(data: ProcessBreadcrumb, breadcrumbsId: string, newCrumbs: ProcessBreadcrumbs[]) {
const obj = {};
Object.keys(data).forEach((key) => {
if (key === breadcrumbsId) {

View File

@@ -14,6 +14,7 @@ import {
DestinationDTO,
AddressDTO,
UpdateShoppingCartItemDTO,
ShippingAddressDTO,
} from '@swagger/checkout';
import { ItemDTO } from '@swagger/cat';
import { ProcessSelectors } from '../selectors/process.selectors';
@@ -24,7 +25,7 @@ import { tap, map, flatMap, concatMap, switchMap } from 'rxjs/operators';
import { Observable, of, from } from 'rxjs';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { AppUserDataSync } from '../actions/app.actions';
import { SupplierDTO, ShippingAddressDTO } from '@swagger/oms';
import { SupplierDTO } from '@swagger/oms';
import { isArray } from 'util';
import { User } from '../../models/user.model';
import { CustomerMapping } from '../../mappings/customer.mapping';
@@ -529,11 +530,11 @@ export class CartEntryState {
logistician: { id: availability.logisticianId },
shippingAddress:
customer && addressDto
? <ShippingAddressDTO>{
? ({
firstName: customer.first_name,
lastName: customer.last_name,
address: addressDto,
}
} as ShippingAddressDTO)
: undefined,
},
};

View File

@@ -1,16 +1,26 @@
import { State, Action, StateContext, Store, Selector } from '@ngxs/store';
import * as actions from '../actions/cart.actions';
import { AddCartToProcess, SetFinishedOrderData, ChangeCurrentRoute } from '../actions/process.actions';
import {
AddCartToProcess,
SetFinishedOrderData,
ChangeCurrentRoute,
} from '../actions/process.actions';
import { DeleteCartEntry } from '../actions/cart-entry.actions';
import { ProcessSelectors } from '../selectors/process.selectors';
import { CheckoutService } from '../../services/checkout.service';
import { tap, catchError } from 'rxjs/operators';
import { ShoppingCartDTO, PaymentDTO, CheckoutDTO, BuyerDTO, PayerDTO } from '@swagger/checkout';
import {
ShoppingCartDTO,
PaymentDTO,
CheckoutDTO,
BuyerDTO,
PayerDTO,
DestinationDTO,
} from '@swagger/checkout';
import { DisplayOrderDTO, EnvironmentChannel } from '@swagger/oms';
import { User } from '../../models/user.model';
import { CustomerMapping } from '../../mappings/customer.mapping';
import { OrganisationDTO } from '@swagger/crm';
import { DestinationDTO } from '@swagger/checkout';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { AppUserDataSync } from '../actions/app.actions';
import { of } from 'rxjs';
@@ -48,7 +58,10 @@ export class CartState {
}
@Action(actions.SetCartToProcess)
setCartToProcess(ctx: StateContext<CartStateModel>, {}: actions.SetCartToProcess) {
setCartToProcess(
ctx: StateContext<CartStateModel>,
{}: actions.SetCartToProcess
) {
return this.checkoutService.createCart().pipe(
tap((cart: ShoppingCartDTO) => {
const state = ctx.getState();
@@ -62,28 +75,41 @@ export class CartState {
}
@Action(actions.SetCartData)
addItemToCart(ctx: StateContext<CartStateModel>, { cartEntryId }: actions.SetCartData) {
addItemToCart(
ctx: StateContext<CartStateModel>,
{ cartEntryId }: actions.SetCartData
) {
const state = ctx.getState();
const currentProcess = this.store.selectSnapshot(ProcessSelectors.getCurrentProcess);
const currentProcess = this.store.selectSnapshot(
ProcessSelectors.getCurrentProcess
);
if (!currentProcess) {
return;
}
const carts = { ...state.carts };
carts[currentProcess.cartId] = [...carts[currentProcess.cartId], cartEntryId];
carts[currentProcess.cartId] = [
...carts[currentProcess.cartId],
cartEntryId,
];
ctx.patchState({ carts });
this.syncApiState(carts);
}
@Action(actions.DeleteProductFromCart)
deleteProductFromCart(ctx: StateContext<CartStateModel>, { cartId, cartEntryId }: actions.DeleteProductFromCart) {
deleteProductFromCart(
ctx: StateContext<CartStateModel>,
{ cartId, cartEntryId }: actions.DeleteProductFromCart
) {
const state = ctx.getState();
const allCarts = { ...state.carts };
const cartEntries = allCarts[cartId];
// Remove product from
const productToDelete = cartEntries.find((entryId: number) => entryId === cartEntryId);
const productToDelete = cartEntries.find(
(entryId: number) => entryId === cartEntryId
);
if (productToDelete) {
this.store.dispatch(new DeleteCartEntry(cartEntryId, true));
}
@@ -99,7 +125,10 @@ export class CartState {
}
@Action(actions.DeleteCart)
deleteCart(ctx: StateContext<CartStateModel>, { cartId }: actions.DeleteCart) {
deleteCart(
ctx: StateContext<CartStateModel>,
{ cartId }: actions.DeleteCart
) {
const state = ctx.getState();
const carts = { ...state.carts };
const cartEntries = carts[cartId];
@@ -122,16 +151,26 @@ export class CartState {
}
@Action(actions.CreateOrder)
createOrder(ctx: StateContext<CartStateModel>, { checkoutId }: actions.CreateOrder) {
createOrder(
ctx: StateContext<CartStateModel>,
{ checkoutId }: actions.CreateOrder
) {
return this.checkoutService.createOrder(checkoutId).pipe(
tap((order: { failedItemIds: { id: number; type: number }[]; displayOrder: DisplayOrderDTO[] }) => {
this.store.dispatch(new SetFinishedOrderData(order));
}),
tap(
(order: {
failedItemIds: { id: number; type: number }[];
displayOrder: DisplayOrderDTO[];
}) => {
this.store.dispatch(new SetFinishedOrderData(order));
}
),
catchError((error) => {
const status = error.status;
const message = error && error.error ? error.error.message : undefined;
const invalidProperties =
error && error.error && error.error.invalidProperties ? JSON.stringify(error.error.invalidProperties) : undefined;
error && error.error && error.error.invalidProperties
? JSON.stringify(error.error.invalidProperties)
: undefined;
this.errorService.addErrors(status, message, invalidProperties);
this.openCart();
return of(undefined);
@@ -151,63 +190,104 @@ export class CartState {
}
@Action(actions.SetPaymentTypeToCheckout)
getPaymentToCheckout(ctx: StateContext<CartStateModel>, { checkoutId, customer, isDelivery }: actions.SetPaymentTypeToCheckout) {
getPaymentToCheckout(
ctx: StateContext<CartStateModel>,
{ checkoutId, customer, isDelivery }: actions.SetPaymentTypeToCheckout
) {
return this.checkoutService.getPayments(checkoutId).pipe(
tap((payment: PaymentDTO) => {
const type = payment.availablePaymentTypes[0].key;
this.store.dispatch(new actions.SetPaymentToCheckout(checkoutId, type, customer, isDelivery));
this.store.dispatch(
new actions.SetPaymentToCheckout(
checkoutId,
type,
customer,
isDelivery
)
);
})
);
}
@Action(actions.SetPaymentToCheckout)
setPaymentToCheckout(ctx: StateContext<CartStateModel>, { checkoutId, type, customer, isDelivery }: actions.SetPaymentToCheckout) {
setPaymentToCheckout(
ctx: StateContext<CartStateModel>,
{ checkoutId, type, customer, isDelivery }: actions.SetPaymentToCheckout
) {
return this.checkoutService.setPaymentType(checkoutId, type).pipe(
tap((checkout: CheckoutDTO) => {
this.store.dispatch(new actions.SetBuyerToCheckout(checkout.id, customer, isDelivery));
this.store.dispatch(
new actions.SetBuyerToCheckout(checkout.id, customer, isDelivery)
);
})
);
}
@Action(actions.SetBuyerToCheckout)
setBuyerToCheckout(ctx: StateContext<CartStateModel>, { checkoutId, customer, isDelivery }: actions.SetBuyerToCheckout) {
return this.checkoutService.setBuyer(checkoutId, this.buyerObject(customer)).pipe(
tap((checkout: CheckoutDTO) => {
if (!isDelivery) {
this.store.dispatch(new actions.CreateOrder(checkout.id));
} else {
this.store.dispatch(new actions.SetPayerToCheckout(checkout.id, customer));
}
})
);
setBuyerToCheckout(
ctx: StateContext<CartStateModel>,
{ checkoutId, customer, isDelivery }: actions.SetBuyerToCheckout
) {
return this.checkoutService
.setBuyer(checkoutId, this.buyerObject(customer))
.pipe(
tap((checkout: CheckoutDTO) => {
if (!isDelivery) {
this.store.dispatch(new actions.CreateOrder(checkout.id));
} else {
this.store.dispatch(
new actions.SetPayerToCheckout(checkout.id, customer)
);
}
})
);
}
@Action(actions.SetPayerToCheckout)
setPayerToCheckout(ctx: StateContext<CartStateModel>, { checkoutId, customer }: actions.SetPayerToCheckout) {
return this.checkoutService.setPayer(checkoutId, this.payerObject(customer)).pipe(
tap((checkout: CheckoutDTO) => {
this.store.dispatch(new actions.CreateOrder(checkout.id));
})
);
setPayerToCheckout(
ctx: StateContext<CartStateModel>,
{ checkoutId, customer }: actions.SetPayerToCheckout
) {
return this.checkoutService
.setPayer(checkoutId, this.payerObject(customer))
.pipe(
tap((checkout: CheckoutDTO) => {
this.store.dispatch(new actions.CreateOrder(checkout.id));
})
);
}
@Action(actions.SetDestinationToCheckout)
setDestinationToCheckout(
ctx: StateContext<CartStateModel>,
{ checkoutId, destinationId, customer, target }: actions.SetDestinationToCheckout
{
checkoutId,
destinationId,
customer,
target,
}: actions.SetDestinationToCheckout
) {
const dest = this.destinationObject(customer, destinationId, target);
return this.checkoutService.setDestination(checkoutId, destinationId, dest);
}
@Action(actions.ReloadCartData)
reloadCarts(ctx: StateContext<CartStateModel>, { carts }: actions.ReloadCartData) {
reloadCarts(
ctx: StateContext<CartStateModel>,
{ carts }: actions.ReloadCartData
) {
ctx.setState({ carts: { ...carts } });
}
@Action(actions.SetNotificationChannel)
setNotificationChannel(ctx: StateContext<CartStateModel>, { checkoutId, notificationChannel }: actions.SetNotificationChannel) {
return this.checkoutService.setNotificationChannel(checkoutId, notificationChannel);
setNotificationChannel(
ctx: StateContext<CartStateModel>,
{ checkoutId, notificationChannel }: actions.SetNotificationChannel
) {
return this.checkoutService.setNotificationChannel(
checkoutId,
notificationChannel
);
}
private syncApiState(carts: { [key: number]: number[] }) {
@@ -239,7 +319,9 @@ export class CartState {
}
private payerObject(customer: User) {
const invoice = customer.invoice_address ? customer.invoice_address : customer.delivery_addres;
const invoice = customer.invoice_address
? customer.invoice_address
: customer.delivery_addres;
const transformed = this.customerMapper.fromUser(customer);
let organisation: OrganisationDTO = {};
@@ -274,25 +356,27 @@ export class CartState {
return payerDTO;
}
private destinationObject(customer: User, destinationId: number, target?: EnvironmentChannel) {
private destinationObject(
customer: User,
destinationId: number,
target?: EnvironmentChannel
) {
const delivery = customer.delivery_addres;
if (!delivery) {
this.cartService.addOrderErrorEvent();
throw new Error(
`Delvery address is missing for customer: ${customer.first_name + ' ' + customer.last_name} when trying to set destination object.
`Delvery address is missing for customer: ${
customer.first_name + ' ' + customer.last_name
} when trying to set destination object.
Source => cart.state.ts.destinationObject. Line 238`
);
}
const transformed = this.customerMapper.fromUser(customer);
const shippingAddress = this.customerMapper.fromUserToShippingAddressDTO(
customer
);
const destinationDTO: DestinationDTO = {
shippingAddress: {
gender: this.customerMapper.getGender(delivery.gender),
communicationDetails: transformed.communicationDetails,
firstName: delivery.first_name,
lastName: delivery.last_name,
address: transformed.address,
source: delivery.id,
},
shippingAddress,
target: target ? target : 2,
id: destinationId,
};

View File

@@ -40,7 +40,10 @@ export class CustomerState {
constructor(private store: Store, private customerService: CustomerService) {}
@Action(actions.SearchUser)
searchUser(ctx: StateContext<CustomerStateModel>, { payload }: actions.SearchUser): Observable<User[]> {
searchUser(
ctx: StateContext<CustomerStateModel>,
{ payload }: actions.SearchUser
): Observable<User[]> {
return this.customerService.searchCustomer(payload).pipe(
map((response: CustomerSearchResponse) => response.customers),
tap((_customers: User[]) => {
@@ -55,21 +58,30 @@ export class CustomerState {
customers = { ...customers, [t.id]: t };
});
ctx.patchState({ customers });
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
this.store.dispatch(new AddCustomerIds(customerIds));
})
);
}
@Action(actions.AddCustomers)
addCustomers(ctx: StateContext<CustomerStateModel>, { payload }: actions.AddCustomers) {
addCustomers(
ctx: StateContext<CustomerStateModel>,
{ payload }: actions.AddCustomers
) {
const state = ctx.getState();
if (!state) {
return;
}
const currentCustomers = state.customers;
const newCustomers = payload.filter((t) => t !== undefined);
const currentCustomerIds = this.store.selectSnapshot(ProcessSelectors.getCustomerIds);
const currentCustomerIds = this.store.selectSnapshot(
ProcessSelectors.getCustomerIds
);
const customerIds = [];
let customers = currentCustomers;
if (!!currentCustomerIds) {
@@ -84,12 +96,19 @@ export class CustomerState {
}
});
ctx.patchState({ customers });
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
this.store.dispatch(new AddCustomerIds(customerIds));
}
@Action(actions.SetActiveUser)
setActiveUser(ctx: StateContext<CustomerStateModel>, { payload, cartHasItemForDownload, cartEntriesIds }: actions.SetActiveUser) {
setActiveUser(
ctx: StateContext<CustomerStateModel>,
{ payload, cartHasItemForDownload, cartEntriesIds }: actions.SetActiveUser
) {
const state = ctx.getState();
if (!state) {
return;
@@ -101,14 +120,21 @@ export class CustomerState {
ctx.patchState({ customers });
this.store.dispatch(new SetActiveCustomer(_customer.id));
this.store.dispatch(new UpdateProcessName(_customer.last_name));
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
}
})
);
}
@Action(actions.AddUser)
addUser(ctx: StateContext<CustomerStateModel>, { payload, setActiveUser }: actions.AddUser) {
addUser(
ctx: StateContext<CustomerStateModel>,
{ payload, setActiveUser }: actions.AddUser
) {
const state = ctx.getState();
let customerApi$: Observable<User>;
let isOnline = false;
@@ -126,10 +152,21 @@ export class CustomerState {
}
return customerApi$.pipe(
tap((addedCustomer: User) => {
this.store.dispatch(new SetOnlineCustomerCreationStatus({ error: false, invalidProperties: null }));
this.store.dispatch(
new SetOnlineCustomerCreationStatus({
error: false,
invalidProperties: null,
})
);
if (state.customers[addedCustomer.id] === undefined) {
const customers = { ...state.customers, [addedCustomer.id]: addedCustomer };
ctx.patchState({ customers, lastCreatedCustomerId: addedCustomer.id });
const customers = {
...state.customers,
[addedCustomer.id]: addedCustomer,
};
ctx.patchState({
customers,
lastCreatedCustomerId: addedCustomer.id,
});
}
if (setActiveUser) {
@@ -153,7 +190,8 @@ export class CustomerState {
this.store.dispatch(
new SetOnlineCustomerCreationStatus({
error: true,
invalidProperties: error && error.error ? error.error.invalidProperties : undefined,
invalidProperties:
error && error.error ? error.error.invalidProperties : undefined,
})
);
console.error(error);
@@ -163,24 +201,40 @@ export class CustomerState {
}
@Action(actions.SetUserDetails)
editUser(ctx: StateContext<CustomerStateModel>, { payload, addBreadcrumb }: actions.SetUserDetails) {
editUser(
ctx: StateContext<CustomerStateModel>,
{ payload, addBreadcrumb }: actions.SetUserDetails
) {
const state = ctx.getState();
const id = +payload.id;
if (!state) {
return;
}
if (payload.features && payload.features.findIndex((v) => v.key === 'onlineshop') >= 0) {
const customers = { ...state.customers, [id]: { ...payload, id: id, error: null } };
if (
payload.features &&
payload.features.findIndex((v) => v.key === 'onlineshop') >= 0
) {
const customers = {
...state.customers,
[id]: { ...payload, id: id, error: null },
};
ctx.patchState({ customers });
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
return;
}
return this.customerService.updateCustomer(payload).pipe(
catchError((error) => {
console.error(error);
const customers = { ...state.customers, [id]: { ...state.customers[id], id: id, error: error } };
const customers = {
...state.customers,
[id]: { ...state.customers[id], id: id, error: error },
};
ctx.patchState({ customers });
return of(undefined);
}),
@@ -188,7 +242,10 @@ export class CustomerState {
if (!addedCustomer) {
return;
}
const customers = { ...state.customers, [id]: { ...payload, id: id, error: null } };
const customers = {
...state.customers,
[id]: { ...payload, id: id, error: null },
};
ctx.patchState({ customers });
if (addBreadcrumb) {
this.store.dispatch(
@@ -201,13 +258,20 @@ export class CustomerState {
)
);
}
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
})
);
}
@Action(actions.SetUserDetailsById)
getUserDetails(ctx: StateContext<CustomerStateModel>, { payload, addBreadcrumb }: actions.SetUserDetailsById) {
getUserDetails(
ctx: StateContext<CustomerStateModel>,
{ payload, addBreadcrumb }: actions.SetUserDetailsById
) {
const state = ctx.getState();
if (!state) {
return;
@@ -215,7 +279,12 @@ export class CustomerState {
return this.customerService.getCustomerById(payload).pipe(
tap((user: User) => {
if (user) {
ctx.setState(this.updateCustomer({ ...user, id: +payload }, state.lastCreatedCustomerId));
ctx.setState(
this.updateCustomer(
{ ...user, id: +payload },
state.lastCreatedCustomerId
)
);
}
})
);
@@ -229,7 +298,10 @@ export class CustomerState {
}
@Action(actions.DeleteCustomerForProcess)
deleteCustomerForProcess(ctx: StateContext<CustomerStateModel>, { payload }: actions.DeleteCustomerForProcess) {
deleteCustomerForProcess(
ctx: StateContext<CustomerStateModel>,
{ payload }: actions.DeleteCustomerForProcess
) {
const state = ctx.getState();
if (!state) {
return;
@@ -238,19 +310,29 @@ export class CustomerState {
delete customers[payload];
ctx.patchState({ customers });
this.store.dispatch(new RemoveCustomerId(payload));
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
return this.customerService.deleteCustomer(payload);
}
@Action(actions.SetDefaultShippingAddress)
setDefaultShippingAddress(ctx: StateContext<CustomerStateModel>, { customerId, shippingAddress }: actions.SetDefaultShippingAddress) {
setDefaultShippingAddress(
ctx: StateContext<CustomerStateModel>,
{ customerId, shippingAddress }: actions.SetDefaultShippingAddress
) {
const state = ctx.getState();
if (!state) {
return;
}
const currentCustomers = state.customers;
const shipping$ = this.customerService.setShippingAddressAsDefault(shippingAddress, customerId);
const shipping$ = this.customerService.setShippingAddressAsDefault(
shippingAddress,
customerId
);
if (!shipping$) {
return;
}
@@ -259,23 +341,38 @@ export class CustomerState {
const customer = currentCustomers[customerId];
const updatedCustomer = {
...customer,
delivery_addres: customer.poossible_delivery_addresses.find((t) => t.id === shippingAddress.id),
delivery_addres: customer.poossible_delivery_addresses.find(
(t) => t.id === shippingAddress.id
),
};
const customers = {
...currentCustomers,
[updatedCustomer.id]: updatedCustomer,
};
const customers = { ...currentCustomers, [updatedCustomer.id]: updatedCustomer };
ctx.patchState({ customers });
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
})
);
}
@Action(actions.SetDefaultInvoiceAddress)
setDefaultInvoiceAddress(ctx: StateContext<CustomerStateModel>, { customerId, invoiceAddress }: actions.SetDefaultInvoiceAddress) {
setDefaultInvoiceAddress(
ctx: StateContext<CustomerStateModel>,
{ customerId, invoiceAddress }: actions.SetDefaultInvoiceAddress
) {
const state = ctx.getState();
if (!state) {
return;
}
const currentCustomers = state.customers;
const invoice$ = this.customerService.setInvoiceAddressAsDefault(invoiceAddress, customerId);
const invoice$ = this.customerService.setInvoiceAddressAsDefault(
invoiceAddress,
customerId
);
if (!invoice$) {
return;
}
@@ -284,17 +381,29 @@ export class CustomerState {
const customer = currentCustomers[customerId];
const updatedCustomer = {
...customer,
invoice_address: customer.poossible_invoice_addresses.find((t) => t.id === invoiceAddress.id),
invoice_address: customer.poossible_invoice_addresses.find(
(t) => t.id === invoiceAddress.id
),
};
const customers = {
...currentCustomers,
[updatedCustomer.id]: updatedCustomer,
};
const customers = { ...currentCustomers, [updatedCustomer.id]: updatedCustomer };
ctx.patchState({ customers });
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
})
);
}
@Action(actions.AddNewShippingAddress)
addNewShippingAddress(ctx: StateContext<CustomerStateModel>, { customerId, shippingAddress }: actions.AddNewShippingAddress) {
addNewShippingAddress(
ctx: StateContext<CustomerStateModel>,
{ customerId, shippingAddress }: actions.AddNewShippingAddress
) {
const state = ctx.getState();
if (!state) {
return;
@@ -310,27 +419,53 @@ export class CustomerState {
updatedCustomer = <User>{
...customer,
delivery_addres: address,
poossible_delivery_addresses: [...customer.poossible_delivery_addresses.filter((t) => t.id !== address.id), address],
poossible_invoice_addresses: [...customer.poossible_invoice_addresses.filter((t) => t.id !== address.id), address],
poossible_delivery_addresses: [
...(customer.poossible_delivery_addresses.filter(
(t) => t.id !== address.id
) || []),
address,
],
poossible_invoice_addresses: [
...(customer.poossible_invoice_addresses.filter(
(t) => t.id !== address.id
) || []),
address,
],
};
} else {
updatedCustomer = <User>{
...customer,
delivery_addres: address,
poossible_delivery_addresses: [...customer.poossible_delivery_addresses, address],
poossible_invoice_addresses: [...customer.poossible_invoice_addresses, address],
poossible_delivery_addresses: [
...(customer.poossible_delivery_addresses || []),
address,
],
poossible_invoice_addresses: [
...(customer.poossible_invoice_addresses || []),
address,
],
};
}
this.store.dispatch(new actions.SetUserDetailsById(customerId));
const customers = { ...currentCustomers, [updatedCustomer.id]: updatedCustomer };
const customers = {
...currentCustomers,
[updatedCustomer.id]: updatedCustomer,
};
ctx.patchState({ customers });
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
})
);
}
@Action(actions.AddNewInvoiceAddress)
addNewInvoiceAddress(ctx: StateContext<CustomerStateModel>, { customerId, invoiceAddress }: actions.AddNewInvoiceAddress) {
addNewInvoiceAddress(
ctx: StateContext<CustomerStateModel>,
{ customerId, invoiceAddress }: actions.AddNewInvoiceAddress
) {
const state = ctx.getState();
if (!state) {
return;
@@ -346,27 +481,53 @@ export class CustomerState {
updatedCustomer = <User>{
...customer,
invoice_address: address,
poossible_delivery_addresses: [...customer.poossible_delivery_addresses.filter((t) => t && t.id !== address.id), address],
poossible_invoice_addresses: [...customer.poossible_invoice_addresses.filter((t) => t && t.id !== address.id), address],
poossible_delivery_addresses: [
...(customer.poossible_delivery_addresses.filter(
(t) => t && t.id !== address.id
) || []),
address,
],
poossible_invoice_addresses: [
...(customer.poossible_invoice_addresses.filter(
(t) => t && t.id !== address.id
) || []),
address,
],
};
} else {
updatedCustomer = <User>{
...customer,
invoice_address: address,
poossible_delivery_addresses: [...customer.poossible_delivery_addresses, address],
poossible_invoice_addresses: [...customer.poossible_invoice_addresses, address],
poossible_delivery_addresses: [
...(customer.poossible_delivery_addresses || []),
address,
],
poossible_invoice_addresses: [
...(customer.poossible_invoice_addresses || []),
address,
],
};
}
this.store.dispatch(new actions.SetUserDetailsById(customerId));
const customers = { ...currentCustomers, [updatedCustomer.id]: updatedCustomer };
const customers = {
...currentCustomers,
[updatedCustomer.id]: updatedCustomer,
};
ctx.patchState({ customers });
this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
this.syncApiState(
customers,
state.lastCreatedCustomerId,
state.cachedCustomerSearch
);
})
);
}
@Action(actions.SetLastCreatedCustomerId)
setLastCreatedCustomerId(ctx: StateContext<CustomerStateModel>, { customerId }: actions.SetLastCreatedCustomerId) {
setLastCreatedCustomerId(
ctx: StateContext<CustomerStateModel>,
{ customerId }: actions.SetLastCreatedCustomerId
) {
const state = ctx.getState();
if (!state) {
return;
@@ -376,30 +537,45 @@ export class CustomerState {
}
@Action(actions.SetCachedCustomers)
setCachedCustomers(ctx: StateContext<CustomerStateModel>, { cachedCustomerSearch }: actions.SetCachedCustomers) {
setCachedCustomers(
ctx: StateContext<CustomerStateModel>,
{ cachedCustomerSearch }: actions.SetCachedCustomers
) {
const state = ctx.getState();
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
if (currentProcessId) {
const cachedCustomers = { ...state.cachedCustomerSearch };
cachedCustomers[currentProcessId] = cachedCustomerSearch;
ctx.patchState({ cachedCustomerSearch: cachedCustomers });
this.syncApiState(state.customers, state.lastCreatedCustomerId, cachedCustomers);
this.syncApiState(
state.customers,
state.lastCreatedCustomerId,
cachedCustomers
);
}
}
@Action(actions.ClearCachedCustomers)
clearCachedCustomers(ctx: StateContext<CustomerStateModel>) {
const state = ctx.getState();
const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
const currentProcessId = this.store.selectSnapshot(
AppState.getCurrentProcessId
);
if (currentProcessId) {
const cachedCustomers = { ...state.cachedCustomerSearch };
cachedCustomers[currentProcessId] = undefined;
ctx.patchState({ cachedCustomerSearch: cachedCustomers });
this.syncApiState(state.customers, state.lastCreatedCustomerId, cachedCustomers);
this.syncApiState(
state.customers,
state.lastCreatedCustomerId,
cachedCustomers
);
}
}
@@ -414,7 +590,11 @@ export class CustomerState {
@Action(actions.ReloadCustomersData)
reloadProcesses(
ctx: StateContext<CustomerStateModel>,
{ customers, lastCreatedCustomerId, cachedCustomerSearch }: actions.ReloadCustomersData
{
customers,
lastCreatedCustomerId,
cachedCustomerSearch,
}: actions.ReloadCustomersData
) {
ctx.patchState({ customers, lastCreatedCustomerId, cachedCustomerSearch });
}

View File

@@ -45,9 +45,9 @@ const errorWhiteList: { url: string; codes: number[] }[] = [
-1,
],
},
{ url: '/customer', codes: [400, 404, 409, 500] },
{ url: '/customer', codes: [400, 409, 500] },
{ url: '/bonuscard', codes: [404] },
{ url: '/order/', codes: [400, 409, 500] },
{ url: '/order/', codes: [400, 404, 409, 500] },
{ url: '/store/customer/emailexists', codes: [400, 409, 500] },
{
url: '/receipt/order/',

View File

@@ -0,0 +1,6 @@
export function toDecimalPlaces(
value: number,
decimalPlaces: number = 2
): string {
return Number(value).toFixed(decimalPlaces);
}

View File

@@ -32,6 +32,10 @@ export function getFormatedPublicationDate(publicationDate: Date): string {
);
}
export function allowedAvailabilityStatusCodes(code: number) {
return [2, 32, 256, 1024, 2048, 4096].findIndex(allowedCode => allowedCode === code) !== -1;
export function allowedAvailabilityStatusCodes(code: number): boolean {
return (
[2, 32, 256, 1024, 2048, 4096].findIndex(
(allowedCode) => allowedCode === code
) !== -1
);
}

View File

@@ -1,7 +1,6 @@
import {
Component,
OnInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
OnDestroy,
Output,
@@ -25,8 +24,12 @@ import { AddBreadcrumb } from '../../../../core/store/actions/breadcrumb.actions
import { SharedSelectors } from '../../../../core/store/selectors/shared.selectors';
import { Observable, Subject } from 'rxjs';
import { ProcessCart } from '../../../../core/models/process-cart.model';
import { map, filter, take, takeUntil } from 'rxjs/operators';
import { deliveryFilter, CartReviewItem, cartToCartReviewArray } from '../../pages/cart-review/cart-review.component';
import { map, filter, takeUntil } from 'rxjs/operators';
import {
deliveryFilter,
CartReviewItem,
cartToCartReviewArray,
} from '../../pages/cart-review/cart-review.component';
import { Address } from '../../../../core/models/user.model';
import { isArrayMinLength } from '../../../../core/utils/app.utils';
import { DeleteProductFromCart } from 'apps/sales/src/app/core/store/actions/cart.actions';
@@ -49,7 +52,11 @@ export class DeliveryCartComponent implements OnInit, OnDestroy {
lastDeletedItemIndex = -1;
destroy$ = new Subject();
constructor(private store: Store, private router: Router, private cdr: ChangeDetectorRef) {}
constructor(
private store: Store,
private router: Router,
private cdr: ChangeDetectorRef
) {}
ngOnInit() {
this.delivery$ = this.cartData$.pipe(
@@ -87,7 +94,14 @@ export class DeliveryCartComponent implements OnInit, OnDestroy {
}
const customer = cart.customer;
if (customer.delivery_addres) {
const { first_name, last_name, street, streetNo, zip, city } = customer.delivery_addres;
const {
first_name,
last_name,
street,
streetNo,
zip,
city,
} = customer.delivery_addres;
this.deliveryAddress = `${first_name} ${last_name} | ${street} ${streetNo}, ${zip} ${city}`;
}
this.detectChanges();
@@ -111,7 +125,9 @@ export class DeliveryCartComponent implements OnInit, OnDestroy {
this.store.dispatch(new SetDetailsCustomer(customerId));
this.store.dispatch(new ChangeCurrentRoute(currentRoute));
this.store.dispatch(new AddBreadcrumb(newBread, 'shoppingCart'));
this.router.navigate([currentRoute], { queryParams: { cart: true, notEditable: true } });
this.router.navigate([currentRoute], {
queryParams: { cart: true, notEditable: true },
});
}
}
@@ -120,7 +136,10 @@ export class DeliveryCartComponent implements OnInit, OnDestroy {
this.store.dispatch(
new AddBreadcrumb(
{
name: item.product.name ? item.product.name.substring(0, 12) + (item.product.name.length > 12 ? '...' : '') : '',
name: item.product.name
? item.product.name.substring(0, 12) +
(item.product.name.length > 12 ? '...' : '')
: '',
path: '/product/details/' + item.id,
},
'product'
@@ -133,12 +152,18 @@ export class DeliveryCartComponent implements OnInit, OnDestroy {
}
validateDeliveryAddress(processCart) {
const deliveryExist = processCart.cart.some((item: Cart) => item.deliveryType === DeliveryOption.DELIVERY);
const deliveryExist = processCart.cart.some(
(item: Cart) => item.deliveryType === DeliveryOption.DELIVERY
);
const missingAddress =
processCart.customer && (!this.isAddressSet(processCart.customer.delivery_addres) || !processCart.customer.email);
processCart.customer &&
(!this.isAddressSet(processCart.customer.delivery_addres) ||
!processCart.customer.email);
if (deliveryExist && missingAddress) {
this.store.dispatch(new UpdateCustomerFormState(CustomerFormState.MISSING_DELIVERY));
this.store.dispatch(
new UpdateCustomerFormState(CustomerFormState.MISSING_DELIVERY)
);
const currentRoute = `customer/search/missing-data/${processCart.customer.id}`;
this.store.dispatch(new ChangeCurrentRoute(currentRoute));
this.router.navigate([currentRoute], { queryParams: { card: 'create' } });
@@ -169,20 +194,29 @@ export class DeliveryCartComponent implements OnInit, OnDestroy {
}
getDeleteAnimation(index: number) {
const animation = index >= this.lastDeletedItemIndex && this.lastDeletedItemIndex !== -1 ? 'delete' : '';
const animation =
index >= this.lastDeletedItemIndex && this.lastDeletedItemIndex !== -1
? 'delete'
: '';
return animation;
}
deleteItem(item: CartReviewItem, index: number) {
this.lastDeletedItemIndex = index;
if (item) {
this.store.dispatch(new DeleteProductFromCart(item.cartId, item.cartEntryId));
this.store.dispatch(
new DeleteProductFromCart(item.cartId, item.cartEntryId)
);
}
}
detectChanges() {
setTimeout(() => {
if (this.cdr !== null && this.cdr !== undefined && !(this.cdr as ViewRef).destroyed) {
if (
this.cdr !== null &&
this.cdr !== undefined &&
!(this.cdr as ViewRef).destroyed
) {
this.cdr.detectChanges();
}
}, 0);

View File

@@ -1,7 +1,10 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { ModalService, ButtonComponent } from '@libs/ui';
import { Breadcrumb } from '../../../../core/models/breadcrumb.model';
import { ChangeCurrentRoute, EmptyFinishedOrders } from '../../../../core/store/actions/process.actions';
import {
ChangeCurrentRoute,
EmptyFinishedOrders,
} from '../../../../core/store/actions/process.actions';
import { Store, Select } from '@ngxs/store';
import { Router } from '@angular/router';
import { AddBreadcrumb } from '../../../../core/store/actions/breadcrumb.actions';
@@ -10,8 +13,15 @@ import { ProcessSelectors } from '../../../../core/store/selectors/process.selec
import { Observable, Subject, of, forkJoin } from 'rxjs';
import { takeUntil, switchMap, filter, map } from 'rxjs/operators';
import { CheckoutService } from '../../../../core/services/checkout.service';
import { CheckoutDTO, EntityDTOContainerOfDestinationDTO } from '@swagger/checkout';
import { SetPaymentTypeToCheckout, SetDestinationToCheckout, SetNotificationChannel } from '../../../../core/store/actions/cart.actions';
import {
CheckoutDTO,
EntityDTOContainerOfDestinationDTO,
} from '@swagger/checkout';
import {
SetPaymentTypeToCheckout,
SetDestinationToCheckout,
SetNotificationChannel,
} from '../../../../core/store/actions/cart.actions';
import { User } from '../../../../core/models/user.model';
import { CustomerSelectors } from 'apps/sales/src/app/core/store/selectors/customer.selectors';
import { isNullOrUndefined } from 'util';
@@ -99,19 +109,31 @@ export class PayMethodComponent implements OnInit, OnDestroy {
return this.checkoutService.createCheckout(this.cartId).pipe(
takeUntil(this.destroy$),
switchMap((checkout: CheckoutDTO) => {
return this.setNotificationChannel(checkout, this.customer, this.delivery || this.download);
return this.setNotificationChannel(
checkout,
this.customer,
this.delivery || this.download
);
}),
switchMap((response) => {
if (this.delivery || this.download) {
const deliveryShippingDestionation = response.checkout.destinations.find(
(dest: EntityDTOContainerOfDestinationDTO) => this.delivery && dest.data.target === 2
(dest: EntityDTOContainerOfDestinationDTO) =>
this.delivery && dest.data.target === 2
);
const downloadShippingDestionation = response.checkout.destinations.find(
(dest: EntityDTOContainerOfDestinationDTO) => this.download && dest.data.target === 16
(dest: EntityDTOContainerOfDestinationDTO) =>
this.download && dest.data.target === 16
);
if (!deliveryShippingDestionation && !downloadShippingDestionation) {
return forkJoin(
this.store.dispatch(new SetPaymentTypeToCheckout(response.checkout.id, this.customer, response.delivery)),
this.store.dispatch(
new SetPaymentTypeToCheckout(
response.checkout.id,
this.customer,
response.delivery
)
),
of({
checkout: response.checkout,
delivery: response.delivery,
@@ -127,7 +149,7 @@ export class PayMethodComponent implements OnInit, OnDestroy {
new SetDestinationToCheckout(
response.checkout.id,
deliveryShippingDestionation.id,
response.customer,
this.customer,
deliveryShippingDestionation.data.target
)
),
@@ -139,13 +161,16 @@ export class PayMethodComponent implements OnInit, OnDestroy {
downloadDestinationDTO: undefined,
})
);
} else if (!deliveryShippingDestionation && downloadShippingDestionation) {
} else if (
!deliveryShippingDestionation &&
downloadShippingDestionation
) {
return forkJoin(
this.store.dispatch(
new SetDestinationToCheckout(
response.checkout.id,
downloadShippingDestionation.id,
response.customer,
this.customer,
downloadShippingDestionation.data.target
)
),
@@ -157,13 +182,16 @@ export class PayMethodComponent implements OnInit, OnDestroy {
downloadDestinationDTO: undefined,
})
);
} else if (deliveryShippingDestionation && downloadShippingDestionation) {
} else if (
deliveryShippingDestionation &&
downloadShippingDestionation
) {
return forkJoin(
this.store.dispatch(
new SetDestinationToCheckout(
response.checkout.id,
deliveryShippingDestionation.id,
response.customer,
this.customer,
deliveryShippingDestionation.data.target
)
),
@@ -177,7 +205,13 @@ export class PayMethodComponent implements OnInit, OnDestroy {
);
} else {
return forkJoin(
this.store.dispatch(new SetPaymentTypeToCheckout(response.checkout.id, this.customer, response.delivery)),
this.store.dispatch(
new SetPaymentTypeToCheckout(
response.checkout.id,
this.customer,
response.delivery
)
),
of({
checkout: response.checkout,
delivery: response.delivery,
@@ -189,7 +223,13 @@ export class PayMethodComponent implements OnInit, OnDestroy {
}
} else {
return forkJoin(
this.store.dispatch(new SetPaymentTypeToCheckout(response.checkout.id, this.customer, response.delivery)),
this.store.dispatch(
new SetPaymentTypeToCheckout(
response.checkout.id,
this.customer,
response.delivery
)
),
of({
checkout: response.checkout,
delivery: response.delivery,
@@ -202,8 +242,19 @@ export class PayMethodComponent implements OnInit, OnDestroy {
}),
switchMap(([, data]) => {
if (data.finished) {
return forkJoin(of({}), of({ checkout: data.checkout, delivery: data.delivery, finished: true }));
} else if (!data.finished && data.downloadDestinationNedded && !isNullOrUndefined(data.downloadDestinationDTO)) {
return forkJoin(
of({}),
of({
checkout: data.checkout,
delivery: data.delivery,
finished: true,
})
);
} else if (
!data.finished &&
data.downloadDestinationNedded &&
!isNullOrUndefined(data.downloadDestinationDTO)
) {
return forkJoin(
this.store.dispatch(
new SetDestinationToCheckout(
@@ -213,12 +264,26 @@ export class PayMethodComponent implements OnInit, OnDestroy {
data.downloadDestinationDTO.target
)
),
of({ checkout: data.checkout, delivery: data.delivery, finished: false })
of({
checkout: data.checkout,
delivery: data.delivery,
finished: false,
})
);
} else {
return forkJoin(
this.store.dispatch(new SetPaymentTypeToCheckout(data.checkout.id, this.customer, data.delivery)),
of({ checkout: data.checkout, delivery: data.delivery, finished: true })
this.store.dispatch(
new SetPaymentTypeToCheckout(
data.checkout.id,
this.customer,
data.delivery
)
),
of({
checkout: data.checkout,
delivery: data.delivery,
finished: true,
})
);
}
}),
@@ -226,7 +291,13 @@ export class PayMethodComponent implements OnInit, OnDestroy {
if (data.finished) {
return of({});
} else {
return this.store.dispatch(new SetPaymentTypeToCheckout(data.checkout.id, this.customer, data.delivery));
return this.store.dispatch(
new SetPaymentTypeToCheckout(
data.checkout.id,
this.customer,
data.delivery
)
);
}
})
);
@@ -251,13 +322,17 @@ export class PayMethodComponent implements OnInit, OnDestroy {
customer: User,
delivery: boolean
): Observable<{ checkout: CheckoutDTO; customer: User; delivery: boolean }> {
const user: User = this.store.selectSnapshot(CustomerSelectors.getCurrentProcessActiveUser);
const notificationChannel = this.customerMap.getNotificationChannels(user);
return this.store.dispatch(new SetNotificationChannel(checkout.id, notificationChannel)).pipe(
map(() => {
return { checkout, customer, delivery };
})
const user: User = this.store.selectSnapshot(
CustomerSelectors.getCurrentProcessActiveUser
);
const notificationChannel = this.customerMap.getNotificationChannels(user);
return this.store
.dispatch(new SetNotificationChannel(checkout.id, notificationChannel))
.pipe(
map(() => {
return { checkout, customer, delivery };
})
);
}
ngOnDestroy() {

View File

@@ -13,7 +13,9 @@
<div class="cart-content">
<div class="cart-list">
<hr />
<app-invoice-address [disableAddressChanging]="disableAddressChanging"></app-invoice-address>
<app-invoice-address
[disableAddressChanging]="disableAddressChanging"
></app-invoice-address>
<hr />
<app-notification-settings
*ngIf="showNotificationSettings"
@@ -22,33 +24,65 @@
></app-notification-settings>
<div *ngIf="takeNowCount$ | async" @removeItemCartTrigger>
<hr />
<app-take-now-cart (editItem)="enableDisableOrder($event)"></app-take-now-cart>
<app-take-now-cart
(editItem)="enableDisableOrder($event)"
></app-take-now-cart>
</div>
<div *ngIf="deliveryCount$ | async" @removeItemCartTrigger [@cartPaymentType]="getDeliveryDeleteAnimation()">
<div
*ngIf="deliveryCount$ | async"
@removeItemCartTrigger
[@cartPaymentType]="getDeliveryDeleteAnimation()"
>
<hr />
<app-delivery-cart [disableAddressChanging]="disableAddressChanging" (editItem)="enableDisableOrder($event)"></app-delivery-cart>
<app-delivery-cart
[disableAddressChanging]="disableAddressChanging"
(editItem)="enableDisableOrder($event)"
></app-delivery-cart>
</div>
<div *ngIf="pickUpCount$ | async" @removeItemCartTrigger [@cartPaymentType]="getPickUpDeleteAnimation()">
<div
*ngIf="pickUpCount$ | async"
@removeItemCartTrigger
[@cartPaymentType]="getPickUpDeleteAnimation()"
>
<hr />
<app-pick-up-cart (editItem)="enableDisableOrder($event)"></app-pick-up-cart>
<app-pick-up-cart
(editItem)="enableDisableOrder($event)"
></app-pick-up-cart>
</div>
<div *ngIf="downloadCount$ | async" @removeItemCartTrigger>
<hr />
<app-download-cart (editItem)="enableDisableOrder($event)"></app-download-cart>
<app-download-cart
(editItem)="enableDisableOrder($event)"
></app-download-cart>
</div>
</div>
</div>
<div class="cart-footer">
<div class="overview-container">
<span class="items">{{ total.items }} Artikel I {{ (cartData$ | async)?.promotionPoints }} Lesepunkte</span>
<span class="items"
>{{ total.items }} Artikel I
{{ (cartData$ | async)?.promotionPoints }} Lesepunkte</span
>
<div class="overview">
<div class="overview-price-container">
<span class="overview-price">Zwischensumme {{ total.price | bookPrice }} EUR</span>
<span *ngIf="(deliveryCount$ | async) > 0" class="overview-tax">ohne Versandkosten</span>
<span *ngIf="(deliveryCount$ | async) === 0" class="overview-tax">&nbsp;</span>
<span class="overview-price"
>Zwischensumme {{ total.price | bookPrice }} EUR</span
>
<span *ngIf="(deliveryCount$ | async) > 0" class="overview-tax"
>ohne Versandkosten</span
>
<span *ngIf="(deliveryCount$ | async) === 0" class="overview-tax"
>&nbsp;</span
>
</div>
<app-button (action)="next()" [disabled]="orderDisabled" [primary]="true" [load]="true" [stayOnPage]="true" #continue
<app-button
(action)="next()"
[disabled]="orderDisabled"
[primary]="true"
[load]="true"
[stayOnPage]="true"
#continue
>Bestellen</app-button
>
</div>
@@ -58,18 +92,25 @@
<ng-template #emptyCart>
<div class="cart-container-empty">
<div class="image-empty"><img src="assets/images/shopping_cart_white.svg" /></div>
<div class="image-empty">
<img src="assets/images/shopping_cart_white.svg" />
</div>
<div class="header-empty"><span>Ihr Warenkorb ist leer.</span></div>
<div class="content-empty"><span>Sie haben alle Artikel aus dem</span></div>
<div class="content-empty"><span>Warenkorb entfernt oder noch</span></div>
<div class="content-empty"><span>Artikel hinzugefügt.</span></div>
<div class="btn-empty">
<app-button [primary]="true" (action)="search()">Artikel suchen</app-button>
<app-button [primary]="true" (action)="search()"
>Artikel suchen</app-button
>
</div>
</div>
</ng-template>
<app-printer-selection #printModal (print)="print($event)"></app-printer-selection>
<app-printer-selection
#printModal
(print)="print($event)"
></app-printer-selection>
<app-pay-method
#payMethod

View File

@@ -133,7 +133,7 @@ export class CreateCustomerCardComponent implements OnInit, OnDestroy {
cartHasItemsForDownload = false;
guestChecked = false;
onlineChecked = false;
guestDisabled = true;
guestDisabled = false;
customerCardChecked = false;
deliveryAndGuestOrOnline = false;
pickUpOrTakeNowAndOnline = false;
@@ -261,31 +261,48 @@ export class CreateCustomerCardComponent implements OnInit, OnDestroy {
this.cartHasItemsForTakeNow = cartHasItemsFor(DeliveryOption.TAKE_NOW);
this.cartHasItemsForDownload = cartHasItemsFor(DeliveryOption.DOWNLOAD);
(this.cartHasItemsForDownload || this.cartHasItemsForDelivery).ifTrue(
() => {
this.onlineChecked = true;
this.guestChecked = false;
this.flagsSetByConditions = true;
this.cartHasItemsForDownload.ifTrue(() => {
this.onlineChecked = true;
this.guestDisabled = true;
this.guestChecked = false;
this.flagsSetByConditions = true;
if (this.cartHasItemsForDownload) {
this.headerText =
'Um Ihnen das ebook bereitstellen zu können, brauchen Sie ein Onlinekonto bei Hugendubel. Wir richten es Ihnen gerne sofort ein.';
}
}
);
this.headerText =
'Um Ihnen das ebook bereitstellen zu können, brauchen Sie ein Onlinekonto bei Hugendubel. Wir richten es Ihnen gerne sofort ein.';
(
this.cartHasItemsForDelivery &&
!this.cartHasItemsForDownload &&
!this.onlineChecked
).ifTrue(this.guestCheckboxInitialization);
return;
});
// Ruecklage
if (this.cartHasItemsForTakeNow) {
this.disableGuestCheckbox();
}
// Abholung
if (this.cartHasItemsForPickUp) {
this.disableGuestCheckbox();
}
// Versand
if (this.cartHasItemsForDelivery) {
this.guestCheckboxInitialization();
}
this.detectChanges();
};
disableGuestCheckbox = () => {
this.guestDisabled = true;
this.flagsSetByConditions = true;
};
guestCheckboxInitialization = () => {
this.guestChecked = true;
this.onlineChecked = false;
this.guestDisabled = false;
this.flagsSetByConditions = true;
if (!this.guestChecked && !this.onlineChecked) {
this.guestChecked = true;
}
};
initializationSubscriptionHandler = (process: Process) => {
@@ -308,7 +325,11 @@ export class CreateCustomerCardComponent implements OnInit, OnDestroy {
this.formState = process.customerFormState;
this.canBeSetToActiveUser = !process.closeDirectlyTab;
this.customerFormState = process.customerFormState;
if (process.customerFormState === CustomerFormState.MISSING_DELIVERY) {
if (
process.customerFormState === CustomerFormState.MISSING_DELIVERY &&
this.customer.customer &&
this.customer.customer.hasOnlineAccount
) {
this.buildSetDeliveryAddressForm(this.fb, this.customer.customer);
this.customerHelper.customerUpdateRedirect();
this.id = this.customer.customer.id;
@@ -316,7 +337,9 @@ export class CreateCustomerCardComponent implements OnInit, OnDestroy {
this.btnText = 'Weiter';
this.create = false;
} else if (
process.customerFormState === CustomerFormState.MISSING_ONLINE
process.customerFormState === CustomerFormState.MISSING_ONLINE ||
(process.customerFormState === CustomerFormState.MISSING_DELIVERY &&
!this.customer.customer.hasOnlineAccount)
) {
this.buildSetDeliveryAddressForm(this.fb, this.customer.customer);
this.customerHelper.customerUpdateRedirect();

View File

@@ -1,14 +1,36 @@
import { Store, Select } from '@ngxs/store';
import { Component, OnInit, OnDestroy, Input, ViewChild, ChangeDetectorRef } from '@angular/core';
import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms';
import {
Component,
OnInit,
OnDestroy,
Input,
ViewChild,
ChangeDetectorRef,
} from '@angular/core';
import {
FormGroup,
FormBuilder,
Validators,
AbstractControl,
} from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { Subject, Observable, of } from 'rxjs';
import { SharedSelectors } from '../../../../core/store/selectors/shared.selectors';
import { EditCustomerData } from '../../../../core/models/edit-customer.model';
import { takeUntil, distinctUntilChanged, take, map, filter, switchMap } from 'rxjs/operators';
import {
takeUntil,
distinctUntilChanged,
take,
map,
filter,
switchMap,
} from 'rxjs/operators';
import { User, Address } from '../../../../core/models/user.model';
import { ChangeCurrentRoute } from 'apps/sales/src/app/core/store/actions/process.actions';
import { AddNewShippingAddress, AddNewInvoiceAddress } from 'apps/sales/src/app/core/store/actions/customer.actions';
import {
AddNewShippingAddress,
AddNewInvoiceAddress,
} from 'apps/sales/src/app/core/store/actions/customer.actions';
import { GENDERS, TIITLES } from '../../dropdown-values';
import { CountrySelector } from 'apps/sales/src/app/core/store/selectors/countries.selector';
import { Country } from 'apps/sales/src/app/core/models/country.model';
@@ -35,9 +57,14 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
@Input() title: string;
@Input() addressId: string;
@Input() isBillingForm = true;
@Select(SharedSelectors.getCustomerEditData) customerEditData$: Observable<EditCustomerData>;
@Select(CountrySelector.getCountriesIterable) countries$: Observable<Country[]>;
@ViewChild('suggestions', { static: false }) suggestions: AddressSugestionsComponent;
@Select(SharedSelectors.getCustomerEditData) customerEditData$: Observable<
EditCustomerData
>;
@Select(CountrySelector.getCountriesIterable) countries$: Observable<
Country[]
>;
@ViewChild('suggestions', { static: false })
suggestions: AddressSugestionsComponent;
@ViewChild('postBtn', { static: false }) postBtn: ButtonComponent;
form: FormGroup;
submitted = false;
@@ -74,7 +101,7 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
private cdrf: ChangeDetectorRef,
private errorService: ErrorService,
private route: ActivatedRoute
) { }
) {}
ngOnInit(): void {
if (this.customerInput) {
@@ -82,9 +109,13 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
this.customer = this.customerInput;
if (this.isBillingForm) {
this.selecetedAddress = this.customerInput.poossible_invoice_addresses.find((address: Address) => address.id === +this.addressId);
this.selecetedAddress = this.customerInput.poossible_invoice_addresses.find(
(address: Address) => address.id === +this.addressId
);
} else {
this.selecetedAddress = this.customerInput.poossible_delivery_addresses.find((address: Address) => address.id === +this.addressId);
this.selecetedAddress = this.customerInput.poossible_delivery_addresses.find(
(address: Address) => address.id === +this.addressId
);
}
this.buildForm(this.fb, this.selecetedAddress);
this.onFormChange();
@@ -113,11 +144,13 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
this.countryList = data;
});
this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((queryParams) => {
if (queryParams && queryParams['notEditable']) {
this.notEditable = true;
}
});
this.route.queryParams
.pipe(takeUntil(this.destroy$))
.subscribe((queryParams) => {
if (queryParams && queryParams['notEditable']) {
this.notEditable = true;
}
});
this.setCompanyValidators();
}
@@ -126,7 +159,9 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
// store address form data on changes
this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((v) => {
const formStateString = JSON.stringify(v);
this.store.dispatch(new SaveFormState(ADDRESS_FORM_STATE_KEY, formStateString));
this.store.dispatch(
new SaveFormState(ADDRESS_FORM_STATE_KEY, formStateString)
);
});
}
@@ -155,8 +190,15 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
if (key !== 'FirstName' && key !== 'LastName') {
showErrorPopUp = true;
}
if (keysChecked === Object.keys(invalidProperties).length && showErrorPopUp) {
this.errorService.addErrors(409, null, JSON.stringify(invalidProperties));
if (
keysChecked === Object.keys(invalidProperties).length &&
showErrorPopUp
) {
this.errorService.addErrors(
409,
null,
JSON.stringify(invalidProperties)
);
}
});
scrollToFirstInvalidElement();
@@ -216,7 +258,12 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
}
if (this.isBillingForm) {
this.store
.dispatch(new AddNewInvoiceAddress(updatedCustomer.id, updatedCustomer.invoice_address))
.dispatch(
new AddNewInvoiceAddress(
updatedCustomer.id,
updatedCustomer.invoice_address
)
)
.pipe(
takeUntil(this.destroy$),
switchMap(() => {
@@ -228,16 +275,25 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
this.addAddressErrorHandler(invalidProperties);
this.postBtn.stopLoading();
} else {
const queryParams = this.notEditable ? { notEditable: true } : undefined;
const queryParams = this.notEditable
? { notEditable: true }
: undefined;
const currentRoute = `customer/edit/${this.customer.id}/billing`;
this.store.dispatch(new ChangeCurrentRoute(currentRoute, false, queryParams));
this.store.dispatch(
new ChangeCurrentRoute(currentRoute, false, queryParams)
);
this.postBtn.stopLoading();
this.router.navigate([currentRoute], { queryParams: queryParams });
}
});
} else {
this.store
.dispatch(new AddNewShippingAddress(updatedCustomer.id, updatedCustomer.delivery_addres))
.dispatch(
new AddNewShippingAddress(
updatedCustomer.id,
updatedCustomer.delivery_addres
)
)
.pipe(
takeUntil(this.destroy$),
switchMap(() => {
@@ -249,9 +305,13 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
this.addAddressErrorHandler(invalidProperties);
this.postBtn.stopLoading();
} else {
const queryParams = this.notEditable ? { notEditable: true } : undefined;
const queryParams = this.notEditable
? { notEditable: true }
: undefined;
const currentRoute = `customer/edit/${this.customer.id}/delivery`;
this.store.dispatch(new ChangeCurrentRoute(currentRoute, false, queryParams));
this.store.dispatch(
new ChangeCurrentRoute(currentRoute, false, queryParams)
);
this.postBtn.stopLoading();
this.router.navigate([currentRoute], { queryParams: queryParams });
}
@@ -324,7 +384,9 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
}
countryKey(country: string) {
return this.store.selectSnapshot(CountrySelector.getCountriesIterable).find((t) => t.value === country).key;
return this.store
.selectSnapshot(CountrySelector.getCountriesIterable)
.find((t) => t.value === country).key;
}
private updateUserData(): User {
@@ -332,25 +394,29 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
const updatedCustomer: User = { ...this.customer };
if (this.isBillingForm) {
updatedCustomer.poossible_invoice_addresses = updatedCustomer.poossible_invoice_addresses.map((address: Address) => {
if (address.id === +this.addressId) {
return updateAddress;
}
updatedCustomer.poossible_invoice_addresses = updatedCustomer.poossible_invoice_addresses.map(
(address: Address) => {
if (address.id === +this.addressId) {
return updateAddress;
}
return address;
});
return address;
}
);
if (updatedCustomer.invoice_address.id === updateAddress.id) {
updatedCustomer.invoice_address = updateAddress;
}
} else {
updatedCustomer.poossible_delivery_addresses = updatedCustomer.poossible_delivery_addresses.map((address: Address) => {
if (address.id === +this.addressId) {
return updateAddress;
}
updatedCustomer.poossible_delivery_addresses = updatedCustomer.poossible_delivery_addresses.map(
(address: Address) => {
if (address.id === +this.addressId) {
return updateAddress;
}
return address;
});
return address;
}
);
if (updatedCustomer.delivery_addres.id === updateAddress.id) {
updatedCustomer.delivery_addres = updateAddress;
@@ -365,10 +431,16 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
const updatedCustomer: User = { ...this.customer };
if (this.isBillingForm) {
updatedCustomer.poossible_invoice_addresses = [...updatedCustomer.poossible_invoice_addresses, newAddress];
updatedCustomer.poossible_invoice_addresses = [
...(updatedCustomer.poossible_invoice_addresses || []),
newAddress,
];
updatedCustomer.invoice_address = newAddress;
} else {
updatedCustomer.poossible_delivery_addresses = [...updatedCustomer.poossible_delivery_addresses, newAddress];
updatedCustomer.poossible_delivery_addresses = [
...(updatedCustomer.poossible_delivery_addresses || []),
newAddress,
];
updatedCustomer.delivery_addres = newAddress;
}
@@ -417,9 +489,13 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
this.form.get('city').value,
this.countryKey(this.form.get('country').value),
this.form.get('company').value ? this.form.get('company').value : null,
this.form.get('department').value ? this.form.get('department').value : null,
this.form.get('department').value
? this.form.get('department').value
: null,
this.form.get('note').value ? this.form.get('note').value : null,
this.form.get('tax_number').value ? this.form.get('tax_number').value : null,
this.form.get('tax_number').value
? this.form.get('tax_number').value
: null,
this.form.get('title').value ? this.form.get('title').value : null,
this.form.get('gender').value ? this.form.get('gender').value : null
);
@@ -427,7 +503,11 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
return data;
}
private updateDataForSelectedOption(field: string, value: string, defaultValue: string) {
private updateDataForSelectedOption(
field: string,
value: string,
defaultValue: string
) {
if (value !== defaultValue) {
this.form.get(field).patchValue(value);
} else {
@@ -448,33 +528,95 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
.subscribe((value) => {
const savedAddress = value ? JSON.parse(value) : undefined;
this.form = fb.group({
id: [address ? address.id : this.getProperty(savedAddress, 'id') || ''],
firstName: [address ? address.first_name : this.getProperty(savedAddress, 'firstName') || '', Validators.required],
lastName: [address ? address.last_name : this.getProperty(savedAddress, 'lastName') || '', Validators.required],
id: [
address ? address.id : this.getProperty(savedAddress, 'id') || '',
],
firstName: [
address
? address.first_name
: this.getProperty(savedAddress, 'firstName') || '',
Validators.required,
],
lastName: [
address
? address.last_name
: this.getProperty(savedAddress, 'lastName') || '',
Validators.required,
],
address: [
address ? (address.street ? address.street : '') : this.getProperty(savedAddress, 'address') || '',
address
? address.street
? address.street
: ''
: this.getProperty(savedAddress, 'address') || '',
Validators.required,
],
streetNo: [
address ? (address.streetNo ? address.streetNo : '') : this.getProperty(savedAddress, 'streetNo') || '',
address
? address.streetNo
? address.streetNo
: ''
: this.getProperty(savedAddress, 'streetNo') || '',
Validators.required,
],
zipCode: [
address
? address.zip
? address.zip
: ''
: this.getProperty(savedAddress, 'zipCode') || '',
Validators.required,
],
city: [
address
? address.city
? address.city
: ''
: this.getProperty(savedAddress, 'city') || '',
Validators.required,
],
country: [
this.getProperty(savedAddress, 'country') ||
this.getCountryFromAddress(address),
Validators.required,
],
zipCode: [address ? (address.zip ? address.zip : '') : this.getProperty(savedAddress, 'zipCode') || '', Validators.required],
city: [address ? (address.city ? address.city : '') : this.getProperty(savedAddress, 'city') || '', Validators.required],
country: [this.getProperty(savedAddress, 'country') || this.getCountryFromAddress(address), Validators.required],
gender: [
address ? address.gender : this.getProperty(savedAddress, 'gender') || '',
address
? address.gender
: this.getProperty(savedAddress, 'gender') || '',
[Validators.required, CustomValidators.validateGender],
],
company: [address ? (address.company_name ? address.company_name : '') : this.getProperty(savedAddress, 'company') || ''],
company: [
address
? address.company_name
? address.company_name
: ''
: this.getProperty(savedAddress, 'company') || '',
],
department: [
address ? (address.company_department ? address.company_department : '') : this.getProperty(savedAddress, 'department') || '',
address
? address.company_department
? address.company_department
: ''
: this.getProperty(savedAddress, 'department') || '',
],
tax_number: [
address ? (address.company_tax_number ? address.company_tax_number : '') : this.getProperty(savedAddress, 'tax_number') || '',
address
? address.company_tax_number
? address.company_tax_number
: ''
: this.getProperty(savedAddress, 'tax_number') || '',
],
title: [
address
? address.title
: this.getProperty(savedAddress, 'title') || '',
],
note: [
address
? address.note
: this.getProperty(savedAddress, 'note') || '',
],
title: [address ? address.title : this.getProperty(savedAddress, 'title') || ''],
note: [address ? address.note : this.getProperty(savedAddress, 'note') || ''],
});
});
}
@@ -484,7 +626,9 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
return '';
}
const countries = this.store.selectSnapshot(CountrySelector.getCountriesIterable);
const countries = this.store.selectSnapshot(
CountrySelector.getCountriesIterable
);
const country = countries.find((t) => t.key === address.country);
if (isNullOrUndefined(country)) {
return address.country;
@@ -502,11 +646,21 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
const addressesLength = address.length;
address.forEach((add) => {
processedItems++;
const street = add.street ? add.street : (this.form.get('address').value as string);
const streetNo = add.streetNumber ? add.streetNumber : (this.form.get('streetNo').value as string);
const zipCode = add.zipCode ? add.zipCode : (this.form.get('zipCode').value as string);
const city = add.city ? add.city : (this.form.get('city').value as string);
const countryValue = add.country ? add.country : (this.form.get('country').value as string);
const street = add.street
? add.street
: (this.form.get('address').value as string);
const streetNo = add.streetNumber
? add.streetNumber
: (this.form.get('streetNo').value as string);
const zipCode = add.zipCode
? add.zipCode
: (this.form.get('zipCode').value as string);
const city = add.city
? add.city
: (this.form.get('city').value as string);
const countryValue = add.country
? add.country
: (this.form.get('country').value as string);
if (city && street && streetNo && zipCode && countryValue) {
this.addressSuggestions.push({
@@ -520,7 +674,10 @@ export class CustomerAddressFormComponent implements OnInit, OnDestroy {
zip: zipCode,
});
}
if (processedItems === addressesLength && this.addressSuggestions.length === addressesLength) {
if (
processedItems === addressesLength &&
this.addressSuggestions.length === addressesLength
) {
this.cdrf.detectChanges();
this.openSuggestions();
}

View File

@@ -9,8 +9,12 @@ import { SetEditOrder } from 'apps/sales/src/app/core/store/actions/collecting-s
import { OrderDTO, ReceiptListItemDTO } from '@swagger/oms';
import { OrderPaymentReceipt } from 'apps/sales/src/app/core/models/order-payment-receipt.model';
import { isNullOrUndefined } from 'util';
import { receiptType, orderPaymentType } from 'apps/sales/src/app/core/mappings/shelf.mapping';
import {
receiptType,
orderPaymentType,
} from 'apps/sales/src/app/core/mappings/shelf.mapping';
import { DatePipe } from '@angular/common';
import { ShelfNavigationService } from '../../../shelf/shared/services';
@Component({
selector: 'app-customer-order-item-row',
@@ -39,7 +43,12 @@ export class CustomerOrderItemRowComponent implements OnInit {
}
}
constructor(private store: Store, private router: Router, private datePipe: DatePipe) {}
constructor(
private store: Store,
private router: Router,
private datePipe: DatePipe,
private shelfNavigationService: ShelfNavigationService
) {}
ngOnInit() {
this.loadPreorderedTag();
@@ -55,13 +64,20 @@ export class CustomerOrderItemRowComponent implements OnInit {
this.store.dispatch(new SetEditOrder(this.order));
let breadcrumbName = '';
if (this.order.buyer) {
breadcrumbName = this.order.buyer.firstName + ' ' + this.order.buyer.lastName + ' Bearbeiten';
breadcrumbName =
this.order.buyer.firstName +
' ' +
this.order.buyer.lastName +
' Bearbeiten';
}
const subItem = this.order.items.find((t) => t.id === id).data.subsetItems[0].data;
const path = `/shelf/edit/${subItem.compartmentCode ? subItem.compartmentCode : this.order.id}/${this.order.id}/${
subItem.processingStatus
}/${subItem.compartmentCode ? 'c' : 'o'}/customer/${this.customerId}/${subItem.id}`;
const subItem = this.order.items.find((t) => t.id === id).data
.subsetItems[0].data;
const path = `/shelf/edit/${
subItem.compartmentCode ? subItem.compartmentCode : this.order.id
}/${this.order.id}/${subItem.processingStatus}/${
subItem.compartmentCode ? 'c' : 'o'
}/customer/${this.customerId}/${subItem.id}`;
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
@@ -86,15 +102,23 @@ export class CustomerOrderItemRowComponent implements OnInit {
const receiptTp = receiptType[this.paymentDataDTO.receiptType];
let printedDate: string;
if (this.paymentDataDTO.printedDate) {
printedDate = this.datePipe.transform(this.paymentDataDTO.printedDate, 'dd.MM.yy | HH:mm') + ' Uhr';
printedDate =
this.datePipe.transform(
this.paymentDataDTO.printedDate,
'dd.MM.yy | HH:mm'
) + ' Uhr';
}
const placeOfPayment = this.paymentDataDTO.placeOfPayment;
if (this.paymentDataDTO.paidAt && !isNullOrUndefined(this.paymentDataDTO.paidAt)) {
if (
this.paymentDataDTO.paidAt &&
!isNullOrUndefined(this.paymentDataDTO.paidAt)
) {
this.isPayed = true;
} else {
this.isPayed = false;
}
const invoiceText = this.item && this.item.invoiceText ? this.item.invoiceText : '';
const invoiceText =
this.item && this.item.invoiceText ? this.item.invoiceText : '';
this.paymentData = <OrderPaymentReceipt>{
receiptNumber,
placeOfPayment,

View File

@@ -1,4 +1,11 @@
import { Component, OnInit, Input, AfterViewInit, ViewChild, OnDestroy } from '@angular/core';
import {
Component,
OnInit,
Input,
AfterViewInit,
ViewChild,
OnDestroy,
} from '@angular/core';
import { Router } from '@angular/router';
import { Store, Select } from '@ngxs/store';
import { Observable, Subject } from 'rxjs';
@@ -8,34 +15,50 @@ import { SearchInputComponent } from '@libs/ui';
import { Process } from '../../../../core/models/process.model';
import { Breadcrumb } from '../../../../core/models/breadcrumb.model';
import { AddBreadcrumb } from '../../../../core/store/actions/breadcrumb.actions';
import { AddProcess, ChangeCurrentRoute, AddCustomerSearch } from '../../../../core/store/actions/process.actions';
import {
AddProcess,
ChangeCurrentRoute,
AddCustomerSearch,
} from '../../../../core/store/actions/process.actions';
import { SharedSelectors } from '../../../../core/store/selectors/shared.selectors';
import { CustomerSearchResult } from '../../../../core/models/customer-search-result.model';
import { AppState } from 'apps/sales/src/app/core/store/state/app.state';
import { CustomerService } from 'apps/sales/src/app/core/services/customer.service';
import { CustomerSearchResponse } from 'apps/sales/src/app/core/models/customer-search-response.model';
import { CUSTOMER_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants';
import { ClearCachedCustomers, SetCachedCustomers } from 'apps/sales/src/app/core/store/actions/customer.actions';
import {
ClearCachedCustomers,
SetCachedCustomers,
} from 'apps/sales/src/app/core/store/actions/customer.actions';
import { CustomerFeatures } from 'apps/sales/src/app/core/models/customer-features.model';
import { CustomerSearchEmptyModalComponent } from '../customer-search-empty-modal/customer-search-empty-modal.component';
@Component({
selector: 'app-search-customer-card',
templateUrl: './customer-search-card.component.html',
styleUrls: ['./customer-search-card.component.scss']
styleUrls: ['./customer-search-card.component.scss'],
})
export class CustomerSearchCardComponent implements OnInit, AfterViewInit, OnDestroy {
@Select(SharedSelectors.getSearchedCustomers) customers$: Observable<CustomerSearchResult>;
export class CustomerSearchCardComponent
implements OnInit, AfterViewInit, OnDestroy {
@Select(SharedSelectors.getSearchedCustomers) customers$: Observable<
CustomerSearchResult
>;
destroy$ = new Subject();
processes: Process[];
searchError = '';
@Input() searchParams = '';
@ViewChild('searchInput', { static: false }) searchInput: SearchInputComponent;
@ViewChild('searchEmptyModal', { static: false }) searchEmptyModal: CustomerSearchEmptyModalComponent;
@ViewChild('searchInput', { static: false })
searchInput: SearchInputComponent;
@ViewChild('searchEmptyModal', { static: false })
searchEmptyModal: CustomerSearchEmptyModalComponent;
constructor(private store: Store, private router: Router, private customerService: CustomerService) { }
constructor(
private store: Store,
private router: Router,
private customerService: CustomerService
) {}
ngOnInit() {
this.store.dispatch(new ClearCachedCustomers());
@@ -62,8 +85,8 @@ export class CustomerSearchCardComponent implements OnInit, AfterViewInit, OnDes
.pipe(take(1), takeUntil(this.destroy$))
.subscribe((r: CustomerSearchResponse) => {
if (r.customers.length > 0) {
const filteredCustomer = r.customers.filter(data => {
if (!data.features) {
const filteredCustomer = r.customers.filter((data) => {
if (isNullOrUndefined(data) || !data.features) {
return true;
}
const customerFeatures = new CustomerFeatures(data.features);
@@ -72,22 +95,29 @@ export class CustomerSearchCardComponent implements OnInit, AfterViewInit, OnDes
}
return true;
});
const response = { ...r, customers: filteredCustomer, hits: filteredCustomer ? filteredCustomer.length : 0 };
const response = {
...r,
customers: filteredCustomer,
hits: r.hits,
};
this.store.dispatch(
new AddBreadcrumb(
{
name:
(searchParams.length > 12 ? searchParams.substring(0, 12) + '...' : searchParams) +
(searchParams.length > 12
? searchParams.substring(0, 12) + '...'
: searchParams) +
` (${
!isNullOrUndefined(response)
? !isNullOrUndefined(response.hits)
? response.hits
: response.customers
!isNullOrUndefined(response)
? !isNullOrUndefined(response.hits)
? response.hits
: response.customers
? response.customers.length
: 0
: 0
: 0
} Ergebnisse)`,
path: '/customer/results'
path: '/customer/results',
},
'customer'
)
@@ -97,7 +127,7 @@ export class CustomerSearchCardComponent implements OnInit, AfterViewInit, OnDes
query: searchParams,
skip: 0,
take: 5,
firstLoad: true
firstLoad: true,
})
);
sessionStorage.removeItem(CUSTOMER_SCROLL_INDEX);
@@ -119,7 +149,7 @@ export class CustomerSearchCardComponent implements OnInit, AfterViewInit, OnDes
const newProcess = <Process>{
id: 1,
name: 'Vorgang 1',
currentRoute: '/product/search'
currentRoute: '/product/search',
};
this.store.dispatch(new AddProcess(newProcess));
@@ -127,7 +157,7 @@ export class CustomerSearchCardComponent implements OnInit, AfterViewInit, OnDes
new AddBreadcrumb(
<Breadcrumb>{
name: 'Artikelsuche',
path: '/product/search'
path: '/product/search',
},
'customer'
)

View File

@@ -3,12 +3,14 @@ import { User } from '../../../../core/models/user.model';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import { takeUntil, debounceTime, take } from 'rxjs/operators';
import { Store } from '@ngxs/store';
import { CurrentCustomerPageLoaded, AddCustomers } from '../../../../core/store/actions/customer.actions';
import {
CurrentCustomerPageLoaded,
AddCustomers,
} from '../../../../core/store/actions/customer.actions';
import { CustomerService } from '../../../../core/services/customer.service';
import { CustomerSearchResponse } from '../../../../core/models/customer-search-response.model';
import { CustomerFilters } from './customer-search-result.component';
import { AddBreadcrumb, AddBreadcrumbNoDub } from '../../../../core/store/actions/breadcrumb.actions';
import { isArrayMinLength } from 'apps/sales/src/app/core/utils/app.utils';
import { AddBreadcrumbNoDub } from '../../../../core/store/actions/breadcrumb.actions';
import { isNullOrUndefined } from 'util';
import { CustomerSelectors } from 'apps/sales/src/app/core/store/selectors/customer.selectors';
@@ -16,7 +18,9 @@ export class CustomerSearchDataSource extends DataSource<User | undefined> {
private pageSize = 10;
public cachedData = Array.from<User>({ length: 0 });
private fetchedPages = new Set<number>();
private dataStream = new BehaviorSubject<(User | undefined)[]>(this.cachedData);
private dataStream = new BehaviorSubject<(User | undefined)[]>(
this.cachedData
);
destroy$ = new Subject();
public loading = true;
public results = false;
@@ -35,21 +39,22 @@ export class CustomerSearchDataSource extends DataSource<User | undefined> {
super();
}
connect(collectionViewer: CollectionViewer): Observable<(User | undefined)[]> {
collectionViewer.viewChange.pipe(takeUntil(this.destroy$)).subscribe(range => {
const startPage = this.getPageForIndex(range.start);
const endPage = this.getPageForIndex(range.end - 1);
for (let i = startPage; i <= endPage; i++) {
this.fetchPage(i);
}
});
connect(
collectionViewer: CollectionViewer
): Observable<(User | undefined)[]> {
collectionViewer.viewChange
.pipe(takeUntil(this.destroy$))
.subscribe((range) => {
const startPage = this.getPageForIndex(range.start);
const endPage = this.getPageForIndex(range.end - 1);
for (let i = startPage; i <= endPage; i++) {
this.fetchPage(i);
}
});
this.dataStream
.pipe(
takeUntil(this.destroy$),
debounceTime(1000)
)
.subscribe(i => {
.pipe(takeUntil(this.destroy$), debounceTime(1000))
.subscribe((i) => {
this.store.dispatch(new AddCustomers([...i]));
});
@@ -79,7 +84,14 @@ export class CustomerSearchDataSource extends DataSource<User | undefined> {
const customerSource$ = !this.scan
? page === 0 && this.first
? this.store.select(CustomerSelectors.getCachedCustomers)
: this.customerService.searchCustomer(this.search, false, page * this.pageSize, this.pageSize, false, this.customerFilters)
: this.customerService.searchCustomer(
this.search,
false,
page * this.pageSize,
this.pageSize,
false,
this.customerFilters
)
: this.customerService.scannCustomer(this.search);
customerSource$.pipe(take(1)).subscribe((data: CustomerSearchResponse) => {
if (!data) {
@@ -92,8 +104,14 @@ export class CustomerSearchDataSource extends DataSource<User | undefined> {
new AddBreadcrumbNoDub(
{
name:
(this.search.length > 12 ? this.search.substring(0, 12) + '...' : this.search) +
` (${!isNullOrUndefined(data.hits) ? data.hits : data.customers.length} Ergebnisse)`,
(this.search.length > 12
? this.search.substring(0, 12) + '...'
: this.search) +
` (${
!isNullOrUndefined(data.hits)
? data.hits
: data.customers.length
} Ergebnisse)`,
path: '/customer/results',
},
'customer'
@@ -106,7 +124,11 @@ export class CustomerSearchDataSource extends DataSource<User | undefined> {
this.allHits = data.hits;
}
this.cachedData.splice(page * this.pageSize, this.pageSize, ...data.customers);
this.cachedData.splice(
page * this.pageSize,
this.pageSize,
...data.customers
);
this.dataStream.next(this.cachedData);
if (page === 0) {

View File

@@ -9,17 +9,29 @@
</div>
<div class="filters" *ngIf="ds">
<app-checkbox
[option]="{ value: 'Gastkunde', disabled: false, selected: filters.guestaccount !== undefined }"
[option]="{
value: 'Gastkunde',
disabled: false,
selected: filters.guestaccount !== undefined
}"
[negative]="selectedFilterMode === 1"
(valueChanges)="updateFilter($event, 'guestaccount')"
></app-checkbox>
<app-checkbox
[option]="{ value: 'Kundenkarte', disabled: false, selected: filters.bonuscard !== undefined }"
[option]="{
value: 'Kundenkarte',
disabled: false,
selected: filters.bonuscard !== undefined
}"
[negative]="selectedFilterMode === 1"
(valueChanges)="updateFilter($event, 'bonuscard')"
></app-checkbox>
<app-checkbox
[option]="{ value: 'Onlinekonto', disabled: false, selected: filters.onlineshop !== undefined }"
[option]="{
value: 'Onlinekonto',
disabled: false,
selected: filters.onlineshop !== undefined
}"
[negative]="selectedFilterMode === 1"
(valueChanges)="updateFilter($event, 'onlineshop')"
></app-checkbox>
@@ -36,23 +48,39 @@
<span>
Kunde nicht gefunden?
</span>
<app-button (action)="redirectToCustomerCreate()">Neue Kundendaten erfassen</app-button>
<app-button (action)="redirectToCustomerCreate()"
>Neue Kundendaten erfassen</app-button
>
</div>
</div>
<cdk-virtual-scroll-viewport itemSize="144" #viewport
[perfectScrollbar]="{minScrollbarLength: 20}">
<div *cdkVirtualFor="let customer of ds; let i = index; let last = last" class="virtual-item">
<cdk-virtual-scroll-viewport
itemSize="144"
#viewport
[perfectScrollbar]="{ minScrollbarLength: 20 }"
>
<div
*cdkVirtualFor="let customer of ds; let i = index; let last = last"
class="virtual-item"
>
<div class="user">
<div class="user-info-container" *ngIf="customer != null; else loadingComponent" (click)="details(customer, i)">
<div
class="user-info-container"
*ngIf="customer != null; else loadingComponent"
(click)="details(customer, i)"
>
<div class="info">
<div class="user-info">
<div class="name-created-date">
<h1 class="user-title">{{ customer.first_name }} {{ customer.last_name }}</h1>
<h1 class="user-title">
{{ customer.first_name }} {{ customer.last_name }}
</h1>
<span>{{ customer.created | date: 'dd.MM.yy' }}</span>
</div>
<div class="user-location">
<span class="user-label">PLZ und Wohnort</span>
<span class="user-data">{{ getCustomerDeliveryAddress(customer) }}</span>
<span class="user-data">{{
getCustomerDeliveryAddress(customer)
}}</span>
</div>
<div class="user-email">
<span class="user-label">E-Mail</span>
@@ -61,13 +89,25 @@
</div>
<div
class="user-actions"
[ngClass]="{ 'additional-visible': customer.customer_card || customer.shop || customer.newsletter || customer.tolino }"
[ngClass]="{
'additional-visible':
customer.customer_card ||
customer.shop ||
customer.newsletter ||
customer.tolino
}"
></div>
</div>
<div class="additional">
<ng-container>
<div class="data" *ngFor="let features of customer.features">
<lib-icon name="Check_2" alt="check" height="11px" width="15px" pr="5px"></lib-icon>
<lib-icon
name="Check_2"
alt="check"
height="11px"
width="15px"
pr="5px"
></lib-icon>
<span>{{ features.description }}</span>
</div>
</ng-container>
@@ -93,13 +133,16 @@
</div>
<div class="note" *ngIf="last">
<span class="note-text">
Hinweis: Aus Datenschutzgründen werden nur Teilinformationen dargestellt. Tab auf einen Kunden um mehr zu erfahren.
Hinweis: Aus Datenschutzgründen werden nur Teilinformationen
dargestellt. Tab auf einen Kunden um mehr zu erfahren.
</span>
<div class="new-customer-container">
<span>
Kunde nicht gefunden?
</span>
<app-button (action)="redirectToCustomerCreate()">Neue Kundendaten erfassen</app-button>
<app-button (action)="redirectToCustomerCreate()"
>Neue Kundendaten erfassen</app-button
>
</div>
</div>
</div>

View File

@@ -170,20 +170,7 @@ export class CustomerSearchResultComponent implements OnInit {
}
hasAllRequiredData = (user: User): boolean => {
if (
!user.gender ||
!user.first_name ||
!user.last_name ||
!user.email ||
!!(
isNullOrUndefined(user.delivery_addres) &&
isNullOrUndefined(user.invoice_address)
) ||
!(
(user.delivery_addres && user.delivery_addres.country) ||
(user.invoice_address && user.invoice_address.country)
)
) {
if (!user.gender || !user.first_name || !user.last_name || !user.email) {
return false;
}
@@ -275,10 +262,10 @@ export class CustomerSearchResultComponent implements OnInit {
if (!user) {
return '';
}
if (!user.delivery_addres) {
if (!user.base_addres) {
return '';
}
const address = user.delivery_addres;
const address = user.base_addres;
const plz = address
? isNullOrUndefined(address.zip)
? ''

View File

@@ -1,37 +1,22 @@
<div class="filter-wrapper">
<div class="filter-nav">
<button
*ngFor="let filter of value; trackBy: trackByKeyOrName; let first = first"
type="button"
(click)="selected = filter"
class="isa-btn isa-btn-block isa-btn-default isa-btn-l"
[class.customer]="module === 'Customer'"
[class.isa-mt-10]="!first"
[class.is-active]="filter === selected"
[class.isa-font-weight-emphasis]="filter !== selected"
[class.isa-font-weight-bold]="filter === selected"
>
<button *ngFor="let filter of value; trackBy: trackByKeyOrName; let first = first" type="button"
(click)="selected = filter" class="isa-btn isa-btn-block isa-btn-default-bg isa-btn-large"
[class.customer]="module === 'Customer'" [class.isa-mt-10]="!first" [class.is-active]="filter === selected"
[class.isa-font-weight-emphasis]="filter !== selected" [class.isa-font-weight-bold]="filter === selected">
<span>{{ filter.name }}</span>
<lib-icon
[name]="
<lib-icon [name]="
filter === selected
? 'Arrow_Next_Dark'
: module === 'Customer'
? 'Arrow_Next'
: 'Arrow_Next_Grey'
"
height="17px"
></lib-icon>
" height="17px"></lib-icon>
</button>
</div>
<div class="filter-content isa-ml-10" [ngSwitch]="selected?.type">
<app-select-filter
*ngSwitchCase="'select'"
[options]="selected.options"
[module]="module"
[max]="selected.max"
(optionsChanged)="emitOnChange($event)"
>
<app-select-filter *ngSwitchCase="'select'" [options]="selected.options" [module]="module" [max]="selected.max"
(optionsChanged)="emitOnChange($event)">
</app-select-filter>
</div>
</div>
</div>

View File

@@ -5,8 +5,9 @@
>
<button
type="button"
class="isa-btn isa-p-0 isa-font-lightgrey isa-font-weight-emphasis"
class="isa-btn isa-p-0 isa-font-weight-emphasis"
[class.isa-font-color-customer]="module === 'Customer'"
[class.isa-font-lightgrey]="module !== 'Customer'"
*ngIf="!max"
(click)="selectAll()"
>
@@ -34,15 +35,16 @@
<button
*ngIf="canScroll"
class="scroll-btn isa-btn isa-btn-circle isa-btn-default"
class="scroll-btn isa-btn isa-btn-circle isa-btn-default-bg"
[class.up]="scrollPositionPersantage >= 20"
[class.down]="scrollPositionPersantage < 20"
[class.no-shadow]="module === 'Customer'"
(click)="scroll()"
>
<lib-icon
width="18px"
height="20px"
mt="3px"
name="Arrow_back_branch"
[name]="module === 'Customer' ? 'Arrow_back' : 'Arrow_back_branch'"
></lib-icon>
</button>

View File

@@ -28,6 +28,20 @@
right: 6px;
box-shadow: 0px 0px 20px 0px rgba(89, 100, 112, 0.5);
&.no-shadow {
box-shadow: none;
}
@include mq-desktop() {
bottom: 28px;
right: 16vw;
}
@include mq-big-desktop() {
bottom: 28px;
right: 26vw;
}
transition: all 100ms linear;
&.up {
transform: rotate(90deg);

View File

@@ -0,0 +1,58 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SelectFilterComponent } from './select-filter.component';
import { SelectFilterOptionComponent } from './select-filter-option.component';
import {
selectFilterOptionsAllSelected,
selectFilterOptionsAllDeselected,
selectFilterOptionsMixedSelected,
} from '../../../../shelf/shared/mockdata';
import { NO_ERRORS_SCHEMA } from '@angular/core';
fdescribe('SelectFilterComponent', () => {
let fixture: ComponentFixture<SelectFilterComponent>;
let component: SelectFilterComponent;
beforeEach(async () => {
TestBed.configureTestingModule({
providers: [],
declarations: [SelectFilterComponent, SelectFilterOptionComponent],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SelectFilterComponent);
component = fixture.componentInstance;
spyOn(component, 'ngAfterViewInit').and.callFake(() => {});
fixture.detectChanges();
});
describe('setAllSelected', () => {
beforeEach(() => {
spyOn(component, 'setAllSelected').and.callThrough();
spyOn(component.allSelected$, 'next').and.callFake(() => {});
});
it('should set allSelected with true', () => {
const mockFilters = selectFilterOptionsAllSelected;
component.setAllSelected(mockFilters);
expect(component.allSelected$.next).toHaveBeenCalledWith(true);
});
it('should set allSelected to false if only some options are selected', () => {
const mockFilters = selectFilterOptionsMixedSelected;
component.setAllSelected(mockFilters);
expect(component.allSelected$.next).toHaveBeenCalledWith(false);
});
it('should set allSelected with false', () => {
const mockFilters = selectFilterOptionsAllDeselected;
component.setAllSelected(mockFilters);
expect(component.allSelected$.next).toHaveBeenCalledWith(false);
});
});
});

View File

@@ -146,7 +146,7 @@ export class SelectFilterComponent
}
return options.every((option) => {
if (!option.options) {
if (!option.options || !option.options.length) {
return option.selected;
}

View File

@@ -5,4 +5,5 @@ export interface SelectFilterOption extends FilterOptionBase {
expanded?: boolean;
options?: SelectFilterOption[];
id?: string;
initial_selected_state?: boolean;
}

View File

@@ -0,0 +1 @@
export type FilterResetStrategy = 'all' | 'default';

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './filter-reset-strategy.model';
// end:ng42.barrel

View File

@@ -1,10 +1,10 @@
<div class="container" *ngIf="value">
<div class="clear-all">
<div class="clear-all" *ngIf="showResetButton">
<button
class="isa-btn isa-btn-primary-link remove-all isa-pt-0 isa-pb-0 isa-pl-15 isa-pr-15"
(click)="clearAllFilters()"
(click)="resetFilters()"
>
Alle Filter Entfernen
{{ resetStrategy | filterResetWording }}
</button>
</div>

View File

@@ -24,10 +24,6 @@ $font-size: 18px;
}
.clear-all {
&:only-child {
display: none;
}
.filter-name {
padding-right: $filter-padding-right;
padding-left: 2rem;

View File

@@ -0,0 +1,150 @@
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { SelectedFilterOptionsComponent } from './selected-filter-options.component';
import { ChangeDetectorRef, Renderer2 } from '@angular/core';
import { IconModule } from '@libs/ui';
import {
FilterResetWordingPipe,
SelectedFiltersPipe,
CheckAllChildOptionsSelectedPipe,
} from '../pipe';
import { By } from '@angular/platform-browser';
import { PrimaryFilterOption } from '../../../shelf/defs';
import {
mockFilters,
primaryFiltersMock,
} from '../../../shelf/shared/mockdata';
import { SelectFilter } from '../../models';
fdescribe('SelectedFilterOptionsComponent', () => {
let fixture: ComponentFixture<SelectedFilterOptionsComponent>;
let component: SelectedFilterOptionsComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [IconModule, IconModule],
providers: [ChangeDetectorRef, Renderer2, SelectedFilterOptionsComponent],
declarations: [
SelectedFilterOptionsComponent,
SelectedFiltersPipe,
FilterResetWordingPipe,
CheckAllChildOptionsSelectedPipe,
],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SelectedFilterOptionsComponent);
component = fixture.componentInstance;
component.value = [];
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
describe('#ResetFilters', () => {
let button: HTMLButtonElement;
beforeEach(() => {
spyOn(component, 'resetFilters').and.callThrough();
component.resetStrategy = 'all';
component.showResetButton = true;
button = fixture.debugElement.query(
By.css('.isa-btn.isa-btn-primary-link.remove-all')
).nativeElement;
fixture.detectChanges();
});
it('should be shown by default', () => {
expect(button).toBeTruthy();
});
it('should not be shwon if showResetButton is false', async () => {
component.showResetButton = false;
fixture.detectChanges();
await fixture.whenStable();
const debugButtonEl = fixture.debugElement.query(
By.css('.isa-btn.isa-btn-primary-link.remove-all')
);
button = debugButtonEl && debugButtonEl.nativeElement;
expect(button).toBeFalsy();
});
it('should deselect all filters when resetStrategy is all', () => {
spyOn(component, 'deselectAllFilters').and.callThrough();
button.click();
fixture.detectChanges();
expect(component.resetFilters).toHaveBeenCalled();
expect(component.deselectAllFilters).toHaveBeenCalled();
});
it('should revert to the default selection of filters when resetStrategy is default', () => {
spyOn(component, 'resetToDefault').and.callThrough();
component.resetStrategy = 'default';
fixture.detectChanges();
button.click();
fixture.detectChanges();
expect(component.resetFilters).toHaveBeenCalled();
expect(component.resetToDefault).toHaveBeenCalled();
});
});
describe('resetToDefault', () => {
let primaryFilters: PrimaryFilterOption[];
let selectFilters: SelectFilter[];
beforeEach(() => {
primaryFilters = primaryFiltersMock;
selectFilters = mockFilters;
});
it('should reset the filters to its initial state', () => {
selectFilters[0].options[0].selected = true;
selectFilters[0].options[0].initial_selected_state = false;
spyOn(component, 'resetToDefault').and.callThrough();
const result = component.resetToDefault(selectFilters);
expect(result[0].options[0].selected).toBe(false);
expect(result[0].options[0].initial_selected_state).toBe(false);
expect(result.length).toEqual(selectFilters.length);
});
});
describe('deselectAllFilters', () => {
let primaryFilters: PrimaryFilterOption[];
let selectFilters: SelectFilter[];
beforeEach(() => {
primaryFilters = primaryFiltersMock;
selectFilters = mockFilters;
});
it('should deselect all filters', () => {
spyOn(component, 'deselectAllFilters').and.callThrough();
const result = component.deselectAllFilters(selectFilters);
expect(
result.every((filter) => {
if (filter.options) {
return filter.options.every((o) => !o.selected);
}
return true;
})
);
expect(result.length).toEqual(selectFilters.length);
});
});
});

View File

@@ -13,6 +13,7 @@ import {
} from '@angular/core';
import { Filter, SelectFilterOption } from '../../models';
import { cloneFilter } from '../../utils';
import { FilterResetStrategy } from './defs';
@Component({
selector: 'app-selected-filter-options',
@@ -21,11 +22,13 @@ import { cloneFilter } from '../../utils';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectedFilterOptionsComponent implements AfterViewInit {
@Input()
value: Filter[];
@Input() value: Filter[];
@Input() module: 'Customer' | 'Branch' = 'Branch';
@Input() resetStrategy: FilterResetStrategy = 'all';
@Input() showResetButton = true;
@Output() filterChanged = new EventEmitter<Filter[]>();
@Output() reset = new EventEmitter<void>();
@ViewChildren('filteredItem') items: QueryList<ElementRef>;
@@ -61,8 +64,18 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
this.cdr.detectChanges();
}
clearAllFilters() {
this.filterChanged.emit(this.deselectAllFilters(this.value));
resetFilters() {
switch (this.resetStrategy) {
case 'default':
this.filterChanged.emit(this.resetToDefault(this.value));
break;
case 'all':
this.filterChanged.emit(this.deselectAllFilters(this.value));
break;
}
this.reset.emit();
}
clearFilter(filter: SelectFilterOption) {
@@ -106,7 +119,7 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
this.filterChanged.emit(clonedFilter);
}
private deselectAllFilters(filters: Filter[]): Filter[] {
deselectAllFilters(filters: Filter[]): Filter[] {
return filters.reduce((acc, curr) => {
if (Array.isArray(curr.options)) {
curr.options.forEach((option) => this.deselect(option));
@@ -115,6 +128,21 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
}, []);
}
resetToDefault(filters: Filter[]): Filter[] {
const updatedFilters = filters.map((filter) => {
let updatedOptions = filter.options || [];
if (filter.options) {
updatedOptions = filter.options.map((f) => ({
...this.resetToDefaultValue(f),
}));
}
return { ...filter, options: updatedOptions };
});
return updatedFilters;
}
private deselectAllMatches(filter: SelectFilterOption, match?: string) {
if (filter.name === match) {
filter.selected = false;
@@ -142,6 +170,18 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
return filter;
}
private resetToDefaultValue(filter: SelectFilterOption): SelectFilterOption {
return {
...filter,
selected: filter.initial_selected_state,
options: [
...(filter.options
? filter.options.map((option) => this.resetToDefaultValue(option))
: []),
],
};
}
private hasNestedOptions(filter: SelectFilterOption): boolean {
return filter && Array.isArray(filter.options) && !!filter.options.length;
}

View File

@@ -0,0 +1,19 @@
import { FilterResetWordingPipe } from './filter-reset-wording.pipe';
describe('#FilterResetWordingPipe', () => {
let pipe: FilterResetWordingPipe;
beforeEach(() => {
pipe = new FilterResetWordingPipe();
});
it('should show Alle Filter Entfernen for the default strategy', () => {
const result = pipe.transform('all');
expect(result).toEqual('Alle Filter Entfernen');
});
it('should show Filter zurücksetzen for the default strategy', () => {
const result = pipe.transform('default');
expect(result).toEqual('Filter zurücksetzen');
});
});

View File

@@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from '@angular/core';
import { FilterResetStrategy } from '../components/defs';
@Pipe({
name: 'filterResetWording',
})
export class FilterResetWordingPipe implements PipeTransform {
RESET_TO_DEFAULT_WORDING = 'Filter zurücksetzen';
RESET_All_WORDING = 'Alle Filter Entfernen';
transform(strategy: FilterResetStrategy): string {
if (strategy === 'default') {
return this.RESET_TO_DEFAULT_WORDING;
}
return this.RESET_All_WORDING;
}
}

View File

@@ -1,5 +1,5 @@
// start:ng42.barrel
export * from './check-all-selected.pipe';
export * from './selected-filters.pipe';
export * from './filter-reset-wording.pipe';
// end:ng42.barrel

View File

@@ -2,15 +2,25 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IconModule } from '@libs/ui';
import { SelectedFilterOptionsComponent } from './components';
import { SelectedFiltersPipe, CheckAllChildOptionsSelectedPipe} from './pipe';
import {
SelectedFiltersPipe,
CheckAllChildOptionsSelectedPipe,
FilterResetWordingPipe,
} from './pipe';
@NgModule({
imports: [CommonModule, IconModule],
exports: [SelectedFilterOptionsComponent, CheckAllChildOptionsSelectedPipe],
declarations:
[SelectedFilterOptionsComponent,
exports: [
SelectedFilterOptionsComponent,
CheckAllChildOptionsSelectedPipe,
FilterResetWordingPipe,
],
declarations: [
SelectedFilterOptionsComponent,
SelectedFiltersPipe,
CheckAllChildOptionsSelectedPipe],
FilterResetWordingPipe,
CheckAllChildOptionsSelectedPipe,
],
providers: [],
})
export class SelectedFilterOptionsModule {}

View File

@@ -1,20 +1,380 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ProductCheckoutComponent } from './product-checkout.component';
import { DeliveryOption } from 'apps/sales/src/app/core/models/delivery-option.model';
import {
ProductAvailabilityService,
BranchService,
} from '@sales/core-services';
import { of } from 'rxjs';
import { CheckoutType } from 'apps/sales/src/app/core/models/checkout-type.enum';
import { AvailabilityDTO } from '@swagger/availability';
import { NO_ERRORS_SCHEMA, ChangeDetectorRef } from '@angular/core';
import { BookPricePipe } from 'apps/sales/src/app/pipes/book-price.pipe';
import { ModalService } from '@libs/ui';
import { NgxsModule } from '@ngxs/store';
import { RouterTestingModule } from '@angular/router/testing';
import { DatePipe } from '@angular/common';
import { BranchDTO } from '@swagger/checkout';
import { ProductAvailability } from 'apps/sales/src/app/core/models/product-availability.model';
describe('ProductCheckoutComponent', () => {
const mockAvailabilityResponse: {
branchId: number;
type: CheckoutType;
av: AvailabilityDTO[];
} = {
branchId: 1,
type: CheckoutType.store,
av: [
{
itemId: 42455413,
requestReference: '0',
ean: '9783957260376',
shop: 3,
price: { value: { value: 16.8, currency: 'EUR' }, vat: { vatType: 8 } },
supplier: 'L',
supplierId: 3,
ssc: '999',
sscText: 'Lieferbar in 1-2 Tagen',
qty: 9.0,
isPrebooked: false,
at: '2020-07-16T14:00:00',
status: 1024,
preferred: 1,
requested: '2020-07-15T08:28:39.5751342Z',
requestStatusCode: '1',
requestMessage: 'OK',
},
{
itemId: 42455413,
requestReference: '0',
ean: '9783957260376',
shop: 3,
price: { value: { value: 16.8, currency: 'EUR' }, vat: { vatType: 8 } },
supplier: 'K',
supplierId: 4,
ssc: '999',
sscText: 'Lieferbar in 1-2 Tagen',
qty: 6.0,
isPrebooked: false,
at: '2020-07-16T14:00:00',
status: 1024,
preferred: 0,
requested: '2020-07-15T08:28:39.5754585Z',
requestStatusCode: '1',
requestMessage: 'OK',
},
{
itemId: 42455413,
requestReference: '0',
ean: '9783957260376',
shop: 3,
price: { value: { value: 16.8, currency: 'EUR' }, vat: { vatType: 8 } },
supplier: 'LV3',
supplierId: 13,
ssc: '999',
sscText: 'Lieferbar in 10-14Tagen',
isPrebooked: false,
at: '2020-07-29T00:00:00',
status: 1024,
preferred: 0,
requested: '2020-07-15T08:28:39.5757159Z',
requestStatusCode: '1',
requestMessage: 'OK',
},
],
};
const mockProductAvailabilities: ProductAvailability[] = [
{
av: {
at: '2020-07-15T14:00:00+02:00',
ean: '9783957260291',
isPrebooked: false,
itemId: 38219742,
logisticianId: 1002,
preferred: 1,
price: { value: { value: 9.99, currency: 'EUR' }, vat: { vatType: 2 } },
qty: 1,
requestMessage: 'Artikel ist lieferbar.',
requestReference: '0',
requestStatusCode: '1',
requested: '2020-07-15T15:19:50.8364336Z',
ssc: '005',
sscText: 'Sofort lieferbar (Download)',
status: 1024,
supplier: 'DIG',
supplierId: 41,
supplierProductNumber: '33161986',
},
itemId: 38219742,
branchId: 123,
quantity: 1,
status: 1024,
type: 3,
},
{
av: {
at: '2020-08-15T14:00:00+02:00',
ean: '9783957270291',
isPrebooked: false,
itemId: 38229742,
logisticianId: 1002,
preferred: 0,
price: { value: { value: 3.99, currency: 'EUR' }, vat: { vatType: 2 } },
qty: 1,
requestMessage: 'Artikel ist lieferbar.',
requestReference: '0',
requestStatusCode: '1',
requested: '2020-07-15T15:19:50.8364336Z',
ssc: '005',
sscText: 'Sofort lieferbar (Download)',
status: 1024,
supplier: 'DIG',
supplierId: 41,
supplierProductNumber: '33161986',
},
itemId: 38219742,
branchId: 456,
quantity: 1,
status: 1024,
type: 3,
},
];
fdescribe('ProductCheckoutComponent', () => {
let component: ProductCheckoutComponent;
let fixture: ComponentFixture<ProductCheckoutComponent>;
let productAvailabilityService: jasmine.SpyObj<ProductAvailabilityService>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ProductCheckoutComponent]
}).compileComponents();
imports: [NgxsModule.forRoot([]), RouterTestingModule],
declarations: [ProductCheckoutComponent, BookPricePipe],
providers: [
{
provide: ProductAvailabilityService,
useValue: jasmine.createSpyObj('productAvailabilityService', [
'getStoreAvailability',
]),
},
{
provide: ModalService,
useValue: jasmine.createSpy('modalService'),
},
{
provide: BranchService,
useValue: jasmine.createSpy('branchService'),
},
DatePipe,
ChangeDetectorRef,
],
schemas: [NO_ERRORS_SCHEMA],
})
.overrideTemplate(ProductCheckoutComponent, '<div>Test</div>')
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProductCheckoutComponent);
component = fixture.componentInstance;
productAvailabilityService = TestBed.get(ProductAvailabilityService);
spyOn(component, 'ngOnInit').and.callFake(() => {});
fixture.detectChanges();
});
describe('get preferredDownloadAvailability', () => {
let availabilities: ProductAvailability[];
beforeEach(() => {
availabilities = mockProductAvailabilities;
component.isDownload = true;
component.availability = availabilities;
fixture.detectChanges();
});
it('should return the preferred availability', () => {
const expected = mockProductAvailabilities[0].av;
const result = component.preferredDownloadAvailability;
expect(result).toEqual(expected);
});
it('should return undefined if availability is undefined', () => {
component.availability = undefined;
fixture.detectChanges();
const result = component.preferredDownloadAvailability;
expect(result).toBeUndefined();
});
it('should return undefined if no matching availabilities exist', () => {
const availabilitiesWithTakeNowCheckoutType = mockProductAvailabilities.map(
(av) => ({ ...av, type: 0 })
);
component.availability = availabilitiesWithTakeNowCheckoutType;
fixture.detectChanges();
const result = component.preferredDownloadAvailability;
expect(result).toBeUndefined();
});
});
describe('setQuantityError', () => {
let numberOfItems: number;
beforeEach(() => {
spyOn(component, 'setQuantityError').and.callThrough();
numberOfItems = 16;
});
it('should return Exemplar if only 1 item is available', () => {
const limit = 1;
const expected = `${limit} Exemplar sofort lieferbar.`;
component.setQuantityError(numberOfItems, limit);
expect(component.quantityError).toEqual(expected);
});
it('should return Exemplar if more than 1 item is available', () => {
const limit = 10;
const expected = `${limit} Exemplare sofort lieferbar.`;
component.setQuantityError(numberOfItems, limit);
expect(component.quantityError).toEqual(expected);
});
it('should return a error message if requested items is larger than 999', () => {
const limit = 10;
numberOfItems = 1000;
const expected = `Achtung, Maximal ${limit} Exemplare bestellbar.`;
component.setQuantityError(numberOfItems, limit);
expect(component.quantityError).toEqual(expected);
});
});
describe('validateAvailability', () => {
beforeEach(() => {
spyOn(component, 'validateAvailability').and.callThrough();
component.book = {
product: { ean: 123 },
};
component.selectedBranch = { id: 789 } as BranchDTO;
});
it('should set current availability and pick up price if a preferred supplier is present', async () => {
component.deliveryType = DeliveryOption.PICK_UP;
productAvailabilityService.getStoreAvailability.and.returnValue(
of(mockAvailabilityResponse)
);
fixture.detectChanges();
const result = await component.validateAvailability(9);
const expectedCurrentAvailability = mockAvailabilityResponse.av.find(
(av) => av && av.preferred === 1
);
const expectedPickUpPrice = expectedCurrentAvailability.price.value.value;
expect(component.currentAvailability).toBe(expectedCurrentAvailability);
expect(component._pikUpPrice).toBe(expectedPickUpPrice);
});
it('should not set current availability and pick up price if no preferred supplier is present', () => {
component.deliveryType = DeliveryOption.PICK_UP;
const mockAvailabilityResponseWithoutPreferred = {
...mockAvailabilityResponse,
av: mockAvailabilityResponse.av.filter(
(av) => av && av.preferred !== 1
),
};
productAvailabilityService.getStoreAvailability.and.returnValue(
of(mockAvailabilityResponseWithoutPreferred)
);
fixture.detectChanges();
expect(component.currentAvailability).toBeFalsy();
expect(component._pikUpPrice).toBeFalsy();
});
it('should provide the sum of available quantities across all suppliers', async () => {
component.deliveryType = DeliveryOption.PICK_UP;
productAvailabilityService.getStoreAvailability.and.returnValue(
of(mockAvailabilityResponse)
);
fixture.detectChanges();
const expected = 15;
const result = await component.validateAvailability(9);
expect(result).toEqual(expected);
});
it('should return 0 if no supplier has any quantity available', async () => {
component.deliveryType = DeliveryOption.PICK_UP;
const responseWithOutQuantity = setMockAvailablityResponse(
mockAvailabilityResponse,
'qty',
0
);
productAvailabilityService.getStoreAvailability.and.returnValue(
of(responseWithOutQuantity)
);
fixture.detectChanges();
const expected = 0;
const result = await component.validateAvailability(9);
expect(result).toEqual(expected);
});
it('should return 0 if no availabliliy has an eligible (allowed) status code', async () => {
component.deliveryType = DeliveryOption.PICK_UP;
const responseWithFakeStatusCode = setMockAvailablityResponse(
mockAvailabilityResponse,
'status',
5
);
productAvailabilityService.getStoreAvailability.and.returnValue(
of(responseWithFakeStatusCode)
);
fixture.detectChanges();
const expected = 0;
const result = await component.validateAvailability(9);
expect(result).toEqual(expected);
});
});
});
function setMockAvailablityResponse(
mr: {
branchId: number;
type: CheckoutType;
av: AvailabilityDTO[];
},
property: keyof AvailabilityDTO,
value: string | number
): {
branchId: number;
type: CheckoutType;
av: AvailabilityDTO[];
} {
return {
...mr,
av: (mr.av.map(({ [property]: prop, ...rest }) => ({
rest,
property: value,
})) as unknown) as AvailabilityDTO[],
};
}

View File

@@ -1,5 +1,15 @@
import { Component, OnInit, Output, EventEmitter, Input, OnDestroy, ViewChild, ChangeDetectorRef, ViewRef } from '@angular/core';
import { ModalService, ButtonComponent, DeleteDropdownComponent } from '@libs/ui';
import {
Component,
OnInit,
Output,
EventEmitter,
Input,
OnDestroy,
ViewChild,
ChangeDetectorRef,
ViewRef,
} from '@angular/core';
import { ModalService, DeleteDropdownComponent } from '@libs/ui';
import { Router } from '@angular/router';
import { Store, Select } from '@ngxs/store';
import { ItemDTO } from '@swagger/cat';
@@ -7,7 +17,7 @@ import { DeliveryOption } from '../../../../core/models/delivery-option.model';
import { ChangeCurrentRoute } from '../../../../core/store/actions/process.actions';
import { SetCartEntry } from '../../../../core/store/actions/cart-entry.actions';
import { Subject, Observable, of } from 'rxjs';
import { takeUntil, tap, map, filter, take } from 'rxjs/operators';
import { tap, map, filter, take } from 'rxjs/operators';
import { AddBreadcrumb } from '../../../../core/store/actions/breadcrumb.actions';
import { Breadcrumb } from '../../../../core/models/breadcrumb.model';
import { BranchSelectors } from '../../../../core/store/selectors/branch.selector';
@@ -15,7 +25,11 @@ import { ProductAvailability } from '../../../../core/models/product-availabilit
import { CheckoutType } from '../../../../core/models/checkout-type.enum';
import { BranchDTO } from '@swagger/checkout';
import { AvailabilityDTO } from '@swagger/availability';
import { mapToIterable, distance, objectNotNull } from '../../../../core/utils/app.utils';
import {
mapToIterable,
distance,
objectNotNull,
} from '../../../../core/utils/app.utils';
import { isNullOrUndefined } from 'util';
import { ProductAvailabilityService } from '../../../../core/services/product-availability.service';
import { BranchService } from '../../../../core/services/branch.service';
@@ -26,7 +40,6 @@ import { SearchDropdownComponent } from '@libs/ui/lib/search-dropdown';
import { AddBranchesIfNotLoaded } from 'apps/sales/src/app/core/store/actions/branch.actions';
import { allowedAvailabilityStatusCodes } from 'apps/sales/src/app/core/utils/product.util';
@Component({
selector: 'app-checkout',
templateUrl: './product-checkout.component.html',
@@ -34,11 +47,17 @@ import { allowedAvailabilityStatusCodes } from 'apps/sales/src/app/core/utils/pr
})
export class ProductCheckoutComponent implements OnInit, OnDestroy {
@Select(SharedSelectors.getCart) cartData$: Observable<ProcessCart>;
@Select(SharedSelectors.getBreadcrumbs) getBreadcrumbs$: Observable<Breadcrumb[]>;
@Select(BranchSelectors.getBranches) branches$: Observable<{ [key: number]: BranchDTO }>;
@Select(SharedSelectors.getBreadcrumbs) getBreadcrumbs$: Observable<
Breadcrumb[]
>;
@Select(BranchSelectors.getBranches) branches$: Observable<{
[key: number]: BranchDTO;
}>;
@ViewChild('deleteDropdown', { static: false }) deleteDropdown: DeleteDropdownComponent;
@ViewChild('branchesdropdown', { static: false }) branchesdd: SearchDropdownComponent;
@ViewChild('deleteDropdown', { static: false })
deleteDropdown: DeleteDropdownComponent;
@ViewChild('branchesdropdown', { static: false })
branchesdd: SearchDropdownComponent;
id = 'checkout-modal';
stepOne = true;
@@ -87,7 +106,10 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
@Input() availability: ProductAvailability[];
get takeNowAvailable() {
if (this.availability.filter((t) => t.type === CheckoutType.takeNow).length > 0) {
if (
this.availability.filter((t) => t.type === CheckoutType.takeNow).length >
0
) {
return of(true);
}
return of(false);
@@ -98,7 +120,8 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
this.availability.filter(
(t) =>
t.type === CheckoutType.store &&
(allowedAvailabilityStatusCodes(t.status) || (t.av && t.av.ssc === '830' && t.av.supplier === 'G'))
(allowedAvailabilityStatusCodes(t.status) ||
(t.av && t.av.ssc === '830' && t.av.supplier === 'G'))
).length > 0
) {
return of(true);
@@ -107,7 +130,13 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
}
get shippingAvailable() {
if (this.availability.filter((t) => t.type === CheckoutType.delivery && allowedAvailabilityStatusCodes(t.status)).length > 0) {
if (
this.availability.filter(
(t) =>
t.type === CheckoutType.delivery &&
allowedAvailabilityStatusCodes(t.status)
).length > 0
) {
return of(true);
}
return of(false);
@@ -188,6 +217,25 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
}
}
get preferredDownloadAvailability(): AvailabilityDTO {
if (this.availability) {
const downloadAvailabilites = this.availability.filter(
(availability) => availability.type === CheckoutType.donwload
);
if (downloadAvailabilites) {
const preferredProductAvailability = downloadAvailabilites.find(
(productAvailability) =>
!!productAvailability.av && !!productAvailability.av.preferred
);
if (preferredProductAvailability) {
return preferredProductAvailability.av;
}
}
}
}
constructor(
private modalService: ModalService,
private store: Store,
@@ -196,22 +244,21 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
private branchService: BranchService,
private datePipe: DatePipe,
private cdrf: ChangeDetectorRef
) { }
) {}
ngOnInit() {
if (this.isDownload) {
this.stepTwoImgType = 'download-type';
this.possibleItems = [1];
this.deliveryType = DeliveryOption.DOWNLOAD;
if (this.availability && this.availability.length > 0) {
const currAv = this.availability.filter((t) => t && t.type === CheckoutType.donwload);
if (currAv && currAv[0] && currAv[0].av) {
this.currentAvailability = currAv[0].av;
}
if (this.preferredDownloadAvailability) {
this.currentAvailability = this.preferredDownloadAvailability;
}
}
this.currency = this.book.catalogAvailability.price ? this.book.catalogAvailability.price.value.currency : null;
this.currency = this.book.catalogAvailability.price
? this.book.catalogAvailability.price.value.currency
: null;
this.currentPoints = this.book.promoPoints + '';
this.branches$
@@ -255,8 +302,12 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
});
this.branchList = branches;
const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
const userBranch = branches.find((t) => t.branchNumber === userBranchNumber);
const userBranchNumber = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
const userBranch = branches.find(
(t) => t.branchNumber === userBranchNumber
);
if (!!userBranch) {
this.selectedBranch = userBranch;
this.currentLocation = userBranch.name;
@@ -277,29 +328,49 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
this.filteredBranches = this.branches;
if (!!userBranch) {
this.filteredBranches = this.branchesData
.sort((a: BranchDTO, b: BranchDTO) => this.branchSorterFn(a, b, userBranch))
.sort((a: BranchDTO, b: BranchDTO) =>
this.branchSorterFn(a, b, userBranch)
)
.map((t) => t.name);
}
}
private branchSorterFn(a: BranchDTO, b: BranchDTO, userBranch: BranchDTO) {
const userLat = userBranch.address && userBranch.address.geoLocation ? userBranch.address.geoLocation.latitude : 0;
const userLong = userBranch.address && userBranch.address.geoLocation ? userBranch.address.geoLocation.longitude : 0;
const aLat = a.address && a.address.geoLocation ? a.address.geoLocation.latitude : 0;
const aLong = a.address && a.address.geoLocation ? a.address.geoLocation.longitude : 0;
const bLat = b.address && b.address.geoLocation ? b.address.geoLocation.latitude : 0;
const bLong = b.address && b.address.geoLocation ? b.address.geoLocation.longitude : 0;
const userLat =
userBranch.address && userBranch.address.geoLocation
? userBranch.address.geoLocation.latitude
: 0;
const userLong =
userBranch.address && userBranch.address.geoLocation
? userBranch.address.geoLocation.longitude
: 0;
const aLat =
a.address && a.address.geoLocation ? a.address.geoLocation.latitude : 0;
const aLong =
a.address && a.address.geoLocation ? a.address.geoLocation.longitude : 0;
const bLat =
b.address && b.address.geoLocation ? b.address.geoLocation.latitude : 0;
const bLong =
b.address && b.address.geoLocation ? b.address.geoLocation.longitude : 0;
const aDist = distance(userLat, userLong, aLat, aLong);
const bDist = distance(userLat, userLong, bLat, bLong);
return aDist - bDist;
}
branchSearch(term: string) {
const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
const userBranchNumber = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
const branchList = [...this.branchesData];
const userBranch = branchList.find((t) => t.branchNumber === userBranchNumber);
const userBranch = branchList.find(
(t) => t.branchNumber === userBranchNumber
);
if (isNullOrUndefined(term) || term.length < 1) {
this.filteredBranches = branchList.sort((a: BranchDTO, b: BranchDTO) => this.branchSorterFn(a, b, userBranch)).map((t) => t.name);
this.filteredBranches = branchList
.sort((a: BranchDTO, b: BranchDTO) =>
this.branchSorterFn(a, b, userBranch)
)
.map((t) => t.name);
return;
}
this.branchService
@@ -334,7 +405,9 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
this.store.dispatch(new AddBranchesIfNotLoaded(branches));
if (branches) {
branches.forEach((br) => {
const existsInBranchData = this.branchesData.find((t) => t.branchNumber === br.branchNumber);
const existsInBranchData = this.branchesData.find(
(t) => t.branchNumber === br.branchNumber
);
if (!existsInBranchData) {
this.branches.push(br.name);
this.branchesData.push(br);
@@ -351,26 +424,40 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
}
storeBranches(): Observable<number[]> {
return of(this.availability.filter((t) => t.type === CheckoutType.store).map((t) => t.branchId));
return of(
this.availability
.filter((t) => t.type === CheckoutType.store)
.map((t) => t.branchId)
);
}
// STEP ONE
selectLocation(location: string) {
this.branchLoad = true;
this.searchingNewBranch = true;
const branch = this.branchesData.find((b: BranchDTO) => b.name === location);
const branch = this.branchesData.find(
(b: BranchDTO) => b.name === location
);
// reset store availability
this.availability = [...this.availability.filter((t) => t.type !== CheckoutType.store)];
this.availability = [
...this.availability.filter((t) => t.type !== CheckoutType.store),
];
this.productAvailabilityService
.getStoreAvailability(this.book, this.book.product.ean, branch.id)
.pipe(
tap((response) => {
if (response.av && response.av.length > 0) {
this.currentLocation = location;
this.filteredBranches = [location, ...this.branches.filter((t) => t !== location)];
this.filteredBranches = [
location,
...this.branches.filter((t) => t !== location),
];
this.selectedBranch = branch;
const preferedAv = response.av.find((t) => t.preferred === 1);
this.currentPickUpDate = this.datePipe.transform(new Date(preferedAv.at), 'dd.MM.yy');
this.currentPickUpDate = this.datePipe.transform(
new Date(preferedAv.at),
'dd.MM.yy'
);
response.av.forEach((t) => {
this.availability.push({
itemId: t.itemId,
@@ -395,13 +482,19 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
return;
}
if (action === DeliveryOption.TAKE_NOW) {
this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.takeNow).av;
this.currentAvailability = this.availability.find(
(t) => t.type === CheckoutType.takeNow
).av;
this.stepTwoImgType = 'Take_now';
this.deliveryType = DeliveryOption.TAKE_NOW;
this.confirmationBtnText = 'Reservieren';
// Set user branch as selected branch
const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
const userBranch = this.branchList.find((t) => t.branchNumber === userBranchNumber);
const userBranchNumber = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
const userBranch = this.branchList.find(
(t) => t.branchNumber === userBranchNumber
);
if (!!userBranch) {
this.selectedBranch = userBranch;
this.currentLocation = userBranch.name;
@@ -410,12 +503,16 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
if (!this.availability.find((t) => t.type === CheckoutType.store)) {
return;
}
this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.store).av;
this.currentAvailability = this.availability.find(
(t) => t.type === CheckoutType.store
).av;
this.stepTwoImgType = 'Package_Icon';
this.deliveryType = DeliveryOption.PICK_UP;
this.confirmationBtnText = 'Fortfahren';
} else {
this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.delivery).av;
this.currentAvailability = this.availability.find(
(t) => t.type === CheckoutType.delivery
).av;
this.stepTwoImgType = 'truck_Icon';
this.deliveryType = DeliveryOption.DELIVERY;
this.confirmationBtnText = 'Fortfahren';
@@ -442,7 +539,8 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
this.loading = true;
const av = await this.validateAvailability(numberOfItems);
this.setQuantityError(numberOfItems, av);
const isTakeNowAndSelectedMoreThanInStock = this.deliveryType === DeliveryOption.TAKE_NOW && numberOfItems > av;
const isTakeNowAndSelectedMoreThanInStock =
this.deliveryType === DeliveryOption.TAKE_NOW && numberOfItems > av;
isTakeNowAndSelectedMoreThanInStock.ifFalse(() => {
this.addItemsToCartDisabled = false;
});
@@ -458,7 +556,9 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
if (numberOfItems > 999) {
this.quantityError = `Achtung, Maximal ${limit} Exemplare bestellbar.`;
} else if (numberOfItems > limit) {
this.quantityError = `${limit} Exemplar sofort lieferbar.`;
this.quantityError = `${limit} ${
limit > 1 ? 'Exemplare' : 'Exemplar'
} sofort lieferbar.`;
} else {
this.quantityError = null;
}
@@ -478,24 +578,39 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
).toPromise();
} else if (this.deliveryType === DeliveryOption.PICK_UP) {
const storeAvailability = await this.productAvailabilityService
.getStoreAvailability(this.book, this.book.product.ean, this.selectedBranch.id, numberOfItems)
.getStoreAvailability(
this.book,
this.book.product.ean,
this.selectedBranch.id,
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._pikUpPrice = preferedAv.price.value.value;
if (allowedAvailabilityStatusCodes(preferedAv.status)) {
preferredQty = preferedAv.qty;
}
this.detectChanges();
const preferredSupplier = response.av.find(
(av) => av && av.preferred === 1
);
if (preferredSupplier) {
this.currentAvailability = preferredSupplier;
this._pikUpPrice = preferredSupplier.price.value.value;
}
if (preferredQty) {
return preferredQty;
}
return 0;
const eligibleAvailabilities = response.av
.filter(
(availability) =>
allowedAvailabilityStatusCodes(availability.status) &&
availability.qty
)
.map((availability) => availability.qty);
const totalAvailableQuantity = eligibleAvailabilities.reduce(
(acc, curr) => acc + curr,
0
);
this.detectChanges();
return totalAvailableQuantity;
}
return 0;
})
@@ -505,11 +620,18 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
return storeAvailability;
} else if (this.deliveryType === DeliveryOption.DELIVERY) {
const shippingAvailability = await this.productAvailabilityService
.getShippingAvailability(this.book, this.book.product.ean, +this.selectedBranch.branchNumber, numberOfItems)
.getShippingAvailability(
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);
const preferedAv = response.av.find(
(t) => t && t.preferred === 1
);
let preferredQty = 0;
if (preferedAv) {
this.currentAvailability = preferedAv;
@@ -572,11 +694,15 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
private redirectRoute() {
if (this.customerAlredySet) {
this.store.dispatch(new AddBreadcrumb(this.generateBreadcrumb(), 'shoppingCart', true));
this.store.dispatch(
new AddBreadcrumb(this.generateBreadcrumb(), 'shoppingCart', true)
);
this.store.dispatch(new ChangeCurrentRoute('/cart/review'));
this.router.navigate(['/cart/review']);
} else {
this.store.dispatch(new AddBreadcrumb(this.generateBreadcrumb(), 'customer', true));
this.store.dispatch(
new AddBreadcrumb(this.generateBreadcrumb(), 'customer', true)
);
this.store.dispatch(new ChangeCurrentRoute('/customer/search'));
this.router.navigate(['/customer/search']);
}
@@ -630,7 +756,11 @@ export class ProductCheckoutComponent implements OnInit, OnDestroy {
detectChanges() {
setTimeout(() => {
if (this.cdrf !== null && this.cdrf !== undefined && !(this.cdrf as ViewRef).destroyed) {
if (
this.cdrf !== null &&
this.cdrf !== undefined &&
!(this.cdrf as ViewRef).destroyed
) {
this.cdrf.detectChanges();
}
}, 0);

View File

@@ -1,3 +1,9 @@
:host {
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.headers {
display: block;
min-height: 60px;

View File

@@ -1,8 +1,5 @@
<div class="start-remission">
<button
class="isa-btn isa-btn-l isa-btn-primary isa-btn-shadow isa-btn-pill"
(click)="startRemission.emit()"
>
<button class="isa-btn isa-btn-xl isa-btn-primary isa-btn-shadow isa-btn-pill" (click)="startRemission.emit()">
Remission starten
</button>
</div>
</div>

View File

@@ -22,6 +22,7 @@
<app-selected-filter-options
[value]="filters"
(filterChanged)="onFiltersChange($event)"
[showResetButton]="filters | hasSelectedFilters"
>
</app-selected-filter-options>
</div>
@@ -38,7 +39,12 @@
</div>
</ng-container>
<div class="apply-filter-wrapper">
<div
class="apply-filter-wrapper"
centerBottomDuringTransition
[inTransition]="inTransition"
className="in-transition"
>
<button
class="isa-btn isa-btn-primary isa-btn-pill btn-apply-filters text-nowrap"
(click)="applyFilters()"

View File

@@ -13,6 +13,13 @@
.apply-filter-wrapper {
text-align: center;
padding-bottom: 5px;
&.in-transition {
position: absolute;
transform: translate(-50%);
bottom: 0;
left: 50%;
}
}
.btn-apply-filters {

View File

@@ -1,15 +1,47 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RemissionListFilterComponent } from './remission-list-filter.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { IsaOverlayRef } from 'apps/sales/src/app/core/overlay';
import { RemissionFilterService } from '../../services/remission-filter.service';
import { RemissionService } from '@isa/remission';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { NgxsModule } from '@ngxs/store';
import { BehaviorSubject } from 'rxjs';
import { HasSelectedFiltersPipe } from '../../pipes';
describe('RemissionListFilterComponent', () => {
fdescribe('RemissionListFilterComponent', () => {
let fixture: ComponentFixture<RemissionListFilterComponent>;
let component: RemissionListFilterComponent;
let filterService: RemissionFilterService;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule, NgxsModule.forRoot([])],
providers: [
{
provide: IsaOverlayRef,
useValue: jasmine.createSpyObj('isaOverlayRef', ['close']),
},
{
provide: RemissionFilterService,
useValue: jasmine.createSpy('remissionFilterService'),
},
{
provide: RemissionService,
useValue: jasmine.createSpy('remissionService'),
},
],
declarations: [RemissionListFilterComponent, HasSelectedFiltersPipe],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [RemissionListFilterComponent],
}).compileComponents();
fixture = TestBed.createComponent(RemissionListFilterComponent);
component = fixture.componentInstance;
filterService = TestBed.get(RemissionFilterService);
spyOn(component, 'ngOnInit').and.callFake(() => {});
fixture.detectChanges();
});
@@ -17,4 +49,47 @@ describe('RemissionListFilterComponent', () => {
it('should create the component', () => {
expect(fixture).toBeTruthy();
});
describe('close', () => {
beforeEach(() => {
spyOn(component, 'close').and.callThrough();
filterService.resetFilters$ = new BehaviorSubject(null);
fixture.detectChanges();
});
it('should set inTransition to true', () => {
component.close();
fixture.detectChanges();
const result = component.inTransition;
expect(result).toBeTruthy();
});
});
describe('closeAnimationDone', () => {
beforeEach(() => {
spyOn(component, 'closeAnimationDone').and.callThrough();
fixture.detectChanges();
});
it('should set inTransition to false if animation is complete (in)', () => {
component.animate = 'in';
component.inTransition = true;
fixture.detectChanges();
component.closeAnimationDone();
fixture.detectChanges();
expect(component.inTransition).toBeFalsy();
});
it('should not set in transition if animation out is already complete', () => {
component.animate = 'out';
component.inTransition = true;
fixture.detectChanges();
component.closeAnimationDone();
fixture.detectChanges();
expect(component.inTransition).toBeTruthy();
});
});
});

View File

@@ -40,7 +40,8 @@ import { slideIn } from 'apps/sales/src/app/core/overlay';
animations: [slideIn],
})
export class RemissionListFilterComponent implements OnInit, OnDestroy {
animate = 'in';
animate: 'in' | 'out' = 'in';
inTransition = true;
@ViewChild('selectionContainer', { static: false })
selectionContainer: ElementRef;
@@ -205,17 +206,27 @@ export class RemissionListFilterComponent implements OnInit, OnDestroy {
}
updateLastGroupChanged(updatedGroup: Filter) {
this.filterService.lastFilterGroupChanged$.next(updatedGroup);
this.filterService.lastFilterGroupChanged = updatedGroup;
}
close() {
this.animate = 'out';
this.filterService.resetFilters$.next();
this.setTransition(true);
}
closeAnimationDone() {
if (this.animate === 'in') {
this.setTransition(false);
}
if (this.animate === 'out') {
this.overlayRef.close();
}
}
private setTransition(isTransitioning: boolean) {
this.inTransition = isTransitioning;
}
}

View File

@@ -9,6 +9,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RemissionUeberlaufCapacitiesModule } from '../../pages/remission-list-create/ueberlauf-capacities';
import { SharedModule } from 'apps/sales/src/app/shared/shared.module';
import { RemissionRequiredCapacitiesWidgetModule } from '../../components/remission-required-capacities-widget';
import { HasSelectedFiltersPipe } from '../../pipes';
@NgModule({
imports: [
@@ -21,8 +22,8 @@ import { RemissionRequiredCapacitiesWidgetModule } from '../../components/remiss
RemissionUeberlaufCapacitiesModule,
RemissionRequiredCapacitiesWidgetModule,
],
exports: [RemissionListFilterComponent],
declarations: [RemissionListFilterComponent],
exports: [RemissionListFilterComponent, HasSelectedFiltersPipe],
declarations: [RemissionListFilterComponent, HasSelectedFiltersPipe],
entryComponents: [RemissionListFilterComponent],
})
export class RemissionListFilterModule {}

View File

@@ -1,13 +1,28 @@
import { Component, OnInit, OnDestroy, ChangeDetectorRef, ViewChild } from '@angular/core';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ViewChild,
} from '@angular/core';
import { RemissionFinishingProcessStatus } from '../../models/remission-finishing-process-status.enum';
import { Store, Select } from '@ngxs/store';
import { RemissionSelectors } from 'apps/sales/src/app/core/store/selectors/remission.selectors';
import { RemissionSupplier, RemissionProcess, RemissionService, ShippingDocument, Printer } from '@isa/remission';
import { filter, takeUntil, take, switchMap, map } from 'rxjs/operators';
import {
RemissionSupplier,
RemissionProcess,
RemissionService,
ShippingDocument,
Printer,
} from '@isa/remission';
import { filter, takeUntil, take, switchMap, map, tap } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { Subject, Observable, merge } from 'rxjs';
import { SUPPLIER_PLACEHOLDER } from '../../constants/remission.constants';
import { AddBreadcrumb, ResetBreadcrumbsTo } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import {
AddBreadcrumb,
ResetBreadcrumbsTo,
} from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { SetBranchProcessCurrentPath } from 'apps/sales/src/app/core/store/actions/branch-process.actions';
import { Router } from '@angular/router';
import {
@@ -17,7 +32,7 @@ import {
SetRemissionStarted,
SetRemissionProcess,
ResetRemissionShippingDocument,
CompleteRemissionShippingDocument
CompleteRemissionShippingDocument,
} from 'apps/sales/src/app/core/store/actions/remission.actions';
import { Breadcrumb } from 'apps/sales/src/app/core/models/breadcrumb.model';
import { AppService } from '@sales/core-services';
@@ -33,10 +48,12 @@ import { RemissionScanConfirmationDialogComponent } from '../../components/remis
@Component({
selector: 'app-remission-finish',
templateUrl: './remission-finish.component.html',
styleUrls: ['./remission-finish.component.scss']
styleUrls: ['./remission-finish.component.scss'],
})
export class RemissionFinishComponent implements OnInit, OnDestroy {
@Select(RemissionSelectors.getRemissionProcess) remissionProcess$: Observable<RemissionProcess>;
@Select(RemissionSelectors.getRemissionProcess) remissionProcess$: Observable<
RemissionProcess
>;
@Select(RemissionSelectors.getRemissionShippingDocument)
shippingDocument$: Observable<ShippingDocument>;
@Select(RemissionSelectors.getRemissionShippingDocumentstatus)
@@ -49,8 +66,10 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
invalidBarcodeDialog: RemissionScanProductInvalidBarcodeComponent;
@ViewChild('invalidDocumentDialog', { static: false })
invalidDocumentDialog: RemissionScanShippingDocumentClosedComponent;
@ViewChild('printModal', { static: false }) printModal: PrinterSelectionComponent;
@ViewChild('confirmScanModal', { static: false }) confirmScanModal: RemissionScanConfirmationDialogComponent;
@ViewChild('printModal', { static: false })
printModal: PrinterSelectionComponent;
@ViewChild('confirmScanModal', { static: false })
confirmScanModal: RemissionScanConfirmationDialogComponent;
pageStatus: RemissionFinishingProcessStatus;
suppliers: {
leftSupplier: RemissionSupplier;
@@ -61,13 +80,14 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
destroy$ = new Subject();
containerId: string;
showError = false;
errorMsg = 'Leider haben Sie den falschen Barcode gescannt. Scannen Sie bitte den Barcode seitlich an der Wanne.';
errorMsg =
'Leider haben Sie den falschen Barcode gescannt. Scannen Sie bitte den Barcode seitlich an der Wanne.';
supplier: RemissionSupplier;
startNewRemission = false;
title = {
0: 'Wannennummer scannen',
1: 'Wanne abgeschlossen',
2: ``
2: ``,
};
isSafari = false;
isNative = false;
@@ -78,17 +98,17 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
0: 'Scannen Sie die Wannennummer um den Warenbegleitschein abzuschließen.',
// tslint:disable-next-line: max-line-length
1: 'Legen Sie abschließend den "Blank Beizettel" in die abgeschlossene Wanne. Der Warenbegleitschein wird nach Abschluss der Remission versendet. Zum Öffnen eines neuen Warenbegleitscheins setzen Sie die Remission fort.',
2: ''
2: '',
};
private finishPocessTitles = {
1: `${SUPPLIER_PLACEHOLDER} Warenbegleitschein gedruckt`,
2: `Warenbegleitschein versendet`
2: `Warenbegleitschein versendet`,
};
private finishPocessSubTitles = {
1: `Die Warenbegleitscheine für ${SUPPLIER_PLACEHOLDER} werden gedruckt. Bitte in die Wanne legen.`,
2: `Der Warenbegleitschein wurde digital an ${SUPPLIER_PLACEHOLDER} versendet.`
2: `Der Warenbegleitschein wurde digital an ${SUPPLIER_PLACEHOLDER} versendet.`,
};
constructor(
@@ -98,7 +118,7 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
private cdrf: ChangeDetectorRef,
private appService: AppService,
private nativeContainerService: NativeContainerService
) { }
) {}
ngOnInit() {
this.initialize();
@@ -116,21 +136,25 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
.select(RemissionSelectors.getRemissionFinishingProcessStatus)
.pipe(
takeUntil(this.destroy$),
filter(data => !isNullOrUndefined(data)),
filter((data) => !isNullOrUndefined(data)),
take(1)
)
.subscribe(status => {
.subscribe((status) => {
this.pageStatus = status;
this.cdrf.detectChanges();
if (this.pageStatus === RemissionFinishingProcessStatus.containerScanned) {
this.containerId = this.store.selectSnapshot(RemissionSelectors.getRemissionContainerId);
if (
this.pageStatus === RemissionFinishingProcessStatus.containerScanned
) {
this.containerId = this.store.selectSnapshot(
RemissionSelectors.getRemissionContainerId
);
} else if (this.pageStatus === RemissionFinishingProcessStatus.notSet) {
this.navigateToNewRemissionList();
}
});
this.remissionProcess$
.pipe(
filter(data => !isNullOrUndefined(data)),
filter((data) => !isNullOrUndefined(data)),
take(1),
takeUntil(this.destroy$)
)
@@ -138,7 +162,7 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
this.shippingDocument$
.pipe(
filter(data => !isNullOrUndefined(data)),
filter((data) => !isNullOrUndefined(data)),
takeUntil(this.destroy$)
)
.subscribe(this.shippingDocumentSubscriptionHandler);
@@ -146,21 +170,22 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
this.loadCurrentRemissionProcessStatuses().then(() => {
this.continueProcess()
.pipe(takeUntil(this.destroy$), take(1))
.subscribe(remission => {
.subscribe((remission) => {
if (this.shippingDocument && this.shippingDocument.id) {
this.loadSupplier(remission.filter.target);
this.remissionService
.getShippingDocuments({
remissionProcessId: this.remissionProcess.id,
shippingDocumentId: this.shippingDocument.id
shippingDocumentId: this.shippingDocument.id,
})
.pipe(
filter(data => !isNullOrUndefined(data)),
filter((data) => !isNullOrUndefined(data)),
takeUntil(this.destroy$)
)
.subscribe(({ isCompleted }) => {
if (isCompleted) {
this.pageStatus = RemissionFinishingProcessStatus.containerScanned;
this.pageStatus =
RemissionFinishingProcessStatus.containerScanned;
}
});
}
@@ -171,32 +196,42 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
loadCurrentRemissionProcessStatuses() {
return this.remissionProcessStatuses$
.pipe(
filter(data => !isNullOrUndefined(data)),
filter((data) => !isNullOrUndefined(data)),
take(1)
)
.toPromise();
}
loadCurrentRemissionProcess() {
return this.currentRemissionProcess$.pipe(filter(data => !isNullOrUndefined(data), take(1)));
return this.currentRemissionProcess$.pipe(
filter((data) => !isNullOrUndefined(data), take(1))
);
}
continueProcess() {
return this.loadCurrentRemissionProcess().pipe(
take(1),
switchMap(remissionProcess => {
switchMap((remissionProcess) => {
return this.remissionService.continueProcess(remissionProcess);
})
);
}
shippingDocumentSubscriptionHandler = (shippingDocument: ShippingDocument) => {
shippingDocumentSubscriptionHandler = (
shippingDocument: ShippingDocument
) => {
this.shippingDocument = shippingDocument;
};
remissionProcessSubscriptionHandler = (remissionProcess: RemissionProcess) => {
remissionProcessSubscriptionHandler = (
remissionProcess: RemissionProcess
) => {
this.remissionProcess = remissionProcess;
if (remissionProcess && remissionProcess.filter && remissionProcess.filter.target) {
if (
remissionProcess &&
remissionProcess.filter &&
remissionProcess.filter.target
) {
this.loadSupplier(remissionProcess.filter.target);
}
};
@@ -210,11 +245,15 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
}
if (this.supplier) {
const selectedSupplierName = remissionSuplier.name;
this.title[RemissionFinishingProcessStatus.finished] = this.finishPocessTitles[this.supplier.id].replace(
this.title[
RemissionFinishingProcessStatus.finished
] = this.finishPocessTitles[this.supplier.id].replace(
SUPPLIER_PLACEHOLDER,
selectedSupplierName
);
this.subTitle[RemissionFinishingProcessStatus.finished] = this.finishPocessSubTitles[this.supplier.id].replace(
this.subTitle[
RemissionFinishingProcessStatus.finished
] = this.finishPocessSubTitles[this.supplier.id].replace(
SUPPLIER_PLACEHOLDER,
selectedSupplierName
);
@@ -222,7 +261,7 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
}
private openConfirmationDialog(barcode: string) {
this.confirmScanModal.openDialog(barcode)
this.confirmScanModal.openDialog(barcode);
}
retryScan() {
@@ -239,32 +278,37 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
abandonScan() {
this.store.dispatch(new SetBranchProcessCurrentPath('remission', false));
this.store.dispatch(
new SetRemissionFinishedProcessStatus(
RemissionFinishingProcessStatus.notSet
this.store
.dispatch(
new SetRemissionFinishedProcessStatus(
RemissionFinishingProcessStatus.notSet
)
)
).toPromise().then(_ => {
this.router.navigate(['remission/started']);
});
.toPromise()
.then((_) => {
this.router.navigate(['remission/started']);
});
}
handleScanResult(barcode: string) {
if (!barcode) {
return this.showInvalidBarcodeDialog()
return this.showInvalidBarcodeDialog();
}
return this.openConfirmationDialog(barcode)
return this.openConfirmationDialog(barcode);
}
finishRemission() {
const remissionProcessId = this.remissionProcess.id;
this.remissionService.isPrintingRequired({ remissionProcessId }).subscribe(isPrintingRequired => {
if (isPrintingRequired) {
this.printModal.openDialog();
} else {
this.postFinishRemission(remissionProcessId);
}
});
this.remissionService
.isPrintingRequired({ remissionProcessId })
.subscribe((isPrintingRequired) => {
if (isPrintingRequired) {
this.printModal.openDialog();
} else {
this.postFinishRemission(remissionProcessId);
}
});
}
print(selectedPrinter) {
@@ -272,7 +316,7 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
this.remissionService
.printShippingDocument({
remissionProcessId: this.remissionProcess.id,
printerKey: selectedPrinter
printerKey: selectedPrinter,
})
.subscribe(
() => {
@@ -282,21 +326,27 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
},
() => {
this.pageStatus = RemissionFinishingProcessStatus.containerScanned;
this.store.dispatch(new SetRemissionFinishedProcessStatus(this.pageStatus));
this.store.dispatch(
new SetRemissionFinishedProcessStatus(this.pageStatus)
);
this.isPrinting = false;
}
);
}
postFinishRemission(remissionProcessId: number) {
this.remissionService.completeRemissions({ remissionProcessId }).subscribe(status => {
if (status) {
this.appService.clearIdleReminder();
this.pageStatus = RemissionFinishingProcessStatus.finished;
this.store.dispatch(new SetRemissionFinishedProcessStatus(this.pageStatus));
this.store.dispatch(new ResetRemissionState());
}
});
this.remissionService
.completeRemissions({ remissionProcessId })
.subscribe((status) => {
if (status) {
this.appService.clearIdleReminder();
this.pageStatus = RemissionFinishingProcessStatus.finished;
this.store.dispatch(
new SetRemissionFinishedProcessStatus(this.pageStatus)
);
this.store.dispatch(new ResetRemissionState());
}
});
}
continueRemission() {
@@ -310,7 +360,7 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
new ResetBreadcrumbsTo(
<Breadcrumb>{
name: 'Remission',
path: path
path: path,
},
'remission',
true
@@ -321,13 +371,21 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
}
startRemission() {
try {
throw new Error('[Remission Finished] Start Remission');
} catch (error) {
console.error(error);
}
this.remissionService
.startRemission({ remissionProcessId: this.remissionProcess.id })
.pipe(
filter(data => !isNullOrUndefined(data)),
take(1)
filter((data) => !isNullOrUndefined(data)),
take(1),
tap((process) =>
console.log('[Remission Finished] Start Remission II', process)
)
)
.subscribe(remissionProcess => {
.subscribe((remissionProcess) => {
this.store.dispatch(new SetRemissionStarted(true));
this.store.dispatch(new SetRemissionProcess(remissionProcess));
this.navigateToStartedRemission();
@@ -341,7 +399,7 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
new AddBreadcrumb(
{
path: path,
name: 'Warenbegleitschein'
name: 'Warenbegleitschein',
},
'remission'
)
@@ -355,21 +413,25 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
.completeShippingDocument({
remissionProcessId: this.remissionProcess.id,
shippingDocumentId: this.shippingDocument.id,
containerId: this.containerId
containerId: this.containerId,
})
.subscribe(response => {
.subscribe((response) => {
if (response.error) {
if (response.errorReasons.PackageCode) {
this.emptyOrInvalidBarcode = false;
this.pageStatus = RemissionFinishingProcessStatus.start;
this.store.dispatch(new SetRemissionFinishedProcessStatus(this.pageStatus));
this.showInvalidBarcodeDialog()
this.store.dispatch(
new SetRemissionFinishedProcessStatus(this.pageStatus)
);
this.showInvalidBarcodeDialog();
} else {
this.invalidDocumentDialog.openDialog();
}
} else {
this.pageStatus = RemissionFinishingProcessStatus.containerScanned;
this.store.dispatch(new CompleteRemissionShippingDocument(this.shippingDocument));
this.store.dispatch(
new CompleteRemissionShippingDocument(this.shippingDocument)
);
}
});
}

View File

@@ -16,6 +16,7 @@ import {
first,
map,
withLatestFrom,
tap,
} from 'rxjs/operators';
import { RemissionResourceType } from '../../models/remission-resource-type.model';
import {
@@ -316,6 +317,11 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy {
};
startRemission() {
try {
throw new Error('[Remission Create] Start Remission');
} catch (error) {
console.error(error);
}
this.currentRemissionProcess$
.pipe(
switchMap((process) =>
@@ -323,7 +329,10 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy {
.startRemission({ remissionProcessId: process.id })
.pipe(filter((data) => !isNullOrUndefined(data)))
),
first()
first(),
tap((process) =>
console.log('[Remission Create] Start Remission II', process)
)
)
.subscribe((process) => {
this.store.dispatch(new SetRemissionStarted(true));

View File

@@ -0,0 +1,27 @@
import { HasSelectedFiltersPipe } from './has-selected-filters.pipe';
import { filters } from '../mocks';
fdescribe('Pipe: HasSelectedFiltersPipe', () => {
let pipe: HasSelectedFiltersPipe;
beforeEach(() => {
pipe = new HasSelectedFiltersPipe();
});
it('should return true', () => {
const filterWithSelected = filters;
const result = pipe.transform(filterWithSelected);
expect(result).toBeTruthy();
});
it('should return false', () => {
const filterWithSelected = filters.map((f) => ({
...f,
options: f.options.map((o) => ({ ...o, selected: false })),
}));
const result = pipe.transform(filterWithSelected);
expect(result).toBeFalsy();
});
});

View File

@@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';
import { SelectFilter } from '../../filter';
@Pipe({
name: 'hasSelectedFilters',
})
export class HasSelectedFiltersPipe implements PipeTransform {
hasSelectedFilters = (filter: SelectFilter[]) => {
return filter.some((f) => f.options.some((o) => o.selected));
};
transform(filters: SelectFilter[]): boolean {
return this.hasSelectedFilters(filters);
}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './has-selected-filters.pipe';
// end:ng42.barrel

View File

@@ -0,0 +1,104 @@
import { RemissionFilterService } from './remission-filter.service';
import { TestBed } from '@angular/core/testing';
import { RemissionResourceType } from '../models';
import { RemissionService } from '@isa/remission';
import { NgxsModule } from '@ngxs/store';
import { hot, cold } from 'jasmine-marbles';
import { Filter } from '../../filter';
fdescribe('#RemissionFilterService', () => {
let remissionFilterService: RemissionFilterService;
const mockSetFilterGroup: Filter = {
type: 'select',
name: 'Mock',
options: [],
};
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([])],
providers: [
RemissionFilterService,
{
provide: RemissionService,
useValue: jasmine.createSpy('remissionService'),
},
],
});
remissionFilterService = TestBed.get(RemissionFilterService);
});
beforeEach(() => {
spyOn(remissionFilterService, 'getFilters').and.callFake(() => {});
spyOn(remissionFilterService, 'getFlattenedFilters').and.callFake(() => {});
});
it('should be created', () => {
expect(
remissionFilterService instanceof RemissionFilterService
).toBeTruthy();
});
describe('get lastFilterGroupChanged$', () => {
beforeEach(() => {
const remissionSource$ = hot('-a', { a: RemissionResourceType.Central });
spyOnProperty(remissionFilterService, 'remissionSource$').and.returnValue(
remissionSource$
);
const remissionTarget$ = hot('-b', { b: 'libri' });
spyOnProperty(remissionFilterService, 'remissionTarget$').and.returnValue(
remissionTarget$
);
});
it('should return the last filter group changed for the currently selected remisison filter and target', () => {
spyOnProperty(
remissionFilterService,
'lastFilterGroupChanged$'
).and.callThrough();
remissionFilterService.lastFilterGroupChanged = mockSetFilterGroup;
const expected$ = cold('-c', { c: mockSetFilterGroup });
expect(remissionFilterService.lastFilterGroupChanged$).toBeObservable(
expected$
);
});
});
describe('set lastFilterGroupChanged', () => {
beforeEach(() => {
const remissionSource$ = hot('-a--b', {
a: RemissionResourceType.Central,
b: RemissionResourceType.Central,
});
spyOnProperty(remissionFilterService, 'remissionSource$').and.returnValue(
remissionSource$
);
const remissionTarget$ = hot('-b---', { b: 'libri' });
spyOnProperty(remissionFilterService, 'remissionTarget$').and.returnValue(
remissionTarget$
);
});
it('should set the last filter group changed for the currently selected remisison filter and target', () => {
spyOnProperty(
remissionFilterService,
'lastFilterGroupChanged$'
).and.callThrough();
remissionFilterService.lastFilterGroupChanged = mockSetFilterGroup;
const expected$ = cold('-a--b', {
a: mockSetFilterGroup,
b: mockSetFilterGroup,
});
expect(remissionFilterService.lastFilterGroupChanged$).toBeObservable(
expected$
);
});
});
});

View File

@@ -22,6 +22,7 @@ import {
import { isNullOrUndefined } from 'util';
import { RequestUpdateRemissionFilter } from '../../../core/store/actions/remission.actions';
import { flatten } from '../../../shared/utils';
import { RemissionResourceType } from '../models';
@Injectable({ providedIn: 'root' })
export class RemissionFilterService {
@@ -33,13 +34,40 @@ export class RemissionFilterService {
getRemissionActiveFilters$: Observable<{
[filterId: string]: string[];
}>;
@Select(RemissionSelectors.getRemissiontarget)
remissionTarget$: Observable<string>;
@Select(RemissionSelectors.getRemissionSource)
remissionSource$: Observable<RemissionResourceType>;
public filters$: Observable<SelectFilter[]>;
private lastFilterGroupChangedState = new BehaviorSubject<
Map<string, Filter>
>(new Map());
public filtersByGroup$: Observable<SelectFilter[]>;
public lastFilterGroupChanged$ = new BehaviorSubject<Filter>(undefined);
public filters$: Observable<SelectFilter[]>;
public pendingFilters$ = new BehaviorSubject<Filter[]>(null);
public resetFilters$ = new Subject<void>();
public set lastFilterGroupChanged(filterGroup: Filter) {
this.getSupplierAndFilter$()
.pipe(take(1))
.subscribe(([target, supplier]) =>
this.lastFilterGroupChangedState.value.set(
this.getFilterGroupKey(target, supplier),
filterGroup
)
);
}
public get lastFilterGroupChanged$(): Observable<Filter> {
return this.getSupplierAndFilter$().pipe(
map(([target, supplier]) =>
this.lastFilterGroupChangedState.value.get(
this.getFilterGroupKey(target, supplier)
)
)
);
}
constructor(
private remissionService: RemissionService,
private store: Store
@@ -49,14 +77,13 @@ export class RemissionFilterService {
}
getFilters(): Observable<SelectFilter[]> {
// tslint:disable-next-line: deprecation
return combineLatest(
return combineLatest([
this.remissionProcess$,
this.resetFilters$.pipe(
startWith(true),
tap(() => this.pendingFilters$.next(null))
)
).pipe(
),
]).pipe(
debounceTime(100),
filter(([process, _]) => !isNullOrUndefined(process)),
map(([process, _]) => process.id),
@@ -250,9 +277,29 @@ export class RemissionFilterService {
}
private setInitialFilterGroupLastChanged(filters: SelectFilter[]) {
if (!this.lastFilterGroupChanged$.value) {
this.lastFilterGroupChanged$.next(filters[0]);
}
this.lastFilterGroupChanged$
.pipe(take(1))
.subscribe((lastFilterGroupChanged) => {
if (!lastFilterGroupChanged) {
this.lastFilterGroupChanged = filters[0];
}
});
}
private getSupplierAndFilter$(): Observable<[string, RemissionResourceType]> {
return combineLatest([this.remissionTarget$, this.remissionSource$]).pipe(
filter(
([target, supplier]) =>
!isNullOrUndefined(target) && !isNullOrUndefined(supplier)
)
);
}
private getFilterGroupKey(
target: string,
supplier: RemissionResourceType
): string {
return target.toLowerCase() + supplier.toLowerCase();
}
getRemissionFilters(

View File

@@ -0,0 +1,96 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AutocompleteResultsComponent } from './autocomplete-results.component';
import { ResultItemComponent } from './result-item';
import { ActiveDescendantKeyManager, Highlightable } from '@angular/cdk/a11y';
import { QueryList } from '@angular/core';
fdescribe('#AutocompleteResultsComponent', () => {
let fixture: ComponentFixture<AutocompleteResultsComponent>;
let component: AutocompleteResultsComponent;
let items: QueryList<Highlightable & ResultItemComponent>;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [AutocompleteResultsComponent, ResultItemComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AutocompleteResultsComponent);
component = fixture.componentInstance;
items = new QueryList();
component['keyManager'] = new ActiveDescendantKeyManager<
ResultItemComponent
>(items).withWrap();
spyOn(component, 'ngAfterViewInit').and.callFake(() => {});
component.results = [];
fixture.detectChanges();
});
it('should be created', () => {
expect(component instanceof AutocompleteResultsComponent).toBeTruthy();
});
describe('Deselect Items on keypress', () => {
let activeItemMock: Highlightable & ResultItemComponent;
beforeEach(() => {
activeItemMock = ({
result: 'Test Result',
} as unknown) as Highlightable & ResultItemComponent;
spyOn(component, 'onKeyDown').and.callThrough();
spyOn(component.selectItem, 'emit').and.callFake(() => {});
spyOnProperty(component['keyManager'], 'activeItem').and.returnValue(
activeItemMock
);
spyOn(component['keyManager'], 'setActiveItem').and.callFake(() => {});
});
it('should deselect the current active item if a letter is typed', () => {
const event = new KeyboardEvent('keyup', { key: 'a' });
window.dispatchEvent(event);
fixture.detectChanges();
expect(component.onKeyDown).toHaveBeenCalled();
expect(component['keyManager'].setActiveItem).toHaveBeenCalledWith(null);
});
it('should deselect the current active item if backspace is typed', () => {
const event = new KeyboardEvent('keyup', { key: 'Backspace' });
window.dispatchEvent(event);
fixture.detectChanges();
expect(component.onKeyDown).toHaveBeenCalled();
expect(component['keyManager'].setActiveItem).toHaveBeenCalledWith(null);
});
it('should not deselect if ArrowUp is pressed', () => {
const event = new KeyboardEvent('keyup', { key: 'ArrowUp' });
window.dispatchEvent(event);
fixture.detectChanges();
expect(component.onKeyDown).toHaveBeenCalled();
expect(component['keyManager'].setActiveItem).not.toHaveBeenCalledWith(
null
);
expect(component.selectItem.emit).toHaveBeenCalledWith(
activeItemMock.result
);
});
it('should not deselect if ArrowDown is pressed', () => {
const event = new KeyboardEvent('keyup', { key: 'ArrowDown' });
window.dispatchEvent(event);
fixture.detectChanges();
expect(component.onKeyDown).toHaveBeenCalled();
expect(component['keyManager'].setActiveItem).not.toHaveBeenCalledWith(
null
);
expect(component.selectItem.emit).toHaveBeenCalledWith(
activeItemMock.result
);
});
});
});

View File

@@ -33,12 +33,16 @@ export class AutocompleteResultsComponent implements AfterViewInit {
constructor() {}
@HostListener('window:keyup', ['$event']) onKeyDown(event: KeyboardEvent) {
if (event.key === 'ArrowDown' || 'ArrowUp') {
if (this.isUpOrDownKey(event.key)) {
this.keyManager.onKeydown(event);
if (this.keyManager.activeItem) {
this.selectItem.emit(this.keyManager.activeItem.result);
}
}
if (this.shouldDeselectItem(event.key)) {
this.keyManager.setActiveItem(null);
}
}
ngAfterViewInit() {
@@ -57,4 +61,16 @@ export class AutocompleteResultsComponent implements AfterViewInit {
private getItemIndex(item: AutocompleteDTO): number {
return this.results.indexOf(item);
}
private shouldDeselectItem(key: string): boolean {
const isBackspace = key === 'Backspace';
const isLetter = key.length === 1 && /^[a-zA-Z]+$/.test(key);
const isNumber = key.length === 1 && /^[0-9]+$/.test(key);
return isBackspace || isLetter || isNumber;
}
private isUpOrDownKey(key: string): boolean {
return key === 'ArrowDown' || key === 'ArrowUp';
}
}

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './shelf-edit-actions.component';
export * from './shelf-edit-actions.module';
// end:ng42.barrel

View File

@@ -0,0 +1,8 @@
<div class="actions">
<button (click)="cancel.emit()"
class="isa-btn isa-btn-pill isa-btn-secondary isa-btn-shadow isa-btn-red-border isa-btn-medium">
Abbrechen</button><button (click)="save.emit()"
class="isa-btn isa-btn-pill isa-btn-primary isa-btn-shadow isa-btn-medium" [disabled]="disabled">
Speichern
</button>
</div>

View File

@@ -0,0 +1,16 @@
:host {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
.actions {
display: flex;
justify-content: center;
align-items: center;
& > .isa-btn {
margin: 0 31px;
}
}

View File

@@ -0,0 +1,22 @@
import {
Component,
ChangeDetectionStrategy,
Output,
EventEmitter,
Input,
} from '@angular/core';
@Component({
selector: 'app-shelf-edit-actions',
templateUrl: 'shelf-edit-actions.component.html',
styleUrls: ['./shelf-edit-actions.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfEditActionsComponent {
@Input() disabled = false;
@Output() save = new EventEmitter<void>();
@Output() cancel = new EventEmitter<void>();
constructor() {}
}

View File

@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ShelfEditActionsComponent } from './shelf-edit-actions.component';
@NgModule({
imports: [CommonModule],
exports: [ShelfEditActionsComponent],
declarations: [ShelfEditActionsComponent],
providers: [],
})
export class ShelfEditActionsModule {}

View File

@@ -0,0 +1,35 @@
<ng-container *ngIf="orderItemForm">
<app-shelf-edit-order-item-header [src]="orderItemForm.get('ean')?.value | createImageSourceFromEan"
[title]="orderItemForm.get('title')?.value" (expand)="expandGroup($event)" [isOpen]="isGroupOpen">
</app-shelf-edit-order-item-header>
<hr class="isa-content-spacer last" />
<form [formGroup]="orderItemForm" *ngIf="isGroupOpen">
<app-ui-text-input formControlName="quantity" label="Menge" suffix="x"></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input formControlName="price" label="Preis" suffix="EUR"></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input formControlName="ean" label="ISBN/EAN"></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input formControlName="targetBranch" label="Zielfiliale"></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input formControlName="supplier" label="Lieferant"></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input formControlName="ssc" label="Meldenummer"></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-select-input *ngIf="vatLabelPipe" formControlName="vat" [options]="vatOptions"
[optionsPipe]="vatOptionsPipe" [labelPipe]="{ transform: vatLabelTransform }" [valuePipe]="valuePipe"
label="MwSt">
</app-ui-select-input>
<hr class="isa-content-spacer" />
<app-ui-text-input formControlName="comment" label="Anmerkung"></app-ui-text-input>
<hr class="isa-content-spacer" />
</form>
</ng-container>

View File

@@ -0,0 +1,35 @@
import { ShelfEditOrderItemComponent } from '.';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateImageSourceFromEanPipe } from '../../pipes';
import { ShelfEditOrderItemHeaderComponent } from './header';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
fdescribe('ShelfEditOrderItemComponent', () => {
let fixture: ComponentFixture<ShelfEditOrderItemComponent>;
let component: ShelfEditOrderItemComponent;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [CommonModule, ReactiveFormsModule],
declarations: [
ShelfEditOrderItemComponent,
ShelfEditOrderItemHeaderComponent,
CreateImageSourceFromEanPipe,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ShelfEditOrderItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component instanceof ShelfEditOrderItemComponent).toBeTruthy();
});
});

View File

@@ -0,0 +1,53 @@
import {
Component,
ChangeDetectionStrategy,
Input,
OnInit,
SimpleChanges,
OnChanges,
ChangeDetectorRef,
SimpleChange,
} from '@angular/core';
import { FormGroup } from '@angular/forms';
import { VATDTO } from '@swagger/oms';
import { VatTypeToDisplayNamePipe } from '../../pipes/vat-type-to-display-name.pipe';
import { VatDtoToVatValuePipe } from '../../pipes/vat-dto-to-vat-value.pipe';
import { VatDtoToVatTypePipe } from '../../pipes/vat-dto-to-vat-type.pipe';
@Component({
selector: 'app-shelf-edit-order-item',
templateUrl: 'edit-order-item.component.html',
styleUrls: ['./edit-order-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfEditOrderItemComponent implements OnInit, OnChanges {
@Input() orderItemForm: FormGroup;
@Input() onlyChild: boolean;
@Input() firstChild: boolean;
@Input() vatOptions: VATDTO[];
vatLabelPipe: VatTypeToDisplayNamePipe;
vatOptionsPipe = new VatDtoToVatValuePipe();
valuePipe = new VatDtoToVatTypePipe();
isGroupOpen: boolean;
vatLabelTransform = (value) => {
return this.vatLabelPipe.transform(value, this.vatOptions);
};
constructor() {}
ngOnChanges({ vatOptions }: SimpleChanges) {
if (vatOptions.currentValue) {
this.vatLabelPipe = new VatTypeToDisplayNamePipe();
}
}
ngOnInit() {
this.isGroupOpen = this.onlyChild || this.firstChild;
}
expandGroup(shouldExpand: boolean) {
this.isGroupOpen = shouldExpand;
}
}

View File

@@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { IconModule } from '@libs/ui';
import { UiTextInputModule } from '@isa-ui/text-input';
import { ShelfEditOrderItemComponent } from './edit-order-item.component';
import { ShelfEditOrderItemHeaderComponent } from './header';
import { UiSelectInputModule } from '@isa-ui/select-input';
import { ShelfPipesModule } from '../../pipes';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
IconModule,
UiTextInputModule,
UiSelectInputModule,
ShelfPipesModule,
],
exports: [ShelfEditOrderItemComponent, ShelfEditOrderItemHeaderComponent],
declarations: [
ShelfEditOrderItemComponent,
ShelfEditOrderItemHeaderComponent,
],
providers: [],
})
export class ShelfEditOrderItemModule {}

View File

@@ -0,0 +1,24 @@
<div class="header">
<div>
<img
*ngIf="src"
class="isa-thumbnail"
[src]="src"
alt="item.product.name"
/>
</div>
<div class="title isa-font-weight-bold">{{ title }}</div>
<div
class="collapse"
*ngIf="showCollapseArrow"
(click)="expand.emit(!isOpen)"
>
<lib-icon
class="isa-accordion-arrow"
[class.arrow-up]="!isOpen"
[class.arrow-down]="isOpen"
name="Arrow_right"
[height]="'16px'"
></lib-icon>
</div>
</div>

View File

@@ -0,0 +1,13 @@
.header {
display: flex;
align-items: center;
margin: 30px 0;
.title {
margin-left: 20px;
}
.collapse {
margin-left: auto;
}
}

View File

@@ -0,0 +1,25 @@
import {
Component,
ChangeDetectionStrategy,
Input,
Output,
EventEmitter,
} from '@angular/core';
@Component({
selector: 'app-shelf-edit-order-item-header',
templateUrl: 'edit-order-item-header.component.html',
styleUrls: ['./edit-order-item-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfEditOrderItemHeaderComponent {
@Input() showCollapseArrow = true;
@Input() isOpen = false;
@Input() src: string;
@Input() title: string;
@Output() expand = new EventEmitter<boolean>();
constructor() {}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './edit-order-item-header.component';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './edit-order-item.component';
export * from './edit-order-item.module';
// end:ng42.barrel

View File

@@ -0,0 +1,14 @@
<div class="log-entry mt-40" [class.last]="last" [class.first]="first">
<span class="timeline-dot"></span>
<app-shelf-history-log-header
[infoText]="history | historyDtoToInfoText"
[statusText]="history | historyDtoToStatusText"
></app-shelf-history-log-header>
<app-shelf-history-log-body
*ngFor="let changes of history.values"
[oldStatus]="changes | getStatusFromHistory: 'old'"
[newStatus]="changes | getStatusFromHistory: 'new'"
[changedBy]="history.changedBy"
[caption]="changes.caption"
></app-shelf-history-log-body>
</div>

View File

@@ -0,0 +1,61 @@
@import 'variables';
$offset: 10px;
$line-top-offset: 3px;
$dot-size: 16px;
$dot-size-lg: 20px;
$border-size: 1px;
$border-size-cover: 2px;
.log-entry {
position: relative;
// calculate offset relative to container offset (22px)
padding-left: calc(70px - 22px);
&:before {
content: '';
position: absolute;
height: 130%;
width: 1px;
top: $line-top-offset;
left: $offset;
border: $border-size solid $isa-light-blue-platinum;
background: $isa-light-blue-platinum;
}
&.last:before {
height: 30%;
border: $border-size-cover solid #fff;
background: #fff;
left: calc(#{$offset} - #{$border-size-cover});
}
.timeline-dot {
position: absolute;
top: 0;
left: $offset;
background: #fff;
width: 2px;
&:before {
content: '';
position: absolute;
background: $isa-light-blue-platinum;
top: $line-top-offset;
left: calc((#{$dot-size} - #{$line-top-offset}) / -2);
border-radius: 50%;
width: $dot-size;
height: $dot-size;
}
}
&.first {
.timeline-dot:before {
width: $dot-size-lg;
height: $dot-size-lg;
background: $isa-neutral-info;
left: calc(#{$dot-size} / -2);
}
}
}

View File

@@ -0,0 +1,72 @@
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { ShelfHistoryLogComponent } from './history-log.component';
import { ShelfHistoryLogHeaderComponent } from './log-header';
import { ShelfHistoryLogBodyComponent } from './log-body';
import { By } from '@angular/platform-browser';
import { historiesDtoMock, historyDtoMock } from '../../shared/mockdata';
import {
HistoryDtoToInfoTextPipe,
HistoryDtoToStatusTextPipe,
GetStatusFromHistoryPipe,
} from '../../pages/shelf-history/pipes';
import { HistoryDTO } from '@cmf/trade-api';
fdescribe('HistoryLogComponent', () => {
let fixture: ComponentFixture<ShelfHistoryLogComponent>;
let component: ShelfHistoryLogComponent;
let debugElement: DebugElement;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
ShelfHistoryLogComponent,
ShelfHistoryLogHeaderComponent,
ShelfHistoryLogBodyComponent,
HistoryDtoToInfoTextPipe,
HistoryDtoToStatusTextPipe,
GetStatusFromHistoryPipe,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ShelfHistoryLogComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
const historyMockWithMultipleLogEntries: HistoryDTO = historiesDtoMock.find(
(history) => history.values && history.values.length > 2
);
component.history = historyMockWithMultipleLogEntries;
fixture.detectChanges();
});
it('should be created', () => {
expect(component instanceof ShelfHistoryLogComponent).toBeTruthy();
});
it('should show a header for all history values (log entries)', () => {
const header = debugElement.queryAll(
By.css('app-shelf-history-log-header')
);
expect(header.length).toBe(1);
});
it('should show an entry for each history value (log entry)', () => {
const historyMockWithMultipleLogEntries: HistoryDTO = historiesDtoMock.find(
(history) => history.values && history.values.length > 2
);
fixture.detectChanges();
const entries = debugElement.queryAll(By.css('app-shelf-history-log-body'));
expect(entries.length).toBe(
historyMockWithMultipleLogEntries.values.length
);
});
});

View File

@@ -0,0 +1,16 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { HistoryDTO } from '@cmf/trade-api';
@Component({
selector: 'app-shelf-history-log',
templateUrl: './history-log.component.html',
styleUrls: ['./history-log.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryLogComponent {
@Input() first = false;
@Input() last = false;
@Input() history: HistoryDTO;
constructor() {}
}

View File

@@ -0,0 +1,31 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
HistoryDtoToInfoTextPipe,
HistoryDtoToStatusTextPipe,
GetStatusFromHistoryPipe,
} from '../../pages/shelf-history/pipes';
import { IconModule } from '@libs/ui';
import { ShelfHistoryLogComponent } from './history-log.component';
import { ShelfHistoryLogHeaderComponent } from './log-header';
import { ShelfHistoryLogBodyComponent } from './log-body';
@NgModule({
imports: [CommonModule, IconModule],
declarations: [
ShelfHistoryLogComponent,
ShelfHistoryLogHeaderComponent,
ShelfHistoryLogBodyComponent,
HistoryDtoToInfoTextPipe,
HistoryDtoToStatusTextPipe,
GetStatusFromHistoryPipe,
],
exports: [
ShelfHistoryLogComponent,
ShelfHistoryLogHeaderComponent,
ShelfHistoryLogBodyComponent,
],
providers: [],
})
export class ShelfHistoryLogModule {}

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