Compare commits

..

14 Commits

Author SHA1 Message Date
Lorenz Hilpert
a27a0c8b9c Zxing ask for camera permission 2022-05-31 15:55:04 +02:00
Lorenz Hilpert
acb76c1384 zxing scanner poc 2022-05-31 13:22:31 +02:00
Lorenz Hilpert
5882655862 Scanner performance update 2022-05-16 17:36:49 +02:00
Lorenz Hilpert
5b440da557 Scandit aktiviert 2022-05-16 16:11:29 +02:00
Lorenz Hilpert
be91a0f1b4 Scan performance test 2022-05-16 15:53:57 +02:00
Lorenz Hilpert
65e3d5d4f1 Update Gitignore 2022-05-16 15:28:02 +02:00
Lorenz Hilpert
a0e7614fa0 Remove Scandit Folder Content 2022-05-16 15:25:25 +02:00
Lorenz Hilpert
094c881e7f Scanner Performance Update 2022-05-16 15:20:10 +02:00
Lorenz Hilpert
d3cd6f415b Ipad 6 fix scandit 2022-04-19 20:39:58 +02:00
Lorenz Hilpert
e733396c63 Fix Engine Location 2022-04-19 19:08:02 +02:00
Lorenz Hilpert
e88795f96b Fix Reader 2022-04-19 15:56:35 +02:00
Lorenz Hilpert
fd276b6553 scandit typo 2022-04-19 15:21:21 +02:00
Lorenz Hilpert
22c3b057d7 scandit licence 2022-04-19 15:20:14 +02:00
Lorenz Hilpert
b7feea46f7 scandit implementation 2022-04-19 14:58:15 +02:00
2956 changed files with 148132 additions and 22410 deletions

2
.gitignore vendored
View File

@@ -46,4 +46,4 @@ testem.log
.DS_Store
Thumbs.db
libs/swagger/src/lib/*
libs/swagger/src/lib/*

4
.npmrc
View File

@@ -1 +1,3 @@
@paragondata:registry=https://npm.pkg.github.com
@isa:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
@cmf:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
always-auth=true

View File

@@ -5,6 +5,7 @@
/helmvalues
/apps/swagger
/ng-swagger-gen
/apps/isa-app/src/assets
*.json
*.yml

View File

@@ -3,6 +3,256 @@
"version": 1,
"newProjectRoot": "apps",
"projects": {
"ui": {
"root": "libs/ui",
"sourceRoot": "libs/ui",
"projectType": "library",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "libs/ui/tsconfig.lib.json",
"project": "libs/ui/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "libs/ui/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "libs/ui/src/test.ts",
"tsConfig": "libs/ui/tsconfig.spec.json",
"karmaConfig": "libs/ui/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"libs/ui/tsconfig.lib.json",
"libs/ui/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"sales": {
"root": "apps/sales/",
"sourceRoot": "apps/sales/src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"aot": true,
"outputPath": "dist/sales",
"index": "apps/sales/src/index.html",
"main": "apps/sales/src/main.ts",
"polyfills": "apps/sales/src/polyfills.ts",
"tsConfig": "apps/sales/tsconfig.app.json",
"assets": [
"apps/sales/src/favicon.ico",
"apps/sales/src/assets",
"apps/sales/src/manifest.webmanifest",
"apps/sales/src/browserconfig.xml",
"apps/sales/src/silent-refresh.html"
],
"styles": [
"apps/sales/src/styles.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"apps/sales/src/scss"
]
},
"scripts": [],
"customWebpackConfig": {
"path": "apps/sales/webpack.config.js"
}
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "apps/sales/src/environments/environment.ts",
"with": "apps/sales/src/environments/environment.prod.ts"
}
],
"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"
},
{
"type": "anyComponentStyle",
"maximumWarning": "25kb"
}
],
"serviceWorker": true
},
"development": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "25kb"
}
]
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "sales:build"
},
"configurations": {
"test": {
"browserTarget": "sales:build:test"
},
"integration": {
"browserTarget": "sales:build:integration"
},
"staging": {
"browserTarget": "sales:build:staging"
},
"production": {
"browserTarget": "sales:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "sales:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/sales/src/test.ts",
"polyfills": "apps/sales/src/polyfills.ts",
"tsConfig": "apps/sales/tsconfig.spec.json",
"karmaConfig": "apps/sales/karma.conf.js",
"styles": [
"apps/sales/src/styles.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"apps/sales/src/scss"
]
},
"scripts": [],
"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"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"sales-e2e": {
"root": "apps/sales-e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "apps/sales-e2e/protractor.conf.js",
"devServerTarget": "sales:serve"
},
"configurations": {
"integration": {
"devServerTarget": "sales:serve:integration"
},
"production": {
"devServerTarget": "sales:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "apps/sales-e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"sso": {
"root": "libs/sso",
"sourceRoot": "libs/sso/src",
"projectType": "library",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "libs/sso/tsconfig.lib.json",
"project": "libs/sso/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "libs/sso/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "libs/sso/src/test.ts",
"tsConfig": "libs/sso/tsconfig.spec.json",
"karmaConfig": "libs/sso/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"libs/sso/tsconfig.lib.json",
"libs/sso/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@swagger/availability": {
"root": "apps/swagger/availability",
"sourceRoot": "apps/swagger/availability/src",
@@ -3060,11 +3310,6 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": [
"lodash",
"pdfjs-dist/es5/build/pdf",
"pdfjs-dist/es5/web/pdf_viewer"
],
"outputPath": "dist/isa-app",
"index": "apps/isa-app/src/index.html",
"main": "apps/isa-app/src/main.ts",
@@ -3090,12 +3335,13 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "25kb"
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
@@ -3598,68 +3844,6 @@
}
}
}
},
"@core/toast": {
"projectType": "library",
"root": "apps/core/toast",
"sourceRoot": "apps/core/toast/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/core/toast/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/core/toast/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/core/toast/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/core/toast/src/test.ts",
"tsConfig": "apps/core/toast/tsconfig.spec.json",
"karmaConfig": "apps/core/toast/karma.conf.js"
}
}
}
},
"@ui/form-field": {
"projectType": "library",
"root": "apps/ui/form-field",
"sourceRoot": "apps/ui/form-field/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/ui/form-field/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/form-field/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/ui/form-field/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/form-field/src/test.ts",
"tsConfig": "apps/ui/form-field/tsconfig.spec.json",
"karmaConfig": "apps/ui/form-field/karma.conf.js"
}
}
}
}
},
"defaultProject": "isa-app"

View File

@@ -18,7 +18,7 @@ export class NativeScanAdapter implements ScanAdapter {
isReady(): boolean {
// TODO: Fix Login Keycard Dauerschleife
return this.nativeContainerService.isUiWebview().isNative || this.nativeContainerService.isIpadMini6();
return this.nativeContainerService.isUiWebview().isNative;
// return false;
}

View File

@@ -1,14 +1,24 @@
import { NgModule } from '@angular/core';
import { DevScanAdapter } from './dev.scan-adapter';
import { NativeScanAdapter } from './native.scan-adapter';
import { ScanditModalModule } from './scandit-modal';
import { ScanditScanAdapter } from './scandit.scan-adapter';
import { SCAN_ADAPTER } from './tokens';
import { ZxingModalModule } from './zxing-modal';
import { ZxingScanAdapter } from './zxing.scan-adapter';
@NgModule({})
@NgModule({
imports: [ScanditModalModule, ZxingModalModule],
})
export class ScanAdapterModule {
static forRoot(dev?: boolean) {
return {
ngModule: ScanAdapterModule,
providers: [{ provide: SCAN_ADAPTER, useClass: NativeScanAdapter, multi: true }],
providers: [
{ provide: SCAN_ADAPTER, useClass: NativeScanAdapter, multi: true },
// { provide: SCAN_ADAPTER, useClass: ScanditScanAdapter, multi: true },
{ provide: SCAN_ADAPTER, useClass: ZxingScanAdapter, multi: true },
],
// Use for testing:
// providers: [{ provide: SCAN_ADAPTER, useClass: dev ? DevScanAdapter : NativeScanAdapter, multi: true }],
};

View File

@@ -0,0 +1,2 @@
export * from './scandit-modal.component';
export * from './scandit-modal.module';

View File

@@ -0,0 +1 @@
<div class="scanner-container" #scanContainer></div>

View File

@@ -0,0 +1,4 @@
.scanner-container {
@apply mt-8;
max-height: calc(100vh - 10rem);
}

View File

@@ -0,0 +1,86 @@
import { Component, ChangeDetectionStrategy, ViewChild, ElementRef, AfterViewInit, NgZone, Inject, OnDestroy } from '@angular/core';
import { UiMessageModalComponent, UiModalRef, UiModalService } from '@ui/modal';
import { Barcode, BarcodePicker, ScanSettings } from 'scandit-sdk';
@Component({
selector: 'scandit-modal',
templateUrl: 'scandit-modal.component.html',
styleUrls: ['scandit-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ScanditModalComponent implements AfterViewInit, OnDestroy {
private _barcodePicker: BarcodePicker;
@ViewChild('scanContainer', { read: ElementRef, static: true }) scanContainer: ElementRef;
constructor(private _modalRef: UiModalRef, private readonly _zone: NgZone, private readonly _modal: UiModalService) {}
ngAfterViewInit(): void {
this._zone.runOutsideAngular(() => {
BarcodePicker.create(this.scanContainer.nativeElement, {
playSoundOnScan: true,
vibrateOnScan: true,
})
.then(async (picker) => {
this._barcodePicker = picker;
var scanSettings = new ScanSettings({
blurryRecognition: false,
enabledSymbologies: [
Barcode.Symbology.EAN8,
Barcode.Symbology.EAN13,
Barcode.Symbology.UPCA,
Barcode.Symbology.UPCE,
Barcode.Symbology.CODE128,
Barcode.Symbology.CODE39,
Barcode.Symbology.CODE93,
Barcode.Symbology.INTERLEAVED_2_OF_5,
Barcode.Symbology.QR,
],
codeDuplicateFilter: 1000,
});
picker.applyScanSettings(scanSettings);
picker.on('scan', (barcode) => {
this._zone.run(() => {
if (barcode.barcodes.length) {
this._modalRef.close(barcode.barcodes[0].data);
} else if (barcode.texts.length) {
this._modalRef.close(barcode.texts[0].value);
}
});
});
})
.catch((err) => {
this._zone.run(() => {
this.cancel();
this._modal
.open({
content: UiMessageModalComponent,
data: {
message: `
Scanner kann nicht aktiviert werden.
Bitte stellen Sie sicher, dass der zugriff auf die Kamera erlaubt ist.
`,
},
})
.afterClosed$.subscribe(() => {
this.cancel();
});
});
});
});
}
cancel() {
this._modalRef.close();
}
ngOnDestroy(): void {
this._zone.runOutsideAngular(() => {
this._barcodePicker?.destroy(true);
});
}
}

View File

@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ScanditModalComponent } from './scandit-modal.component';
@NgModule({
imports: [CommonModule],
exports: [ScanditModalComponent],
declarations: [ScanditModalComponent],
})
export class ScanditModalModule {}

View File

@@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { UiModalService } from '@ui/modal';
import { Observable } from 'rxjs';
import { ScanAdapter } from './scan-adapter';
import { configure } from 'scandit-sdk';
import { ScanditModalComponent } from './scandit-modal';
import { filter, map } from 'rxjs/operators';
import { Config } from '@core/config';
@Injectable()
export class ScanditScanAdapter implements ScanAdapter {
private _isReady = false;
constructor(private readonly _modal: UiModalService, private readonly _config: Config) {
this.configure();
}
private async configure() {
await configure(this._config.get('licence.scandit'), {
engineLocation: '/assets/scandit/',
});
this._isReady = true;
}
getName(): string {
return 'Scandit';
}
isPrimary(): boolean {
return false;
}
isReady(): boolean {
return this._isReady;
}
scan(): Observable<string> {
return this._modal
.open({
content: ScanditModalComponent,
})
.afterClosed$.pipe(
map((result) => result.data),
filter((result) => !!result)
);
}
}

View File

@@ -0,0 +1,2 @@
export * from './zxing-modal.component';
export * from './zxing-modal.module';

View File

@@ -0,0 +1,17 @@
<!-- <select>
<options [value]="dev.deviceId" *ngFor="let dev in devices$ | async">
{{ dev.label }}
</options>
</select> -->
<div class="device-container">
<button
[class.selected]="(selectedDevice$ | async) === dev.deviceId"
*ngFor="let dev of devices$ | async"
(click)="setSelectedDevice(dev.deviceId)"
>
{{ dev.label }}
</button>
</div>
<video class="scanner-container" #scanContainer></video>

View File

@@ -0,0 +1,17 @@
.scanner-container {
// @apply mt-8;
max-height: calc(100vh - 10rem);
width: 100%;
}
button {
@apply bg-white text-brand px-3 py-2;
&.selected {
@apply bg-brand text-white;
}
}
.device-container {
@apply flex flex-row items-center justify-center my-4;
}

View File

@@ -0,0 +1,104 @@
import { Component, ChangeDetectionStrategy, ViewChild, ElementRef, AfterViewInit, NgZone, Inject, OnDestroy, OnInit } from '@angular/core';
import { UiMessageModalComponent, UiModalRef, UiModalService } from '@ui/modal';
import { BarcodeFormat, DecodeHintType, BrowserMultiFormatReader } from '@zxing/library';
import { BehaviorSubject, from, ReplaySubject, Subject } from 'rxjs';
import { filter, takeUntil, tap } from 'rxjs/operators';
@Component({
selector: 'zxing-modal',
templateUrl: 'zxing-modal.component.html',
styleUrls: ['zxing-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZxingModalComponent implements OnInit, OnDestroy {
private _reader: BrowserMultiFormatReader;
@ViewChild('scanContainer', { read: ElementRef, static: true }) scanContainer: ElementRef;
devices$ = new ReplaySubject<MediaDeviceInfo[]>();
selectedDevice$ = new BehaviorSubject<string>(null);
get selectedDevice() {
return this.selectedDevice$.value;
}
private _onDestroy$ = new Subject<void>();
constructor(private _modalRef: UiModalRef, private readonly _zone: NgZone, private readonly _modal: UiModalService) {}
async ngOnInit() {
try {
const mediaStream: MediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
mediaStream.getVideoTracks().forEach((track) => track.stop());
const hints = new Map<DecodeHintType, any>();
const formats = [
BarcodeFormat.EAN_8,
BarcodeFormat.EAN_13,
BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E,
BarcodeFormat.CODE_128,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_93,
BarcodeFormat.ITF,
BarcodeFormat.QR_CODE,
];
hints.set(DecodeHintType.POSSIBLE_FORMATS, formats);
this._reader = new BrowserMultiFormatReader(hints, 1000);
from(this._reader.listVideoInputDevices()).subscribe((devices) => {
this.devices$.next(devices);
this.selectedDevice$.next(devices[0].deviceId);
});
this.selectedDevice$
.pipe(
takeUntil(this._onDestroy$),
filter((v) => !!v),
tap(() => {
this._reader?.reset();
})
)
.subscribe((device) => {
this._reader.decodeFromVideoDevice(device, this.scanContainer.nativeElement, (result) => {
if (result) {
this._modalRef.close(result.getText());
}
});
});
} catch (error) {
this.cancel();
this._modal
.open({
content: UiMessageModalComponent,
data: {
message: `
Scanner kann nicht aktiviert werden.
Bitte stellen Sie sicher, dass der zugriff auf die Kamera erlaubt ist.
`,
},
})
.afterClosed$.subscribe(() => {
this.cancel();
});
}
}
cancel() {
this._modalRef.close();
}
ngOnDestroy(): void {
this._onDestroy$.next();
this._reader?.reset();
}
setSelectedDevice(id: string) {
this.selectedDevice$.next(id);
}
}

View File

@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ZxingModalComponent } from './zxing-modal.component';
@NgModule({
imports: [CommonModule],
exports: [ZxingModalComponent],
declarations: [ZxingModalComponent],
})
export class ZxingModalModule {}

View File

@@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { UiModalService } from '@ui/modal';
import { Observable } from 'rxjs';
import { ScanAdapter } from './scan-adapter';
import { filter, map } from 'rxjs/operators';
import { Config } from '@core/config';
import { ZxingModalComponent } from './zxing-modal/zxing-modal.component';
@Injectable()
export class ZxingScanAdapter implements ScanAdapter {
private _isReady = true;
constructor(private readonly _modal: UiModalService, private readonly _config: Config) {}
getName(): string {
return 'Zxing';
}
isPrimary(): boolean {
return false;
}
isReady(): boolean {
return this._isReady;
}
scan(): Observable<string> {
return this._modal
.open({
content: ZxingModalComponent,
})
.afterClosed$.pipe(
map((result) => result.data),
filter((result) => !!result)
);
}
}

View File

@@ -181,8 +181,7 @@ describe('ApplicationService', () => {
expect(store.dispatch).toHaveBeenCalledWith({
type: actions.patchProcess.type,
processId: process.id,
changes: {
process: {
...process,
},
});

View File

@@ -60,7 +60,7 @@ describe('applicationReducer', () => {
type: 'cart',
};
const action = actions.patchProcess({ processId: process.id, changes: { ...process, name: 'Test' } });
const action = actions.patchProcess({ process: { ...process, name: 'Test' } });
const state = applicationReducer(
{
...initialState,
@@ -81,7 +81,7 @@ describe('applicationReducer', () => {
type: 'cart',
};
const action = actions.patchProcess({ processId: process.id, changes: { ...process, id: 2 } });
const action = actions.patchProcess({ process: { ...process, id: 2 } });
const state = applicationReducer(
{
...initialState,

View File

@@ -23,7 +23,7 @@ const _applicationReducer = createReducer(
on(patchProcess, (state, { processId, changes }) => {
const processes = state.processes.map((process) => {
if (process.id === processId) {
return { ...process, ...changes, id: processId };
return { ...process, ...changes };
}
return process;
});

View File

@@ -31,11 +31,7 @@ export class AuthService {
this._oAuthService.tokenValidationHandler = new JwksValidationHandler();
this._oAuthService.setupAutomaticSilentRefresh();
try {
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
} catch (error) {
this.login();
}
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
this._initialized.next(true);
}

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { CacheOptions } from './cache-options';
import { Cached } from './cached';
import { sha1 } from 'object-hash';
@Injectable({
providedIn: 'root',
@@ -50,7 +51,7 @@ export class CacheService {
return cached.data;
}
delete(token: Object, from: 'session' | 'persist' = 'session') {
private delete(token: Object, from: 'session' | 'persist' = 'session') {
if (from === 'session') {
sessionStorage.removeItem(this.getKey(token));
} else if (from === 'persist') {
@@ -59,15 +60,7 @@ export class CacheService {
}
private getKey(token: Object) {
return this.hash(JSON.stringify(token));
}
private hash(data: string): string {
let hash = 0;
for (let i = 0; i < data.length; i++) {
hash = data.charCodeAt(i) + ((hash << 5) - hash);
}
return hash.toString(16);
return sha1(token);
}
private serialize(data: Cached): string {

View File

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

View File

@@ -1,25 +0,0 @@
# Toast
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0.
## Code scaffolding
Run `ng generate component component-name --project toast` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project toast`.
> Note: Don't forget to add `--project toast` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build toast` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build toast`, go to the dist folder `cd dist/toast` and run `npm publish`.
## Running unit tests
Run `ng test toast` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -1,7 +0,0 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/core/toast",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -1,11 +0,0 @@
{
"name": "@core/toast",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^12.2.0",
"@angular/core": "^12.2.0"
},
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -1,14 +0,0 @@
import { AnimationTriggerMetadata, trigger, state, transition, style, animate } from '@angular/animations';
export const slideAnimationTime = 150;
export const toastAnimations: {
readonly slideToast: AnimationTriggerMetadata;
} = {
slideToast: trigger('slideAnimation', [
state('default', style({ transform: 'translateY(0%)' })),
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(`${slideAnimationTime}ms ease-in`)]),
transition('default => closing', animate(`${slideAnimationTime}ms ease-in`, style({ transform: 'translateY(-100%)' }))),
]),
};
export type ToastAnimationState = 'default' | 'closing';

View File

@@ -1,4 +0,0 @@
// start:ng42.barrel
export * from './toast';
export * from './toast-ref';
// end:ng42.barrel

View File

@@ -1,17 +0,0 @@
import { OverlayRef } from '@angular/cdk/overlay';
export class ToastRef {
constructor(private readonly _overlay: OverlayRef) {}
close() {
this._overlay.dispose();
}
isVisible() {
return this._overlay && this._overlay.overlayElement;
}
getPosition() {
return this._overlay.overlayElement.getBoundingClientRect();
}
}

View File

@@ -1,11 +0,0 @@
import { TemplateRef } from '@angular/core';
export interface Toast {
title?: string;
text?: string;
timer?: number;
position?: 'top-left' | 'top' | 'top-right' | 'bottom-right' | 'bottom' | 'bottom-left';
size?: 'width-full' | 'content';
template?: TemplateRef<any>; // For rendering dynamic content
templateContext?: {}; // For rendering dynamic content
}

View File

@@ -1,8 +0,0 @@
// start:ng42.barrel
export * from './toast.component';
export * from './toast.module';
export * from './toast.service';
export * from './defs';
export * from './animation';
export * from './tokens';
// end:ng42.barrel

View File

@@ -1,15 +0,0 @@
<div class="toast-main" [style.width]="width" [@slideAnimation]="{ value: animationState }" (@slideAnimation.done)="onSlideFinished()">
<button class="absolute top-2 right-2 p-6 border-none bg-transparent" (click)="close()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
<div class="toast-content flex flex-col justify-center items-center">
<h1 class="text-card-sub font-bold text-center py-3 whitespace-pre-wrap">{{ data.title }}</h1>
<ng-container *ngIf="data.text; else templateRef">
<p class="block text-base overflow-y-hidden pb-3 text-center overflow-x-hidden">{{ data.text }}</p>
</ng-container>
</div>
</div>
<ng-template #templateRef>
<ng-container *ngTemplateOutlet="data.template; context: data.templateContext"> </ng-container>
</ng-template>

View File

@@ -1,12 +0,0 @@
.toast-main {
@apply block relative mx-auto box-border text-white p-4;
background-color: var(--toast-background);
min-width: 18.75rem;
max-width: calc(100vw - 2rem);
min-height: 5rem;
border-radius: 25px;
}
.toast-content {
min-height: 3rem;
}

View File

@@ -1,24 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ToastComponent } from './toast.component';
describe('ToastComponent', () => {
let component: ToastComponent;
let fixture: ComponentFixture<ToastComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ToastComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ToastComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,48 +0,0 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { toastAnimations, ToastAnimationState, slideAnimationTime } from './animation';
import { Toast, ToastRef } from './defs';
import { TOAST_CONFIG_TOKEN } from './tokens';
@Component({
selector: 'lib-toast',
templateUrl: 'toast.component.html',
styleUrls: ['toast.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [toastAnimations.slideToast],
})
export class ToastComponent implements OnInit, OnDestroy {
timeoutRef?: any;
animationState: ToastAnimationState = 'default';
width = '55.25rem';
constructor(
@Inject(TOAST_CONFIG_TOKEN) public readonly data: Toast,
private readonly _ref: ToastRef,
private readonly _cdr: ChangeDetectorRef
) {}
ngOnInit(): void {
if (this.data?.size) {
this.width = this.data?.size === 'width-full' ? '100vw' : '55.25rem';
}
this.timeoutRef = setTimeout(() => {
this.close();
this._cdr.markForCheck();
}, slideAnimationTime + (this.data.timer ?? 5000));
}
ngOnDestroy() {
clearTimeout(this.timeoutRef);
}
close() {
this.animationState = 'closing';
}
onSlideFinished() {
if (this.animationState === 'closing') {
this._ref.close();
}
}
}

View File

@@ -1,12 +0,0 @@
import { OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { ToastComponent } from './toast.component';
@NgModule({
declarations: [ToastComponent],
imports: [CommonModule, OverlayModule, UiIconModule],
exports: [ToastComponent],
})
export class ToastModule {}

View File

@@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ToastService } from './toast.service';
describe('ToastService', () => {
let service: ToastService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ToastService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -1,79 +0,0 @@
import { Overlay } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { Injectable, Injector } from '@angular/core';
import { Toast, ToastRef } from './defs';
import { ToastComponent } from './toast.component';
import { TOAST_CONFIG_TOKEN } from './tokens';
@Injectable({
providedIn: 'root',
})
export class ToastService {
private _lastToastRef: ToastRef;
get lastToastRef() {
return this._lastToastRef;
}
set lastToastRef(toastRef: ToastRef) {
this._lastToastRef = toastRef;
}
constructor(private readonly _overlay: Overlay, private readonly _injector: Injector) {}
create(data: Toast) {
const positionStrategy = this.getPositionStrategy(data);
const overlayRef = this._overlay.create({ positionStrategy });
this.lastToastRef = new ToastRef(overlayRef);
const injector = this.getInjector(data, this.lastToastRef);
const toastPortal = new ComponentPortal(ToastComponent, null, injector);
overlayRef.attach(toastPortal);
return this.lastToastRef;
}
getInjector(data: Toast, ref: ToastRef) {
return Injector.create({
parent: this._injector,
providers: [
{ provide: TOAST_CONFIG_TOKEN, useValue: data },
{ provide: ToastRef, useValue: ref },
],
});
}
getPositionStrategy(data: Toast) {
switch (data?.position) {
case 'top':
return this._overlay.position().global().top(this.getNextPosition()).centerHorizontally();
case 'top-left':
return this._overlay.position().global().top(this.getNextPosition()).left('1rem');
case 'top-right':
return this._overlay.position().global().top(this.getNextPosition()).right('1rem');
case 'bottom':
return this._overlay.position().global().bottom(this.getNextPosition(true)).centerHorizontally();
case 'bottom-left':
return this._overlay.position().global().bottom(this.getNextPosition(true)).left('1rem');
case 'bottom-right':
return this._overlay.position().global().bottom(this.getNextPosition(true)).right('1rem');
default:
return this._overlay.position().global().top(this.getNextPosition()).centerHorizontally();
}
}
getNextPosition(fromBottom?: boolean) {
const lastToastIsVisible = this.lastToastRef && this.lastToastRef.isVisible();
let position = fromBottom ? 6 : 9;
if (lastToastIsVisible && fromBottom) {
position = (window.innerHeight - this.lastToastRef.getPosition().bottom + this.lastToastRef.getPosition().height + 16) / 16;
} else if (lastToastIsVisible) {
position = (this.lastToastRef.getPosition().bottom + 16) / 16;
}
return position + 'rem';
}
}

View File

@@ -1,4 +0,0 @@
import { InjectionToken } from '@angular/core';
import { Toast } from './defs';
export const TOAST_CONFIG_TOKEN = new InjectionToken<Toast>('TOAST_DATA');

View File

@@ -1,20 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -1,17 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -1,42 +1,34 @@
import { Injectable } from '@angular/core';
import { ItemDTO } from '@swagger/cat';
import {
AvailabilityDTO,
BranchDTO,
OLAAvailabilityDTO,
StoreCheckoutBranchService,
StoreCheckoutSupplierService,
SupplierDTO,
} from '@swagger/checkout';
import { ItemDTO, SearchService } from '@swagger/cat';
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, StoreCheckoutService, SupplierDTO } from '@swagger/checkout';
import { combineLatest, Observable, of } from 'rxjs';
import {
AvailabilityRequestDTO,
AvailabilityService,
AvailabilityService as SwaggerAvailabilityService,
AvailabilityDTO as SwaggerAvailabilityDTO,
AvailabilityType,
} from '@swagger/availability';
import { AvailabilityDTO as CatAvailabilityDTO } from '@swagger/cat';
import { map, shareReplay, switchMap, withLatestFrom, mergeMap, timeout } from 'rxjs/operators';
import { isArray, memorize } from '@utils/common';
import { LogisticianDTO, LogisticianService } from '@swagger/oms';
import { OrderService } from '@swagger/oms';
import { ResponseArgsOfIEnumerableOfStockInfoDTO, StockDTO, StockInfoDTO, StockService } from '@swagger/remi';
import { ItemData } from './defs/item-data.model';
import { PriceDTO } from '@swagger/availability';
import { AvailabilityByBranchDTO, ItemData } from './defs';
import { Availability } from './defs/availability';
import { AvailabilityByBranchDTO } from './defs/availability-by-branch-dto.model';
@Injectable()
export class DomainAvailabilityService {
constructor(
private _availabilityService: AvailabilityService,
private _logisticanService: LogisticianService,
private _stockService: StockService,
private _supplierService: StoreCheckoutSupplierService,
private _branchService: StoreCheckoutBranchService
private swaggerAvailabilityService: SwaggerAvailabilityService,
private storeCheckoutService: StoreCheckoutService,
private orderService: OrderService,
private _stock: StockService
) {}
@memorize()
getSuppliers(): Observable<SupplierDTO[]> {
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
map((response) => response.result),
shareReplay()
);
@@ -44,15 +36,15 @@ export class DomainAvailabilityService {
@memorize()
getTakeAwaySupplier(): Observable<SupplierDTO> {
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
map(({ result }) => result?.find((supplier) => supplier?.supplierNumber === 'F')),
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
map(({ result }) => result.find((supplier) => supplier?.supplierNumber === 'F')),
shareReplay()
);
}
@memorize()
getBranches(): Observable<BranchDTO[]> {
return this._branchService.StoreCheckoutBranchGetBranches({}).pipe(
return this.storeCheckoutService.StoreCheckoutGetBranches({}).pipe(
map((response) => response.result),
shareReplay()
);
@@ -60,7 +52,7 @@ export class DomainAvailabilityService {
@memorize()
getCurrentStock(): Observable<StockDTO> {
return this._stockService.StockCurrentStock().pipe(
return this._stock.StockCurrentStock().pipe(
map((response) => response.result),
shareReplay()
);
@@ -68,7 +60,7 @@ export class DomainAvailabilityService {
@memorize()
getCurrentBranch(): Observable<BranchDTO> {
return this._stockService.StockCurrentBranch().pipe(
return this._stock.StockCurrentBranch().pipe(
map((response) => ({
id: response.result.id,
name: response.result.name,
@@ -91,8 +83,8 @@ export class DomainAvailabilityService {
}
@memorize({})
getLogisticians(): Observable<LogisticianDTO> {
return this._logisticanService.LogisticianGetLogisticians({}).pipe(
getLogisticians() {
return this.orderService.OrderGetLogisticians({}).pipe(
map((response) => response.result?.find((l) => l.logisticianNumber === '2470')),
shareReplay()
);
@@ -109,7 +101,7 @@ export class DomainAvailabilityService {
price: PriceDTO;
quantity: number;
}): Observable<AvailabilityByBranchDTO[]> {
return this._stockService.StockStockRequest({ stockRequest: { branchIds, itemId } }).pipe(
return this._stock.StockStockRequest({ stockRequest: { branchIds, itemId } }).pipe(
map((response) => response.result),
withLatestFrom(this.getTakeAwaySupplier()),
map(([result, supplier]) => {
@@ -133,13 +125,8 @@ export class DomainAvailabilityService {
getTakeAwayAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this.getCurrentStock().pipe(
switchMap((s) =>
combineLatest([
this._stockService.StockInStock({ articleIds: [item.itemId], stockId: s.id }),
this.getTakeAwaySupplier(),
this.getCurrentBranch(),
])
),
switchMap((s) => this._stock.StockInStock({ articleIds: [item.itemId], stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
map(([response, supplier, branch]) => {
const price = item?.price;
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
@@ -161,7 +148,7 @@ export class DomainAvailabilityService {
quantity: number;
}): Observable<AvailabilityDTO> {
return combineLatest([
this._stockService.StockStockRequest({ stockRequest: { branchIds: [branch.id], itemId } }),
this._stock.StockStockRequest({ stockRequest: { branchIds: [branch.id], itemId } }),
this.getTakeAwaySupplier(),
]).pipe(
map(([response, supplier]) => {
@@ -181,7 +168,7 @@ export class DomainAvailabilityService {
quantity: number;
}): Observable<AvailabilityDTO> {
return this.getCurrentStock().pipe(
switchMap((s) => this._stockService.StockInStockByEAN({ eans, stockId: s.id })),
switchMap((s) => this._stock.StockInStockByEAN({ eans, stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
map(([response, supplier, branch]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
@@ -193,7 +180,7 @@ export class DomainAvailabilityService {
getTakeAwayAvailabilitiesByEans({ eans }: { eans: string[] }): Observable<StockInfoDTO[]> {
const eansFiltered = Array.from(new Set(eans));
return this.getCurrentStock().pipe(
switchMap((s) => this._stockService.StockInStockByEAN({ eans: eansFiltered, stockId: s.id })),
switchMap((s) => this._stock.StockInStockByEAN({ eans: eansFiltered, stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
map((response) => response[0].result),
shareReplay()
@@ -201,16 +188,8 @@ export class DomainAvailabilityService {
}
@memorize({ ttl: 10000 })
getPickUpAvailability({
item,
branch,
quantity,
}: {
item: ItemData;
quantity: number;
branch: BranchDTO;
}): Observable<Availability<AvailabilityDTO, SwaggerAvailabilityDTO>> {
return this._availabilityService
getPickUpAvailability({ item, branch, quantity }: { item: ItemData; quantity: number; branch: BranchDTO }): Observable<AvailabilityDTO> {
return this.swaggerAvailabilityService
.AvailabilityStoreAvailability([
{
qty: quantity,
@@ -228,7 +207,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this._availabilityService
return this.swaggerAvailabilityService
.AvailabilityShippingAvailability([
{
ean: item?.ean,
@@ -246,7 +225,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDigDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this._availabilityService
return this.swaggerAvailabilityService
.AvailabilityShippingAvailability([
{
qty: quantity,
@@ -291,11 +270,7 @@ export class DomainAvailabilityService {
timeout(5000),
mergeMap((branch) =>
this.getPickUpAvailability({ item, quantity, branch }).pipe(
mergeMap((availability) =>
logistician$.pipe(
map((logistician) => ({ ...(availability?.length > 0 ? availability[0] : []), logistician: { id: logistician.id } }))
)
),
mergeMap((availability) => logistician$.pipe(map((logistician) => ({ ...availability, logistician: { id: logistician.id } })))),
shareReplay()
)
)
@@ -304,7 +279,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDownloadAvailability({ item }: { item: ItemData }): Observable<AvailabilityDTO> {
return this._availabilityService
return this.swaggerAvailabilityService
.AvailabilityShippingAvailability([
{
ean: item?.ean,
@@ -339,11 +314,11 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getTakeAwayAvailabilities(items: { id: number; price: PriceDTO }[], branchId: number) {
return this._stockService.StockGetStocksByBranch({ branchId }).pipe(
return this._stock.StockGetStocksByBranch({ branchId }).pipe(
map((req) => req.result?.find((_) => true)?.id),
switchMap((stockId) =>
stockId
? this._stockService.StockInStock({ articleIds: items.map((i) => i.id), stockId })
? this._stock.StockInStock({ articleIds: items.map((i) => i.id), stockId })
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO)
),
timeout(20000),
@@ -364,7 +339,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getPickUpAvailabilities(payload: AvailabilityRequestDTO[], preferred?: boolean) {
return this._availabilityService.AvailabilityStoreAvailability(payload).pipe(
return this.swaggerAvailabilityService.AvailabilityStoreAvailability(payload).pipe(
timeout(20000),
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result))
);
@@ -372,7 +347,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
return this.swaggerAvailabilityService.AvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
);
@@ -380,7 +355,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
return this.swaggerAvailabilityService.AvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
);
@@ -406,7 +381,7 @@ export class DomainAvailabilityService {
): PriceDTO {
switch (purchasingOption) {
case 'take-away':
return availability?.price || catalogAvailability?.price;
return availability?.price || availability?.retailPrice;
case 'delivery':
case 'dig-delivery':
if (catalogAvailability?.price?.value?.value < availability?.price?.value?.value) {
@@ -467,11 +442,9 @@ export class DomainAvailabilityService {
inStock: inStock,
supplierSSC: quantity <= inStock ? '999' : '',
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
price: price ?? stockInfo?.retailPrice,
price,
supplier: { id: supplier?.id },
// TODO: Change after API Update
// LH: 2021-03-09 preis Property hat nun ein Fallback auf retailPrice
// retailPrice: (stockInfo as any)?.retailPrice,
retailPrice: (stockInfo as any)?.retailPrice, // TODO: Change after API Update
};
return availability;
}
@@ -501,29 +474,26 @@ export class DomainAvailabilityService {
return availability;
}
private _mapToPickUpAvailability(availabilities: SwaggerAvailabilityDTO[]): Availability<AvailabilityDTO, SwaggerAvailabilityDTO>[] {
private _mapToPickUpAvailability(availabilities: SwaggerAvailabilityDTO[]) {
if (isArray(availabilities)) {
const preferred = availabilities.filter((f) => f.preferred === 1);
const totalAvailable = availabilities.reduce((sum, av) => sum + (av?.qty || 0), 0);
return preferred.map((p) => {
return [
{
availabilityType: p?.status,
ssc: p?.ssc,
sscText: p?.sscText,
supplier: { id: p?.supplierId },
isPrebooked: p?.isPrebooked,
estimatedShippingDate: p?.requestStatusCode === '32' ? p?.altAt : p?.at,
price: p?.price,
inStock: totalAvailable,
supplierProductNumber: p?.supplierProductNumber,
supplierInfo: p?.requestStatusCode,
lastRequest: p?.requested,
itemId: p.itemId,
},
p,
];
return {
availabilityType: p?.status,
ssc: p?.ssc,
sscText: p?.sscText,
supplier: { id: p?.supplierId },
isPrebooked: p?.isPrebooked,
estimatedShippingDate: p?.requestStatusCode === '32' ? p?.altAt : p?.at,
price: p?.price,
inStock: totalAvailable,
supplierProductNumber: p?.supplierProductNumber,
supplierInfo: p?.requestStatusCode,
lastRequest: p?.requested,
itemId: p.itemId,
};
});
}
}

View File

@@ -1 +0,0 @@
export type Availability<T, S> = [T, S];

View File

@@ -1,3 +1,3 @@
export * from './availability-by-branch-dto';
export * from './availability';
export * from './item-data';
// start:ng42.barrel
export * from './item-data.model';
// end:ng42.barrel

View File

@@ -21,13 +21,8 @@ import {
UpdateShoppingCartItemDTO,
InputDTO,
ItemPayload,
StoreCheckoutShoppingCartService,
StoreCheckoutPaymentService,
StoreCheckoutBuyerService,
StoreCheckoutPayerService,
StoreCheckoutBranchService,
} from '@swagger/checkout';
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
import { DisplayOrderDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
import { isNullOrUndefined, memorize } from '@utils/common';
import { combineLatest, Observable, of, concat, isObservable, throwError } from 'rxjs';
import { bufferCount, catchError, filter, first, map, mergeMap, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
@@ -37,7 +32,6 @@ import * as DomainCheckoutActions from './store/domain-checkout.actions';
import { DomainAvailabilityService } from '@domain/availability';
import { HttpErrorResponse } from '@angular/common/http';
import { ApplicationService } from '@core/application';
import { CustomerDTO, EntityDTOContainerOfAttributeDTO } from '@swagger/crm';
@Injectable()
export class DomainCheckoutService {
@@ -46,12 +40,7 @@ export class DomainCheckoutService {
private applicationService: ApplicationService,
private storeCheckoutService: StoreCheckoutService,
private orderCheckoutService: OrderCheckoutService,
private availabilityService: DomainAvailabilityService,
private _shoppingCartService: StoreCheckoutShoppingCartService,
private _paymentService: StoreCheckoutPaymentService,
private _buyerService: StoreCheckoutBuyerService,
private _payerService: StoreCheckoutPayerService,
private _branchService: StoreCheckoutBranchService
private availabilityService: DomainAvailabilityService
) {}
//#region shoppingcart
@@ -64,8 +53,8 @@ export class DomainCheckoutService {
return false;
} else if (cart && _latest) {
_latest = false;
this._shoppingCartService
.StoreCheckoutShoppingCartGetShoppingCart({
this.storeCheckoutService
.StoreCheckoutGetShoppingCart({
shoppingCartId: cart.id,
})
.pipe(
@@ -88,7 +77,7 @@ export class DomainCheckoutService {
}
createShoppingCart({ processId }: { processId: number }): Observable<ShoppingCartDTO> {
return this._shoppingCartService.StoreCheckoutShoppingCartCreateShoppingCart().pipe(
return this.storeCheckoutService.StoreCheckoutCreateShoppingCart().pipe(
map((response) => response.result),
tap((shoppingCart) =>
this.store.dispatch(
@@ -105,8 +94,8 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((cart) =>
this._shoppingCartService
.StoreCheckoutShoppingCartAddItemToShoppingCart({
this.storeCheckoutService
.StoreCheckoutAddItemToShoppingCart({
items,
shoppingCartId: cart.id,
})
@@ -136,8 +125,8 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((shoppingCart) =>
this._shoppingCartService
.StoreCheckoutShoppingCartSetLogisticianOnDestinationsByBuyer({
this.storeCheckoutService
.StoreCheckoutSetLogisticianOnDestinationsByBuyer({
shoppingCartId: shoppingCart?.id,
payload: { customerFeatures },
})
@@ -150,7 +139,7 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((cart) =>
this._shoppingCartService.StoreCheckoutShoppingCartCanAddDestination({
this.storeCheckoutService.StoreCheckoutCanAddDestination({
shoppingCartId: cart.id,
payload: destinationDTO,
})
@@ -177,8 +166,8 @@ export class DomainCheckoutService {
first(),
withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
mergeMap(([shoppingCart, customerFeatures]) =>
this._shoppingCartService
.StoreCheckoutShoppingCartCanAddItem({
this.storeCheckoutService
.StoreCheckoutCanAddItem({
shoppingCartId: shoppingCart?.id,
payload: {
customerFeatures,
@@ -210,8 +199,8 @@ export class DomainCheckoutService {
orderType,
};
});
return this._shoppingCartService
.StoreCheckoutShoppingCartCanAddItems({
return this.storeCheckoutService
.StoreCheckoutCanAddItems({
shoppingCartId: shoppingCart.id,
payload,
})
@@ -233,7 +222,7 @@ export class DomainCheckoutService {
shoppingCartItemId: number;
availability: AvailabilityDTO;
}) {
return this._shoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItemAvailability({
return this.storeCheckoutService.StoreCheckoutUpdateShoppingCartItemAvailability({
shoppingCartId,
shoppingCartItemId,
availability,
@@ -252,8 +241,8 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((shoppingCart) =>
this._shoppingCartService
.StoreCheckoutShoppingCartUpdateShoppingCartItem({
this.storeCheckoutService
.StoreCheckoutUpdateShoppingCartItem({
shoppingCartId: shoppingCart.id,
shoppingCartItemId,
values: update,
@@ -329,8 +318,8 @@ export class DomainCheckoutService {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this._paymentService
.StoreCheckoutPaymentGetCheckoutPayment({
this.storeCheckoutService
.StoreCheckoutGetCheckoutPayment({
checkoutId: checkout.id,
})
.pipe(map((response) => response.result))
@@ -346,8 +335,8 @@ export class DomainCheckoutService {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this._paymentService
.StoreCheckoutPaymentSetPaymentType({
this.storeCheckoutService
.StoreCheckoutSetPaymentType({
checkoutId: checkout?.id,
paymentType,
})
@@ -363,8 +352,8 @@ export class DomainCheckoutService {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this._buyerService
.StoreCheckoutBuyerSetBuyerPOST({
this.storeCheckoutService
.StoreCheckoutSetBuyer({
checkoutId: checkout?.id,
buyerDTO: buyer,
})
@@ -380,8 +369,8 @@ export class DomainCheckoutService {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this._payerService
.StoreCheckoutPayerSetPayerPOST({
this.storeCheckoutService
.StoreCheckoutSetPayer({
checkoutId: checkout?.id,
payerDTO: payer,
})
@@ -400,7 +389,7 @@ export class DomainCheckoutService {
mergeMap((cart) =>
concat(
...cart.items.map((item) =>
this._shoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItem({
this.storeCheckoutService.StoreCheckoutUpdateShoppingCartItem({
shoppingCartId: cart.id,
shoppingCartItemId: item.id,
values: { specialComment },
@@ -661,7 +650,7 @@ export class DomainCheckoutService {
first(),
mergeMap((checkout) =>
this.orderCheckoutService
.OrderCheckoutCreateOrderPOST({
.OrderCheckoutCreateOrder({
checkoutId: checkout.id,
})
.pipe(
@@ -745,8 +734,8 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((shoppingCart) =>
this._shoppingCartService
.StoreCheckoutShoppingCartCanAddBuyer({
this.storeCheckoutService
.StoreCheckoutCanAddBuyer({
shoppingCartId: shoppingCart.id,
payload: { customerFeatures },
})
@@ -782,39 +771,35 @@ export class DomainCheckoutService {
return this.canSetCustomer({ processId, customerFeatures: undefined }).pipe(
map((res) => {
let setableTypes: { [key: string]: boolean } = {
store: false,
guest: false,
webshop: false,
b2b: false,
store: true,
guest: true,
webshop: true,
b2b: true,
};
res.create?.options?.values?.forEach((option) => {
setableTypes[option.value] = option.enabled !== false;
});
if (Object.keys(res.filter).length === 0) {
return setableTypes;
}
// if (Object.keys(res.filter).length === 0) {
// return setableTypes;
// }
const customerTypes = res.filter?.customertype?.split(';') || [];
const customerAttributes = res.filter?.customerattributes?.split(';') || [];
// const customerTypes = res.filter?.customertype?.split(';') || [];
// const customerAttributes = res.filter?.customerattributes?.split(';') || [];
const typesAndAttributes = [...customerTypes, ...customerAttributes];
if (typesAndAttributes.includes('webshop') && !typesAndAttributes.includes('!guest')) {
typesAndAttributes.push('guest');
}
// const typesAndAttributes = [...customerTypes, ...customerAttributes];
// if (typesAndAttributes.includes('webshop') && !typesAndAttributes.includes('!guest')) {
// typesAndAttributes.push('guest');
// }
// for (const key in setableTypes) {
// if (Object.prototype.hasOwnProperty.call(setableTypes, key)) {
// if (typesAndAttributes.includes(key)) {
// setableTypes[key] = true;
// } else if (typesAndAttributes.includes(`!${key}`)) {
// setableTypes[key] = false;
// } else {
// setableTypes[key] = false;
// }
// }
// }
for (const key in setableTypes) {
if (Object.prototype.hasOwnProperty.call(setableTypes, key)) {
if (typesAndAttributes.includes(key)) {
setableTypes[key] = true;
} else if (typesAndAttributes.includes(`!${key}`)) {
setableTypes[key] = false;
} else {
setableTypes[key] = false;
}
}
}
return setableTypes;
})
@@ -823,8 +808,8 @@ export class DomainCheckoutService {
@memorize()
getBranches(): Observable<BranchDTO[]> {
return this._branchService
.StoreCheckoutBranchGetBranches({
return this.storeCheckoutService
.StoreCheckoutGetBranches({
take: 999,
})
.pipe(
@@ -843,6 +828,10 @@ export class DomainCheckoutService {
.pipe(map((response) => response.result));
}
setCustomerFeatures({ processId, customerFeatures }: { processId: number; customerFeatures: { [key: string]: string } }) {
this.store.dispatch(DomainCheckoutActions.setCustomerFeatures({ processId, customerFeatures }));
}
setOlaErrors({ processId, errorIds }: { processId: number; errorIds: number[] }) {
this.store.dispatch(
DomainCheckoutActions.setOlaError({
@@ -868,14 +857,6 @@ export class DomainCheckoutService {
this.store.dispatch(DomainCheckoutActions.removeProcess({ processId }));
}
setCustomer({ processId, customerDto }: { processId: number; customerDto: CustomerDTO }) {
this.store.dispatch(DomainCheckoutActions.setCustomer({ processId, customer: customerDto }));
}
getCustomer({ processId }: { processId: number }): Observable<CustomerDTO> {
return this.store.select(DomainCheckoutSelectors.selectCustomerByProcessId, { processId });
}
setPayer({ processId, payer }: { processId: number; payer: PayerDTO }) {
this.store.dispatch(DomainCheckoutActions.setPayer({ processId, payer }));
}
@@ -896,14 +877,6 @@ export class DomainCheckoutService {
return this.store.select(DomainCheckoutSelectors.selectOrders);
}
updateOrderItem(item: DisplayOrderItemDTO) {
this.store.dispatch(DomainCheckoutActions.updateOrderItem({ item }));
}
removeAllOrders() {
this.store.dispatch(DomainCheckoutActions.removeAllOrders());
}
setSpecialComment({ processId, agentComment }: { processId: number; agentComment: string }) {
this.store.dispatch(DomainCheckoutActions.setSpecialComment({ processId, agentComment }));
}

View File

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

View File

@@ -8,8 +8,7 @@ import {
BuyerDTO,
PayerDTO,
} from '@swagger/checkout';
import { CustomerDTO } from '@swagger/crm';
import { DisplayOrderDTO, DisplayOrderItemDTO } from '@swagger/oms';
import { DisplayOrderDTO } from '@swagger/oms';
const prefix = '[DOMAIN-CHECKOUT]';
@@ -39,6 +38,11 @@ export const setCheckoutDestination = createAction(
props<{ processId: number; destination: DestinationDTO }>()
);
export const setCustomerFeatures = createAction(
`${prefix} Set Customer Features`,
props<{ processId: number; customerFeatures: { [key: string]: string } }>()
);
export const setShippingAddress = createAction(
`${prefix} Set Shipping Address`,
props<{ processId: number; shippingAddress: ShippingAddressDTO }>()
@@ -48,10 +52,6 @@ export const removeProcess = createAction(`${prefix} Remove Process`, props<{ pr
export const setOrders = createAction(`${prefix} Add Orders`, props<{ orders: DisplayOrderDTO[] }>());
export const updateOrderItem = createAction(`${prefix} Update Orders`, props<{ item: DisplayOrderItemDTO }>());
export const removeAllOrders = createAction(`${prefix} Remove All Orders`);
export const setBuyer = createAction(`${prefix} Set Buyer`, props<{ processId: number; buyer: BuyerDTO }>());
export const setPayer = createAction(`${prefix} Set Payer`, props<{ processId: number; payer: PayerDTO }>());
@@ -59,5 +59,3 @@ export const setPayer = createAction(`${prefix} Set Payer`, props<{ processId: n
export const setSpecialComment = createAction(`${prefix} Set Agent Comment`, props<{ processId: number; agentComment: string }>());
export const setOlaError = createAction(`${prefix} Set Ola Error`, props<{ processId: number; olaErrorIds: number[] }>());
export const setCustomer = createAction(`${prefix} Set Customer`, props<{ processId: number; customer: CustomerDTO }>());

View File

@@ -46,6 +46,11 @@ const _domainCheckoutReducer = createReducer(
};
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setCustomerFeatures, (s, { processId, customerFeatures }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.customerFeatures = customerFeatures;
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setShippingAddress, (s, { processId, shippingAddress }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.shippingAddress = shippingAddress;
@@ -67,39 +72,11 @@ const _domainCheckoutReducer = createReducer(
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.removeProcess, (s, { processId }) => storeCheckoutAdapter.removeOne(processId, s)),
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders: [...s.orders, ...orders] })),
on(DomainCheckoutActions.updateOrderItem, (s, { item }) => {
const orders = [...s.orders];
const orderToUpdate = orders?.find((order) => order.items?.find((i) => i.id === item?.id));
const orderToUpdateIndex = orders?.indexOf(orderToUpdate);
const orderItemToUpdate = orderToUpdate?.items?.find((i) => i.id === item?.id);
const orderItemToUpdateIndex = orderToUpdate?.items?.indexOf(orderItemToUpdate);
const items = [...orderToUpdate?.items];
items[orderItemToUpdateIndex] = item;
orders[orderToUpdateIndex] = {
...orderToUpdate,
items: [...items],
};
return { ...s, orders: [...orders] };
}),
on(DomainCheckoutActions.removeAllOrders, (s) => ({
...s,
orders: [],
})),
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders })),
on(DomainCheckoutActions.setOlaError, (s, { processId, olaErrorIds }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.olaErrorIds = olaErrorIds;
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setCustomer, (s, { processId, customer }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.customer = customer;
return storeCheckoutAdapter.setOne(entity, s);
})
);
@@ -115,6 +92,7 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
processId,
checkout: undefined,
shoppingCart: undefined,
customerFeatures: undefined,
shippingAddress: undefined,
orders: [],
payer: undefined,
@@ -122,7 +100,6 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
specialComment: '',
notificationChannels: 0,
olaErrorIds: [],
customer: undefined,
};
}

View File

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

View File

@@ -1,26 +1,21 @@
import { Injectable } from '@angular/core';
import {
AddressDTO,
AddressService,
AssignedPayerDTO,
AutocompleteDTO,
CommunicationDetailsDTO,
CountryDTO,
CountryService,
CustomerDTO,
CustomerInfoDTO,
CustomerService,
InputDTO,
KeyValueDTOOfStringAndString,
ListResponseArgsOfCustomerInfoDTO,
LoyaltyCardService,
NotificationChannel,
PayerDTO,
PayerService,
ResponseArgsOfHistoryDTO,
ResponseArgsOfIEnumerableOfBonusCardInfoDTO,
ShippingAddressDTO,
ShippingAddressService,
} from '@swagger/crm';
import { isArray } from '@utils/common';
import { PagedResult, Result } from 'apps/domain/defs/src/public-api';
@@ -29,14 +24,7 @@ import { catchError, map, mergeMap, retry } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CrmCustomerService {
constructor(
private customerService: CustomerService,
private payerService: PayerService,
private addressService: AddressService,
private countryService: CountryService,
private shippingAddressService: ShippingAddressService,
private loyaltyCardService: LoyaltyCardService
) {}
constructor(private customerService: CustomerService, private payerService: PayerService) {}
complete(queryString: string, filter?: { [key: string]: string }): Observable<Result<AutocompleteDTO[]>> {
return this.customerService.CustomerCustomerAutocomplete({
@@ -78,6 +66,10 @@ export class CrmCustomerService {
return this.customerService.CustomerGetAssignedPayersByCustomerId(params);
}
getFilters(): Observable<Result<InputDTO[]>> {
return this.customerService.CustomerQueryCustomerFilter();
}
/* @internal */
getNotificationChannelForCommunicationDetails({
communicationDetails,
@@ -104,23 +96,12 @@ export class CrmCustomerService {
return this.customerService.CustomerPatchCustomer({ customerId, customer: { ...customer, notificationChannels } });
}
createB2BCustomer(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
createB2BCustomer(customer: CustomerDTO) {
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: customer?.communicationDetails,
});
const payload: CustomerDTO = { ...customer, customerType: 16, notificationChannels };
payload.shippingAddresses = payload.shippingAddresses ?? [];
payload.payers = payload.payers ?? [];
return this.customerService
.CustomerCreateCustomer({
customer: payload,
modifiers: [{ key: 'b2b', group: 'customertype' }],
})
.toPromise();
return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 16, notificationChannels });
}
createOnlineCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
@@ -167,253 +148,63 @@ export class CrmCustomerService {
];
}
const p4mUser = customer.features.find((f) => f.key === 'p4mUser')?.value;
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'webshop', group: 'customertype' }];
if (p4mUser) {
modifiers.push({ key: 'add-loyalty-card', value: p4mUser });
}
return this.customerService.CustomerCreateCustomer({
customer: { ...payload, notificationChannels },
modifiers,
});
return this.customerService.CustomerCreateOnlineCustomer({ ...payload, notificationChannels });
}
mapCustomerToPayer(customer: CustomerDTO): PayerDTO {
return {
address: customer.address,
communicationDetails: customer.communicationDetails,
firstName: customer.firstName,
lastName: customer.lastName,
organisation: customer.organisation,
title: customer.title,
payerType: 1,
gender: customer.gender,
};
}
mapCustomerToShippingAddress(customer: CustomerDTO): ShippingAddressDTO {
return {
address: customer.address,
communicationDetails: customer.communicationDetails,
firstName: customer.firstName,
gender: customer.gender,
lastName: customer.lastName,
organisation: customer.organisation,
title: customer.title,
type: 1,
};
}
async updateToOnlineCustomer(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
const payload: CustomerDTO = { shippingAddresses: [], payers: [], ...customer, customerType: 8, hasOnlineAccount: true };
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: payload?.communicationDetails,
});
const shippingAddressesToAdd = payload.shippingAddresses?.filter((sa) => !sa.id)?.map((m) => m.data) ?? [];
payload.shippingAddresses = payload.shippingAddresses?.filter((sa) => !!sa.id) ?? [];
if (payload.shippingAddresses.length === 0) {
shippingAddressesToAdd.unshift(this.mapCustomerToShippingAddress(payload));
}
const payersToAdd = payload.payers?.filter((p) => !p.assignedToCustomer)?.map((p) => p.payer?.data) ?? [];
payload.payers = payload.payers?.filter((p) => !!p.assignedToCustomer) ?? [];
if (payload.payers.length === 0) {
payersToAdd.unshift({
...this.mapCustomerToPayer(payload),
payerType: payload.customerType,
});
}
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'webshop', group: 'customertype' }];
const res = await this.customerService
.CustomerUpdateCustomer({
customerId: customer.id,
payload: {
modifiers,
customer: { ...payload, notificationChannels },
},
})
.toPromise();
for (let shippingAddress of shippingAddressesToAdd) {
await this.createShippingAddress(res.result.id, shippingAddress, true);
}
for (let payer of payersToAdd) {
await this.createPayer(res.result.id, payer, true);
}
return res;
}
async updateToP4MOnlineCustomer(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
const shippingAddressesToAdd = customer.shippingAddresses?.filter((sa) => !sa.id)?.map((m) => m.data) ?? [];
customer.shippingAddresses = customer.shippingAddresses?.filter((sa) => !!sa.id) ?? [];
if (customer.shippingAddresses.length === 0) {
shippingAddressesToAdd.unshift(this.mapCustomerToShippingAddress(customer));
}
const payersToAdd = customer.payers?.filter((p) => !p.assignedToCustomer)?.map((p) => p.payer?.data) ?? [];
customer.payers = customer.payers?.filter((p) => !!p.assignedToCustomer) ?? [];
if (customer.payers.length === 0) {
payersToAdd.unshift({
...this.mapCustomerToPayer(customer),
payerType: customer.customerType,
});
}
const p4mUser = customer.features.find((f) => f.key === 'p4mUser')?.value;
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'webshop', group: 'customertype' }];
if (p4mUser) {
modifiers.push({ key: 'add-loyalty-card', value: p4mUser });
}
const res = await this.customerService
.CustomerUpdateCustomer({
customerId: customer.id,
payload: {
customer,
modifiers,
},
})
.toPromise();
for (let shippingAddress of shippingAddressesToAdd) {
await this.createShippingAddress(res.result.id, shippingAddress, true);
}
for (let payer of payersToAdd) {
await this.createPayer(res.result.id, payer, true);
}
return res;
}
async updateStoreP4MToWebshopP4M(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
const shippingAddressesToAdd = customer.shippingAddresses?.filter((sa) => !sa.id)?.map((m) => m.data) ?? [];
customer.shippingAddresses = customer.shippingAddresses?.filter((sa) => !!sa.id) ?? [];
if (customer.shippingAddresses.length === 0) {
shippingAddressesToAdd.unshift(this.mapCustomerToShippingAddress(customer));
}
const payersToAdd = customer.payers?.filter((p) => !p.assignedToCustomer)?.map((p) => p.payer?.data) ?? [];
customer.payers = customer.payers?.filter((p) => !!p.assignedToCustomer) ?? [];
if (customer.payers.length === 0) {
payersToAdd.unshift({
...this.mapCustomerToPayer(customer),
payerType: customer.customerType,
});
}
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'webshop', group: 'customertype' }];
const res = await this.customerService
.CustomerUpdateCustomer({
customerId: customer.id,
payload: {
customer,
modifiers,
},
})
.toPromise();
for (let shippingAddress of shippingAddressesToAdd) {
await this.createShippingAddress(res.result.id, shippingAddress, true);
}
for (let payer of payersToAdd) {
await this.createPayer(res.result.id, payer, true);
}
return res;
}
async createGuestCustomer(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
createGuestCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: customer?.communicationDetails,
});
const payload: CustomerDTO = { ...customer, customerType: 8, isGuestAccount: true, notificationChannels };
payload.shippingAddresses = customer.shippingAddresses ?? [];
payload.shippingAddresses.push({
data: this.mapCustomerToShippingAddress(customer),
});
payload.payers = customer.payers ?? [];
payload.payers.push({
payer: {
data: {
...this.mapCustomerToPayer(customer),
payerType: payload.customerType,
if (!(isArray(payload.shippingAddresses) && payload.shippingAddresses.length > 0)) {
payload.shippingAddresses = [
{
data: {
address: payload.address,
communicationDetails: payload.communicationDetails,
firstName: payload.firstName,
gender: payload.gender,
lastName: payload.lastName,
organisation: payload.organisation,
title: payload.title,
type: 1,
},
},
},
});
];
}
const res = await this.customerService
.CustomerCreateCustomer({
customer: payload,
modifiers: [{ key: 'webshop', group: 'customertype' }],
})
.toPromise();
if (!(isArray(payload.payers) && payload.payers.length > 0)) {
payload.payers = [
{
payer: {
data: {
address: payload.address,
communicationDetails: payload.communicationDetails,
firstName: payload.firstName,
gender: payload.gender,
lastName: payload.lastName,
organisation: payload.organisation,
title: payload.title,
payerType: payload.customerType,
},
},
},
];
}
return res;
return this.customerService.CustomerCreateOnlineCustomer(payload);
}
createStoreCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
createBranchCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: customer?.communicationDetails,
});
const p4mUser = customer.features.find((f) => f.key === 'p4mUser')?.value;
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'store', group: 'customertype' }];
if (p4mUser) {
modifiers.push({ key: 'add-loyalty-card', value: p4mUser });
}
return this.customerService.CustomerCreateCustomer({
customer: { ...customer, customerType: 8, notificationChannels },
modifiers,
});
return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 8, notificationChannels });
}
validateAddress(address: AddressDTO): Observable<Result<AddressDTO[]>> {
return this.addressService.AddressValidateAddress(address);
}
getOnlineCustomerByEmail(email: string): Observable<CustomerInfoDTO | null> {
return this.getCustomers(email, {
take: 1,
filter: {
customertype: 'webshop',
},
}).pipe(
map((r) => {
if (r.hits === 1) {
return r.result[0];
} else {
return null;
}
}),
catchError((err) => [null])
);
return this.customerService.CustomerValidateAddress(address);
}
private cachedCountriesFailed = false;
@@ -422,8 +213,8 @@ export class CrmCustomerService {
if (!this.cachedCountries || this.cachedCountriesFailed) {
this.cachedCountriesFailed = false;
this.cachedCountries = new ReplaySubject();
this.countryService
.CountryGetCountries({})
this.customerService
.CustomerGetCountries({})
.pipe(
retry(3),
catchError((err) => {
@@ -444,28 +235,22 @@ export class CrmCustomerService {
return this.customerService.CustomerEmailExists(email);
}
checkLoyaltyCard({ loyaltyCardNumber, customerId }: { loyaltyCardNumber: string; customerId?: number }) {
return this.loyaltyCardService.LoyaltyCardCheckLoyaltyCard({ loyaltyCardNumber, customerId });
}
createPayer(customerId: number, payer: PayerDTO, isDefault?: boolean): Promise<[Result<PayerDTO>, Result<AssignedPayerDTO>]> {
return this.getCustomer(customerId)
.pipe(
mergeMap((customerResponse) =>
this.payerService
.PayerCreatePayer({ ...payer, payerType: customerResponse.result.customerType })
.pipe(
mergeMap((payerResponse) =>
this.customerService
.CustomerAddPayerReference({ customerId: customerId, payerId: payerResponse.result.id, isDefault: isDefault })
.pipe(
map((assigendPayerResponse) => [payerResponse, assigendPayerResponse] as [Result<PayerDTO>, Result<AssignedPayerDTO>])
)
)
createPayer(customerId: number, payer: PayerDTO, isDefault?: boolean): Observable<[Result<PayerDTO>, Result<AssignedPayerDTO>]> {
return this.getCustomer(customerId).pipe(
mergeMap((customerResponse) =>
this.payerService
.PayerCreatePayer({ ...payer, payerType: customerResponse.result.customerType })
.pipe(
mergeMap((payerResponse) =>
this.customerService
.CustomerAddPayerReference({ customerId: customerId, payerId: payerResponse.result.id, isDefault: isDefault })
.pipe(
map((assigendPayerResponse) => [payerResponse, assigendPayerResponse] as [Result<PayerDTO>, Result<AssignedPayerDTO>])
)
)
)
)
)
.toPromise();
);
}
updatePayer(customerId: number, payerId: number, payer: PayerDTO, isDefault?: boolean): Observable<Result<PayerDTO>> {
@@ -478,7 +263,11 @@ export class CrmCustomerService {
return this.customerService.CustomerModifyPayerReference({ payerId, customerId, isDefault });
}
createShippingAddress(customerId: number, shippingAddress: ShippingAddressDTO, isDefault?: boolean): Promise<Result<ShippingAddressDTO>> {
createShippingAddress(
customerId: number,
shippingAddress: ShippingAddressDTO,
isDefault?: boolean
): Observable<Result<ShippingAddressDTO>> {
const data: ShippingAddressDTO = { ...shippingAddress };
if (isDefault) {
data.isDefault = new Date().toJSON();
@@ -486,7 +275,7 @@ export class CrmCustomerService {
delete data.isDefault;
}
return this.shippingAddressService.ShippingAddressCreateShippingAddress({ customerId, shippingAddress: data }).toPromise();
return this.customerService.CustomerCreateShippingAddress({ customerId, shippingAddress: data });
}
updateShippingAddress(
@@ -494,7 +283,7 @@ export class CrmCustomerService {
shippingAddressId: number,
shippingAddress: ShippingAddressDTO,
isDefault?: boolean
): Promise<Result<ShippingAddressDTO>> {
): Observable<Result<ShippingAddressDTO>> {
const data: ShippingAddressDTO = { ...shippingAddress };
if (isDefault) {
@@ -503,17 +292,15 @@ export class CrmCustomerService {
delete data.isDefault;
}
return this.shippingAddressService
.ShippingAddressUpdateShippingAddress({ shippingAddressId, shippingAddress: data, customerId })
.toPromise();
return this.customerService.CustomerUpdateShippingAddress({ shippingAddressId, shippingAddress: data, customerId });
}
getShippingAddress(shippingAddressId: number): Observable<Result<ShippingAddressDTO>> {
return this.shippingAddressService.ShippingAddressGetShippingaddress(shippingAddressId);
return this.customerService.CustomerGetShippingaddress(shippingAddressId);
}
getShippingAddresses(params: ShippingAddressService.ShippingAddressGetShippingAddressesParams): Observable<Result<ShippingAddressDTO[]>> {
return this.shippingAddressService.ShippingAddressGetShippingAddresses(params);
getShippingAddresses(params: CustomerService.CustomerGetShippingAddressesParams): Observable<Result<ShippingAddressDTO[]>> {
return this.customerService.CustomerGetShippingAddresses(params);
}
getPayer(payerId: number): Observable<Result<PayerDTO>> {

View File

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

View File

@@ -1,6 +1,4 @@
// start:ng42.barrel
export * from './info-feed-item';
export * from './info-feed';
export * from './kpi-feed-item';
export * from './kpi-feed';
export * from './products-feed';

View File

@@ -1,4 +0,0 @@
export interface InfoFeedItem {
heading: string;
text: string;
}

View File

@@ -1,7 +0,0 @@
import { FeedDTO } from '@swagger/isa';
import { InfoFeedItem } from './info-feed-item';
export interface InfoFeed extends FeedDTO {
type: 'info';
items: InfoFeedItem[];
}

View File

@@ -36,5 +36,4 @@ export * from './shipping-note.action-handler';
export * from './supplier-temporarily-out-of-stock.action-handler copy';
export * from './collect-on-deliverynote.action-handler';
export * from './create-returnitem.action-handler';
export * from './print-pricediffqrcodelabel.action-handler';
// end:ng42.barrel

View File

@@ -1,80 +0,0 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
import { OrderItemsContext } from './order-items.context';
import { DomainPrinterService } from '@domain/printer';
import { ConfirmModalData, UiConfirmModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
import { PriceQRCodeDTO } from '@swagger/print';
import { PrintModalComponent } from '@modal/printer';
@Injectable()
export class PrintPriceDiffQrCodeLabelActionHandler extends ActionHandler<OrderItemsContext> {
constructor(private uiModal: UiModalService, private domainPrinterService: DomainPrinterService) {
super('PRINT_PRICEDIFFQRCODELABEL');
}
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
await this.print(data);
const result = await this.uiModal
.open({
content: UiConfirmModalComponent,
title: 'Bestellpreis abweichend zum Kassenpreis',
data: {
message:
'Der gedruckte QR Code enthält den richtigen\nBestellpreis. Bitte überkleben Sie mit dem Ausdruck\ndas Etikett auf dem Artikel.',
confirmLabel: 'In Ordnung',
rejectLabel: 'QR Code erneut drucken',
} as ConfirmModalData,
})
.afterClosed$.toPromise();
if (result?.data === false) {
await this.print(data);
}
return data;
}
private async print(data: OrderItemsContext) {
await this.uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: true,
printerType: 'Label',
print: async (printer) => {
return await this.printQrCode(data, printer);
},
},
})
.afterClosed$.toPromise();
}
private async printQrCode(data: OrderItemsContext, printer: string) {
try {
const payload = data.items.map((item) => {
return {
compartmentCode: data.compartmentCode,
compartmentInfo: data.compartmentInfo,
ean: item.product?.ean,
price: item.retailPrice?.value,
title: item.product?.name,
copies: data.itemQuantity?.get(item.orderItemSubsetId) || item?.quantity,
} as PriceQRCodeDTO;
});
const response = await this.domainPrinterService.printQrCode({ printer, data: payload }).toPromise();
if (!!response?.error) {
this.uiModal.open({
content: UiErrorModalComponent,
title: 'Fehler beim Drucken des QR Code',
data: { message: response?.message },
});
}
return response;
} catch (err) {
this.uiModal.open({ content: UiErrorModalComponent, title: 'Fehler beim Drucken des QR Code', data: err });
}
}
}

View File

@@ -5,16 +5,10 @@ import { UiModalService } from '@ui/modal';
import { ReorderModalComponent, ReorderResult } from '@modal/reorder';
import { DomainCheckoutService } from '@domain/checkout';
import { AvailabilityDTO2, OrderItemListItemDTO } from '@swagger/oms';
import { ToastService } from '@core/toast';
@Injectable()
export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
constructor(
private _command: CommandService,
private _domainCheckoutService: DomainCheckoutService,
private _uiModal: UiModalService,
private _toastService: ToastService
) {
constructor(private _command: CommandService, private _domainCheckoutService: DomainCheckoutService, private _uiModal: UiModalService) {
super('REORDER');
}
@@ -26,7 +20,7 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
content: ReorderModalComponent,
title: 'Artikel nachbestellen',
data: {
item: { ...orderItem, quantity: data.itemQuantity?.get(orderItem.orderItemSubsetId) ?? orderItem.quantity },
item: orderItem,
showReasons: true,
},
})
@@ -36,8 +30,6 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
const reorderResult = await this.reorder(result.data.item, result.data.availability, result.data.comment);
const resItem = reorderResult.item1;
this.createToast(result?.data?.comment);
updatedItems.push({
...result.data.item,
orderItemSubsetId: resItem.id,
@@ -65,18 +57,6 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
return { ...data, items: updatedItems };
}
createToast(comment?: string) {
switch (comment) {
case 'Artikel unverkäuflich, (und physisch in der Filiale vorhanden)':
case 'Falscher Titel geliefert (richtiges Etikett)':
break;
default:
this._toastService.create({
title: 'Artikel wurde nachbestellt',
});
}
}
async reorder(orderItem: OrderItemListItemDTO, availability: AvailabilityDTO2, comment: string) {
return await this._domainCheckoutService
.reorder(orderItem.orderId, orderItem.orderItemId, orderItem.orderItemSubsetId, {

View File

@@ -25,42 +25,24 @@ describe('DomainGoodsInServie', () => {
expect(spectator.service).toBeDefined();
});
describe('searchWareneingang', () => {
describe('search', () => {
it('should call AbholfachService.AbholfachWareneingang', () => {
const queryTokenDto: QueryTokenDTO = {};
abholfachServiceMock.AbholfachWareneingang.and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.searchWareneingang(queryTokenDto)).toBeObservable(cold('-a|', { a: {} }));
expect(spectator.service.search(queryTokenDto)).toBeObservable(cold('-a|', { a: {} }));
expect(abholfachServiceMock.AbholfachWareneingang).toHaveBeenCalledWith(queryTokenDto);
});
});
describe('searchWarenausgabe', () => {
it('should call AbholfachService.AbholfachWarenausgabe', () => {
const queryTokenDto: QueryTokenDTO = {};
abholfachServiceMock.AbholfachWarenausgabe.and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.searchWarenausgabe(queryTokenDto)).toBeObservable(cold('-a|', { a: {} }));
expect(abholfachServiceMock.AbholfachWarenausgabe).toHaveBeenCalledWith(queryTokenDto);
});
});
describe('wareneingangComplete', () => {
describe('complete', () => {
it('should call AbholfachService.AbholfachWareneingangAutocomplete', () => {
const autocompleteToken: AutocompleteTokenDTO = {};
abholfachServiceMock.AbholfachWareneingangAutocomplete.and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.wareneingangComplete(autocompleteToken)).toBeObservable(cold('-a|', { a: {} }));
expect(spectator.service.complete(autocompleteToken)).toBeObservable(cold('-a|', { a: {} }));
expect(abholfachServiceMock.AbholfachWareneingangAutocomplete).toHaveBeenCalledWith(autocompleteToken);
});
});
describe('warenausgabeComplete', () => {
it('should call AbholfachService.AbholfachWarenausgabeAutocomplete', () => {
const autocompleteToken: AutocompleteTokenDTO = {};
abholfachServiceMock.AbholfachWarenausgabeAutocomplete.and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.warenausgabeComplete(autocompleteToken)).toBeObservable(cold('-a|', { a: {} }));
expect(abholfachServiceMock.AbholfachWarenausgabeAutocomplete).toHaveBeenCalledWith(autocompleteToken);
});
});
describe('list', () => {
it('should call search with queryToken', () => {
const queryToken: QueryTokenDTO = {
@@ -76,7 +58,7 @@ describe('DomainGoodsInServie', () => {
spyOn(dateAdapter, 'today').and.returnValue(new Date(2021, 5, 7));
spyOn(spectator.service, 'search').and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.list()).toBeObservable(cold('-a|', { a: {} }));
expect(spectator.service.searchWareneingang).toHaveBeenCalledWith(queryToken);
expect(spectator.service.search).toHaveBeenCalledWith(queryToken);
});
});

View File

@@ -7,23 +7,15 @@ import { shareReplay } from 'rxjs/operators';
export class DomainGoodsService {
constructor(private abholfachService: AbholfachService, private dateAdapter: DateAdapter) {}
searchWareneingang(queryToken: QueryTokenDTO) {
search(queryToken: QueryTokenDTO) {
return this.abholfachService.AbholfachWareneingang(queryToken);
}
searchWarenausgabe(queryToken: QueryTokenDTO) {
return this.abholfachService.AbholfachWarenausgabe(queryToken);
}
wareneingangComplete(autocompleteToken: AutocompleteTokenDTO) {
complete(autocompleteToken: AutocompleteTokenDTO) {
return this.abholfachService.AbholfachWareneingangAutocomplete(autocompleteToken);
}
warenausgabeComplete(autocompleteToken: AutocompleteTokenDTO) {
return this.abholfachService.AbholfachWarenausgabeAutocomplete(autocompleteToken);
}
getWareneingangItemByOrderNumber(orderNumber: string) {
getItemByOrderNumber(orderNumber: string) {
return this.abholfachService.AbholfachWareneingang({
filter: { all_branches: 'true', archive: 'true' },
input: {
@@ -32,30 +24,21 @@ export class DomainGoodsService {
});
}
getWarenausgabeItemByOrderNumber(orderNumber: string, archive: boolean) {
return this.abholfachService.AbholfachWarenausgabe({
filter: { all_branches: 'true', archive: `${archive}` },
input: {
qs: orderNumber,
},
});
}
getWarenausgabeItemByCompartment(compartmentCode: string, archive: boolean) {
return this.abholfachService.AbholfachWarenausgabe({
filter: { all_branches: 'true', archive: `${archive}` },
getItemByCompartment(compartmentCode: string) {
return this.abholfachService.AbholfachWareneingang({
filter: { all_branches: 'true', archive: 'true' },
input: {
qs: compartmentCode,
},
});
}
getWareneingangItemByCustomerNumber(customerNumber: string) {
getItemByCustomerNumber(customerNumber: string) {
// Suche anhand der Kundennummer mit Status Bestellt, nachbestellt, eingetroffen, weitergeleitet intern
return this.abholfachService.AbholfachWareneingang({
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
filter: { all_branches: 'true', archive: 'true', orderitemprocessingstatus: '16;128;8192;1048576' },
input: {
customer_name: customerNumber,
qs: customerNumber,
},
});
}
@@ -73,7 +56,7 @@ export class DomainGoodsService {
skip: 0,
take: 20,
};
return this.searchWareneingang(queryToken);
return this.search(queryToken);
}
@memorize()

View File

@@ -1,6 +1,5 @@
import { Injectable } from '@angular/core';
import {
BranchService,
ChangeStockStatusCodeValues,
HistoryDTO,
NotificationChannel,
@@ -12,10 +11,8 @@ import {
OrderService,
ReceiptService,
StatusValues,
StockStatusCodeService,
ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO,
ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO,
VATService,
} from '@swagger/oms';
import { memorize } from '@utils/common';
import { Observable } from 'rxjs';
@@ -26,9 +23,6 @@ export class DomainOmsService {
constructor(
private orderService: OrderService,
private receiptService: ReceiptService,
private branchService: BranchService,
private vatService: VATService,
private stockStatusCodeService: StockStatusCodeService,
private _orderCheckoutService: OrderCheckoutService
) {}
@@ -43,7 +37,7 @@ export class DomainOmsService {
}
getBranches() {
return this.branchService.BranchGetBranches({});
return this.orderService.OrderGetBranches({});
}
getHistory(orderItemSubsetId: number): Observable<HistoryDTO> {
@@ -68,13 +62,13 @@ export class DomainOmsService {
@memorize()
getVATs() {
return this.vatService.VATGetVATs({}).pipe(map((response) => response.result));
return this.orderService.OrderGetVATs({}).pipe(map((response) => response.result));
}
// ttl 4 Stunden
@memorize({ ttl: 14400000 })
getStockStatusCodes({ supplierId, eagerLoading = 0 }: { supplierId: number; eagerLoading?: number }) {
return this.stockStatusCodeService.StockStatusCodeGetStockStatusCodes({ supplierId, eagerLoading }).pipe(
return this.orderService.OrderGetStockStatusCodes({ supplierId, eagerLoading }).pipe(
map((response) => response.result),
shareReplay()
);
@@ -158,10 +152,6 @@ export class DomainOmsService {
.pipe(map((response) => response.result));
}
setPreferredPickUpDate({ data }: { data: { [key: string]: string } }) {
return this.orderService.OrderSetPreferredPickUpDate({ data });
}
changeOrderItemStatus(data: OrderService.OrderChangeStatusParams) {
return this.orderService.OrderChangeStatus(data);
}

View File

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

View File

@@ -9,7 +9,7 @@ export function mapFromReturnSuggestionDTO(dto: ReturnSuggestionDTO): RemissionL
remainingQuantity: dto.remainingQuantityInStock,
remissionQuantity: dto.returnItem?.data?.predefinedReturnQuantity,
remissionReason: dto.returnReason,
title: dto.product.name,
title: [dto.product.contributors, dto.product.name].filter((f) => !!f).join(' - '),
department: dto.department,
ean: dto.product.ean,
productGroup: dto.product.productGroup,
@@ -29,7 +29,7 @@ export function mapFromReturnItemDTO(dto: ReturnItemDTO): RemissionListItem {
remissionQuantity: dto.predefinedReturnQuantity,
remainingQuantity: dto.remainingQuantityInStock,
remissionReason: dto.returnReason,
title: dto.product.name,
title: [dto.product.contributors, dto.product.name].filter((f) => !!f).join(' - '),
department: dto.department,
ean: dto.product.ean,
productGroup: dto.product.productGroup,

View File

@@ -12,7 +12,6 @@ import {
ReceiptDTO,
ReturnDTO,
ReturnQueryTokenDTO,
BatchResponseArgsOfReturnItemDTOAndReturnItemDTO,
} from '@swagger/remi';
import { memorize } from '@utils/common';
import { Observable, of, throwError } from 'rxjs';
@@ -141,21 +140,17 @@ export class DomainRemissionService {
)
);
} else if (arg.source === 'Abteilungsremission') {
if (!arg.queryToken.filter.abteilungen) {
result$ = of({ hits: 0, result: [] });
} else {
result$ = this.getCurrentStock().pipe(
switchMap((stock) =>
this.getItemsForAbteilungsremission({
queryToken: {
stockId: stock.id,
supplierId: arg.supplierId,
...arg.queryToken,
},
})
)
);
}
result$ = this.getCurrentStock().pipe(
switchMap((stock) =>
this.getItemsForAbteilungsremission({
queryToken: {
stockId: stock.id,
supplierId: arg.supplierId,
...arg.queryToken,
},
})
)
);
} else {
this._logger.error('Unbekannte Quelle', arg.source);
return throwError(new Error(`Unknown source: ${arg.source}`));
@@ -201,7 +196,7 @@ export class DomainRemissionService {
);
}
getStockInformation(items: RemissionListItem[], recalculate: boolean = false) {
getStockInformation(items: RemissionListItem[]) {
return this.getCurrentStock().pipe(
switchMap((stock) =>
this._stockService
@@ -214,7 +209,7 @@ export class DomainRemissionService {
.pipe(
map((res) => {
const o = items.map((item) => {
const stockInfo = res?.result?.find((stockInfo) => stockInfo.itemId === +item.dto.product.catalogProductNumber);
const stockInfo = res.result.find((stockInfo) => stockInfo.itemId === +item.dto.product.catalogProductNumber);
if (!stockInfo) {
const defaultStockData = {
@@ -231,13 +226,14 @@ export class DomainRemissionService {
let { remainingQuantity, remissionQuantity } = item;
if (!remissionQuantity || recalculate) {
if (!remissionQuantity) {
remissionQuantity = inStock - (remainingQuantity || 0);
if (remissionQuantity < 0) {
remissionQuantity = 0;
}
}
if (!remainingQuantity || recalculate) {
if (!remainingQuantity) {
remainingQuantity = inStock - (remissionQuantity || 0);
if (remainingQuantity < 0) {
remainingQuantity = 0;
@@ -299,12 +295,6 @@ export class DomainRemissionService {
);
}
canAddReturnItem(item: ReturnItemDTO): Observable<BatchResponseArgsOfReturnItemDTOAndReturnItemDTO> {
return this._remiService.RemiCanAddReturnItem({
data: [item],
});
}
async createReturn(supplierId: number, returnGroup?: string): Promise<ReturnDTO> {
const response = await this._returnService
.ReturnCreateReturn({
@@ -402,8 +392,6 @@ export class DomainRemissionService {
quantity,
placementType,
inStock,
impedimentComment,
remainingQuantity,
}: {
returnId: number;
receiptId: number;
@@ -411,15 +399,9 @@ export class DomainRemissionService {
quantity?: number;
placementType?: RemissionPlacementType;
inStock: number;
impedimentComment: string;
remainingQuantity: number;
}) {
return this._returnService
.ReturnAddReturnSuggestion({
returnId,
receiptId,
data: { returnSuggestionId, quantity, placementType, inStock, impedimentComment, remainingQuantity },
})
.ReturnAddReturnSuggestion({ returnId, receiptId, data: { returnSuggestionId, quantity, placementType, inStock } })
.pipe(map((r) => r.result));
}
@@ -437,12 +419,6 @@ export class DomainRemissionService {
.pipe(map((r) => r.result));
}
returnSuggestion(itemId: number) {
return this._returnService
.ReturnReturnSuggestionImpediment({ itemId, data: { comment: 'Produkt nicht gefunden' } })
.pipe(map((r) => r.result));
}
/**
* Create a new receipt for the given return/remission
* @param returnId Return ID

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { DisplayInfoDTO, EISPublicService, FileDTO, ProcessingStatus, QueryTokenDTO } from '@swagger/eis';
import { EISPublicService, FileDTO, ProcessingStatus, QueryTokenDTO } from '@swagger/eis';
import { DateAdapter } from '@ui/common';
import { memorize } from '@utils/common';
import { map, shareReplay, switchMap } from 'rxjs/operators';
@@ -279,80 +279,4 @@ export class DomainTaskCalendarService {
)
);
}
moveRemovedToEnd(a: DisplayInfoDTO, b: DisplayInfoDTO) {
const statusA = this.getProcessingStatusList(a)?.includes('Removed');
const statusB = this.getProcessingStatusList(b)?.includes('Removed');
if (statusA && statusB) {
return 0;
} else if (statusA && !statusB) {
return 1;
} else if (!statusA && statusB) {
return -1;
}
return 0;
}
/**
* Returns an Array of DisplayInfoDTO, sorted by ProcessingStatus
* Ignores Overdue if Task is already Completed
* Compared DisploayInfoDTO is of Type Task and Info Or PreInfo then sort by Type
* @param items DisplayInfoDTO Array to sort
* @param order Processing Status Order
* @returns DisplayInfoDTO Array ordered by Processing Status anf Type
*/
sort(items: DisplayInfoDTO[], order: ProcessingStatusList) {
let result = [...items];
const reversedOrder = [...order].reverse();
for (const status of reversedOrder) {
result = result?.sort((a, b) => {
const statusA = this.getProcessingStatusList(a);
const statusB = this.getProcessingStatusList(b);
// Ignore Overdue when it is already Completed
if (status === 'Overdue' && statusA.includes('Completed')) {
return 0;
}
const aHasStatus = statusA.includes(status);
const bHasStatus = statusB.includes(status);
if (aHasStatus && bHasStatus) {
// If it has the same ProcessingStatus then Sort by Type
const aType = this.getInfoType(a);
const bType = this.getInfoType(b);
if (aType !== bType) {
if (aType === 'Info' || aType === 'PreInfo') {
return -1;
} else {
return 1;
}
}
if (statusB.includes('Completed')) {
return -1;
}
return 0;
} else if (aHasStatus && !bHasStatus) {
return -1;
} else if (!aHasStatus && bHasStatus) {
return 1;
}
return 0;
});
}
return result;
}
getDateGroupKey(d: string) {
// Get Date as string key to ignore time for grouping
const date = new Date(d);
return date.toISOString().split('T')[0];
}
}

View File

@@ -1,14 +1,13 @@
import { Inject, Injectable, InjectionToken } from '@angular/core';
import { SignalrHub, SignalRHubOptions } from '@core/signalr';
import { BehaviorSubject, merge, of } from 'rxjs';
import { filter, map, shareReplay, tap, withLatestFrom } from 'rxjs/operators';
import { merge, of } from 'rxjs';
import { filter, shareReplay, tap } from 'rxjs/operators';
import { EnvelopeDTO, MessageBoardItemDTO } from './defs';
export const NOTIFICATIONS_HUB_OPTIONS = new InjectionToken<SignalRHubOptions>('hub.notifications.options');
@Injectable()
export class NotificationsHub extends SignalrHub {
updateNotification$ = new BehaviorSubject<MessageBoardItemDTO>(undefined);
constructor(@Inject(NOTIFICATIONS_HUB_OPTIONS) options: SignalRHubOptions) {
super(options);
}
@@ -17,14 +16,6 @@ export class NotificationsHub extends SignalrHub {
of(this._getNotifications()).pipe(filter((f) => !!f)),
this.listen<EnvelopeDTO<MessageBoardItemDTO[]>>('messageBoard')
).pipe(
withLatestFrom(this.updateNotification$),
map(([d, update]) => {
const data = d;
if (update && !!data && !data?.data?.find((message) => message?.category === 'ISA-Update')) {
data.data.push(update);
}
return data;
}),
tap((data) => this._storeNotifactions(data)),
shareReplay(1)
);
@@ -42,13 +33,4 @@ export class NotificationsHub extends SignalrHub {
}
return undefined;
}
updateNotification() {
this.updateNotification$.next({
category: 'ISA-Update',
type: 'update',
headline: 'Update Benachrichtigung',
text: 'Es steht eine aktuellere Version der ISA bereit. Bitte aktualisieren Sie die Anwendung.',
});
}
}

View File

@@ -7,11 +7,11 @@ import {
CanActivateCustomerWithProcessIdGuard,
CanActivateGoodsInGuard,
CanActivateGoodsOutGuard,
CanActivateGoodsOutWithProcessIdGuard,
CanActivateProductGuard,
CanActivateProductWithProcessIdGuard,
CanActivateRemissionGuard,
CanActivateTaskCalendarGuard,
InitStoreGuard,
IsAuthenticatedGuard,
} from './guards';
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
@@ -21,10 +21,7 @@ import { TokenLoginComponent, TokenLoginModule } from './token-login';
const routes: Routes = [
{
path: 'login',
children: [
{ path: ':token', component: TokenLoginComponent },
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
],
children: [{ path: ':token', component: TokenLoginComponent }],
},
{
path: '',
@@ -32,11 +29,12 @@ const routes: Routes = [
children: [
{
path: '',
canActivate: [],
canActivate: [InitStoreGuard],
children: [
{
path: 'kunde',
component: ShellComponent,
canActivate: [InitStoreGuard],
children: [
{
path: 'dashboard',
@@ -79,12 +77,6 @@ const routes: Routes = [
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
canActivate: [CanActivateGoodsOutGuard],
},
{
path: ':processId/goods/out',
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
canActivate: [CanActivateGoodsOutWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
],
resolve: { section: CustomerSectionResolver },

View File

@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { ActionReducer, MetaReducer, StoreModule } from '@ngrx/store';
import { ActionReducer, INIT, MetaReducer, StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { storeFreeze } from 'ngrx-store-freeze';
import packageInfo from 'package';
@@ -11,7 +11,7 @@ import { RootState } from './store/root.state';
export function storeInLocalStorage(reducer: ActionReducer<any>): ActionReducer<any> {
return function (state, action) {
if (action.type === 'HYDRATE') {
if (action.type === INIT) {
const initialState = RootStateService.LoadFromLocalStorage();
if (initialState?.version === packageInfo.version) {

View File

@@ -6,22 +6,17 @@ import { ApplicationService } from '@core/application';
import { of } from 'rxjs';
import { Renderer2 } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SwUpdate } from '@angular/service-worker';
import { NotificationsHub } from '@hub/notifications';
import { UserStateService } from '@swagger/isa';
describe('AppComponent', () => {
let spectator: Spectator<AppComponent>;
let config: SpyObject<Config>;
let renderer: SpyObject<Renderer2>;
let applicationServiceMock: SpyObject<ApplicationService>;
let notificationsHubMock: SpyObject<NotificationsHub>;
let swUpdateMock: SpyObject<SwUpdate>;
const createComponent = createComponentFactory({
component: AppComponent,
imports: [CommonModule, RouterTestingModule],
providers: [],
mocks: [Config, SwUpdate, UserStateService],
mocks: [Config],
});
beforeEach(() => {
@@ -30,10 +25,6 @@ describe('AppComponent', () => {
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
renderer = jasmine.createSpyObj('Renderer2', ['addClass', 'removeClass']);
notificationsHubMock = createSpyObject(NotificationsHub);
notificationsHubMock.notifications$ = of({});
swUpdateMock = createSpyObject(SwUpdate);
spectator = createComponent({
providers: [
{ provide: ApplicationService, useValue: applicationServiceMock },
@@ -41,8 +32,6 @@ describe('AppComponent', () => {
provide: Renderer2,
useValue: renderer,
},
{ provide: NotificationsHub, useValue: notificationsHubMock },
{ provide: SwUpdate, useValue: swUpdateMock },
],
});
config = spectator.inject(Config);
@@ -88,48 +77,13 @@ describe('AppComponent', () => {
});
});
// --------------------------------------------------------
// Unit Tests Implementation for Angular Version 13.x.x
// describe('updateClient()', () => {
// it('should call checkForUpdate() if SwUpdate.isEnabled is True', () => {
// spyOn(spectator.component, 'checkForUpdate');
// spyOn(spectator.component, 'initialCheckForUpdate');
// (swUpdateMock as any).isEnabled = true;
// spectator.component.updateClient();
// expect(spectator.component.initialCheckForUpdate).toHaveBeenCalled();
// expect(spectator.component.checkForUpdate).toHaveBeenCalled();
// });
// it('should not call checkForUpdate() if SwUpdate.isEnabled is False', () => {
// spyOn(spectator.component, 'checkForUpdate');
// spyOn(spectator.component, 'initialCheckForUpdate');
// (swUpdateMock as any).isEnabled = false;
// spectator.component.updateClient();
// expect(spectator.component.initialCheckForUpdate).not.toHaveBeenCalled();
// expect(spectator.component.checkForUpdate).not.toHaveBeenCalled();
// });
// });
// describe('checkForUpdate', () => {
// it('should call swUpdate.checkForUpdate() and notifications.updateNotification() every second', fakeAsync(() => {
// swUpdateMock.checkForUpdate.and.returnValue(Promise.resolve());
// spectator.component.checkForUpdates = 1000;
// spectator.component.checkForUpdate();
// spectator.detectChanges();
// tick(1100);
// expect(notificationsHubMock.updateNotification).toHaveBeenCalled();
// discardPeriodicTasks();
// }));
// });
// describe('initialCheckForUpdate', () => {
// it('should call swUpdate.checkForUpdate()', () => {
// swUpdateMock.checkForUpdate.and.returnValue(new Promise(undefined));
// spectator.component.initialCheckForUpdate();
// expect(swUpdateMock.checkForUpdate).toHaveBeenCalled();
// describe('sectionChangeHandler', () => {
// fit('should add class customer and remove class branch from body when section is customer', () => {
// spectator.component.sectionChangeHandler('customer');
// console.log(renderer);
// console.log('expect');
// expect(renderer.removeClass).toHaveBeenCalled();
// expect(renderer.addClass).toHaveBeenCalled();
// });
// });
});

View File

@@ -1,16 +1,9 @@
import { DOCUMENT } from '@angular/common';
import { Component, Inject, OnInit, Renderer2 } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { SwUpdate, UpdateAvailableEvent } from '@angular/service-worker';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { NotificationsHub } from '@hub/notifications';
import packageInfo from 'package';
import { interval, Observable, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Platform } from '@angular/cdk/platform';
import { UserStateService } from '@swagger/isa';
import { IsaLogProvider } from './providers';
@Component({
selector: 'app-root',
@@ -18,39 +11,18 @@ import { IsaLogProvider } from './providers';
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
private _checkForUpdates: number = this._config.get('checkForUpdates');
updateAvailableObs: Observable<UpdateAvailableEvent>;
get checkForUpdates(): number {
return this._checkForUpdates;
}
// For Unit Testing
set checkForUpdates(time: number) {
this._checkForUpdates = time;
}
subscriptions = new Subscription();
constructor(
private readonly _config: Config,
private readonly _title: Title,
private readonly _appService: ApplicationService,
@Inject(DOCUMENT) private readonly _document: Document,
private readonly _renderer: Renderer2,
private readonly _swUpdate: SwUpdate,
private readonly _notifications: NotificationsHub,
private readonly _platform: Platform,
private infoService: UserStateService
) {
this.updateClient();
IsaLogProvider.InfoService = infoService;
}
private readonly _renderer: Renderer2
) {}
ngOnInit() {
this.setTitle();
this.logVersion();
this.determinePlatform();
this._appService.getSection$().subscribe(this.sectionChangeHandler.bind(this));
}
@@ -62,18 +34,6 @@ export class AppComponent implements OnInit {
console.log(`%c${this._config.get('title')}\r\nVersion: ${packageInfo.version}`, 'font-weight: bold; font-size: 20px;');
}
determinePlatform() {
if (this._platform.IOS && !this._platform.SAFARI) {
this._renderer.addClass(this._document.body, 'tablet');
this._renderer.addClass(this._document.body, 'tablet-native');
} else if (this._platform.IOS && this._platform.SAFARI) {
this._renderer.addClass(this._document.body, 'tablet');
this._renderer.addClass(this._document.body, 'tablet-browser');
} else if (this._platform.isBrowser) {
this._renderer.addClass(this._document.body, 'desktop');
}
}
sectionChangeHandler(section: string) {
if (section === 'customer') {
this._renderer.removeClass(this._document.body, 'branch');
@@ -83,74 +43,4 @@ export class AppComponent implements OnInit {
this._renderer.addClass(this._document.body, 'branch');
}
}
// --------------------------------------------------------
// Implementation before Angular Version 13.x.x
async updateClient() {
if (!this._swUpdate.isEnabled) {
return;
}
await this.initialCheckForUpdate();
this.checkForUpdateInterval();
}
checkForUpdateInterval() {
this.updateAvailableObs = this._swUpdate.available.pipe(
tap((availableEvent) => {
if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
this._notifications.updateNotification();
this.subscriptions.unsubscribe();
}
})
);
this.subscriptions.add(
interval(this._checkForUpdates).subscribe(async () => {
await this._swUpdate.checkForUpdate();
})
);
}
async initialCheckForUpdate() {
this.updateAvailableObs = this._swUpdate.available.pipe(
tap((availableEvent) => {
if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
location.reload();
}
})
);
this.subscriptions.add(this.updateAvailableObs.subscribe());
await this._swUpdate.checkForUpdate();
}
// --------------------------------------------------------
// Implementation for Angular Version 13.x.x
// updateClient() {
// if (!this._swUpdate.isEnabled) {
// return;
// }
// this.initialCheckForUpdate();
// this.checkForUpdate();
// }
// checkForUpdate() {
// interval(this._checkForUpdates).subscribe(() => {
// this._swUpdate.checkForUpdate().then((value) => {
// if (value) {
// this._notifications.updateNotification();
// }
// });
// });
// }
// initialCheckForUpdate() {
// this._swUpdate.checkForUpdate().then((value) => {
// if (value) {
// location.reload();
// }
// });
// }
}

View File

@@ -1,12 +1,10 @@
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, ErrorHandler, Injector, LOCALE_ID, NgModule } from '@angular/core';
import { APP_INITIALIZER, ErrorHandler, LOCALE_ID, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PlatformModule } from '@angular/cdk/platform';
import { Config, ConfigModule, JsonConfigLoader } from '@core/config';
import { AuthModule, AuthService } from '@core/auth';
import { CoreCommandModule } from '@core/command';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@@ -30,27 +28,14 @@ import { CoreLoggerModule, LOG_PROVIDER } from '@core/logger';
import { IsaLogProvider } from './providers';
import { IsaErrorHandler } from './providers/isa.error-handler';
import { ScanAdapterModule } from '@adapter/scan';
import { RootStateService } from './store/root-state.service';
import * as Commands from './commands';
import { UiIconModule } from '@ui/icon';
import { UserStateService } from '@swagger/isa';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
export function _appInitializerFactory(config: Config, auth: AuthService, injector: Injector) {
export function _appInitializerFactory(config: Config, auth: AuthService) {
return async () => {
const statusElement = document.querySelector('#init-status');
statusElement.innerHTML = 'Konfigurationen werden geladen...';
await config.init();
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
await auth.init();
if (auth.isAuthenticated()) {
statusElement.innerHTML = 'App wird initialisiert...';
const state = injector.get(RootStateService);
await state.init();
}
};
}
@@ -74,7 +59,6 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
useConfigLoader: JsonConfigLoader,
jsonConfigLoaderUrl: '/config/config.json',
}),
CoreCommandModule.forRoot(Object.values(Commands)),
CoreLoggerModule.forRoot(),
AppStoreModule,
AuthModule.forRoot(),
@@ -87,20 +71,13 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
registrationStrategy: 'registerWhenStable:30000',
}),
ScanAdapterModule.forRoot(!environment.production),
PlatformModule,
UiIconModule.forRoot({
aliases: [
{ alias: 'd-account', name: 'account' },
{ alias: 'd-no-account', name: 'package-variant-closed' },
],
}),
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: _appInitializerFactory,
multi: true,
deps: [Config, AuthService, Injector],
deps: [Config, AuthService],
},
{
provide: NOTIFICATIONS_HUB_OPTIONS,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { ApplicationService } from '@core/application';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
@@ -25,16 +25,11 @@ export class CanActivateCartWithProcessIdGuard implements CanActivate {
id: +route.params.processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes)}`,
name: `Vorgang ${processes.length + 1}`,
});
}
this._applicationService.activateProcess(+route.params.processId);
return true;
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
}
}

View File

@@ -1,12 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
constructor(private readonly _applicationService: ApplicationService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
@@ -26,42 +25,11 @@ export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
id: +route.params.processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
name: `Vorgang ${processes.length + 1}`,
});
}
await this.removeBreadcrumbWithSameProcessId(route);
this._applicationService.activateProcess(+route.params.processId);
return true;
}
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Kundensuche zu tun haben
if (crumbs.length > 1) {
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'customer') === undefined);
for (const crumb of crumbsToRemove) {
await this._breadcrumbService.removeBreadcrumb(crumb.id);
}
}
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}
}
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -1,117 +1,27 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { ApplicationService } from '@core/application';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateCustomerGuard implements CanActivate {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _router: Router
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
const lastActivatedCartCheckoutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const lastActivatedGoodsOutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
await this.fromCartCheckoutProcess(processes, lastActivatedCartCheckoutProcessId);
return false;
} else if (!!lastActivatedGoodsOutProcessId && lastActivatedGoodsOutProcessId === activatedProcessId) {
await this.fromGoodsOutProcess(processes, lastActivatedGoodsOutProcessId);
return false;
}
if (!lastActivatedProcessId) {
await this.fromCartProcess(processes);
return false;
} else {
await this._router.navigate(['/kunde', String(lastActivatedProcessId), 'customer']);
lastActivatedProcessId = Date.now();
await this._applicationService.createProcess({
id: lastActivatedProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${processes.length + 1}`,
});
}
await this._router.navigate(['/kunde', lastActivatedProcessId, 'customer']);
return false;
}
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Kundensuche
async fromCartProcess(processes: ApplicationProcess[]) {
const newProcessId = Date.now();
await this._applicationService.createProcess({
id: newProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this._router.navigate(['/kunde', String(newProcessId), 'customer']);
}
// Bei offener Bestellbestätigung und Klick auf Footer Kundensuche
async fromCartCheckoutProcess(processes: ApplicationProcess[], processId: number) {
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
this._checkoutService.removeProcess({ processId });
// Ändere type cart-checkout zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
data: {},
});
// Navigation
await this._router.navigate(['/kunde', String(processId), 'customer']);
}
// Bei offener Warenausgabe und Klick auf Footer Kundensuche
async fromGoodsOutProcess(processes: ApplicationProcess[], processId: number) {
const buyer = await this._checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
const customerFeatures = await this._checkoutService.getCustomerFeatures({ processId }).pipe(first()).toPromise();
const name = buyer
? customerFeatures?.b2b
? buyer.organisation?.name
? buyer.organisation?.name
: buyer.lastName
: buyer.lastName
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`;
// Ändere type goods-out zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name,
});
// Navigation
await this._router.navigate(['/kunde', String(processId), 'customer']);
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}
}
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -1,52 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateGoodsOutWithProcessIdGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
.getProcessById$(+route.params.processId)
.pipe(first())
.toPromise();
if (!process) {
// const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
await this._applicationService.createProcess({
id: +route.params.processId,
type: 'goods-out',
section: 'customer',
name: `Warenausgabe`,
});
}
await this.removeBreadcrumbWithSameProcessId(route);
this._applicationService.activateProcess(+route.params.processId);
return true;
}
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Warenausgabe zu tun haben
if (crumbs.length > 1) {
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'goods-out') === undefined);
for (const crumb of crumbsToRemove) {
await this._breadcrumbService.removeBreadcrumb(crumb.id);
}
}
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
}
}

View File

@@ -1,183 +1,24 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateGoodsOutGuard implements CanActivate {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _router: Router
) {}
// !!! Ticket #3272 Code soll vorerst bestehen bleiben. Prozess Warenausgabe soll wieder Vorgang heißen (wie aktuell im Produktiv), bis zum neuen Navigationskonzept
// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
// async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
// const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
// let lastActivatedProcessId = (
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
// )?.id;
// const lastActivatedCartProcessId = (
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
// )?.id;
// const lastActivatedCartCheckoutProcessId = (
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
// )?.id;
// const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// // Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
// if (!!lastActivatedCartProcessId && lastActivatedCartProcessId === activatedProcessId) {
// await this.fromCartProcess(processes, route, lastActivatedCartProcessId);
// return false;
// } else if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
// await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
// return false;
// }
// if (!lastActivatedProcessId) {
// await this.fromGoodsOutProcess(processes, route);
// return false;
// } else {
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
// }
// return false;
// }
// // Bei offener Warenausgabe und Klick auf Footer Warenausgabe
// async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
// const newProcessId = Date.now();
// await this._applicationService.createProcess({
// id: newProcessId,
// type: 'goods-out',
// section: 'customer',
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
// });
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
// }
// // Bei offener Artikelsuche/Kundensuche und Klick auf Footer Warenausgabe
// async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
// // Ändere type cart zu goods-out
// this._applicationService.patchProcess(processId, {
// id: processId,
// type: 'goods-out',
// section: 'customer',
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
// });
// // Navigation
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
// }
// // Bei offener Bestellbestätigung, Artikelsuche/Kundensuche und Klick auf Footer Warenausgabe
// async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
// // Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
// this._checkoutService.removeProcess({ processId });
// // Ändere type cart-checkout zu goods-out
// this._applicationService.patchProcess(processId, {
// id: processId,
// type: 'goods-out',
// section: 'customer',
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
// data: {},
// });
// // Navigation
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
// }
// !!! Ticket #3272 Code soll vorerst bestehen bleiben. Prozess Warenausgabe soll wieder Vorgang heißen (wie aktuell im Produktiv), bis zum neuen Navigationskonzept
// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
const lastActivatedCartCheckoutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
return false;
const process = await this._applicationService.getProcessById$(this._config.get('process.ids.goodsOut')).pipe(first()).toPromise();
if (!process) {
await this._applicationService.createProcess({
id: this._config.get('process.ids.goodsOut'),
type: 'goods-out',
section: 'customer',
name: 'Warenausgabe',
});
}
if (!lastActivatedProcessId) {
await this.fromGoodsOutProcess(processes, route);
return false;
} else {
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
}
return false;
}
// Bei offener Warenausgabe und Klick auf Footer Warenausgabe
async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
const newProcessId = Date.now();
await this._applicationService.createProcess({
id: newProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
}
// Bei offener Bestellbestätigung und Klick auf Footer Warenausgabe
async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
this._checkoutService.removeProcess({ processId });
// Ändere type cart-checkout zu goods-out
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
data: {},
});
// Navigation
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
}
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
url.push(...route.url.map((segment) => segment.path));
if (route.firstChild) {
return this.getUrlFromSnapshot(route.firstChild, url);
}
return url.filter((segment) => !!segment);
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}
}
return Math.max(...processNumbers) + 1;
this._applicationService.activateProcess(this._config.get('process.ids.goodsOut'));
return true;
}
}

View File

@@ -1,12 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateProductWithProcessIdGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
constructor(private readonly _applicationService: ApplicationService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
@@ -26,45 +25,11 @@ export class CanActivateProductWithProcessIdGuard implements CanActivate {
id: +route.params.processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
name: `Vorgang ${processes.length + 1}`,
});
}
await this.removeBreadcrumbWithSameProcessId(route);
this._applicationService.activateProcess(+route.params.processId);
return true;
}
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Artikelsuche zu tun haben
if (crumbs.length > 1) {
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'catalog') === undefined);
for (const crumb of crumbsToRemove) {
await this._breadcrumbService.removeBreadcrumb(crumb.id);
}
}
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
// Ticket #3272 Bei Klick auf "+" bzw. neuen Prozess hinzufügen soll der neue Tab immer die höchste Nummer haben (wie aktuell im Produktiv)
// ----------------------------------------------------------------------------------------------------------------------------------------
// for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
// if (!processNumbers.find((number) => number === missingNumber)) {
// return missingNumber;
// }
// }
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -1,16 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { ApplicationService } from '@core/application';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateProductGuard implements CanActivate {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _router: Router
) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
@@ -18,90 +13,20 @@ export class CanActivateProductGuard implements CanActivate {
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
const lastActivatedCartCheckoutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const lastActivatedGoodsOutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
return false;
} else if (!!lastActivatedGoodsOutProcessId && lastActivatedGoodsOutProcessId === activatedProcessId) {
await this.fromGoodsOutProcess(processes, route, lastActivatedGoodsOutProcessId);
return false;
}
if (!lastActivatedProcessId) {
await this.fromCartProcess(processes, route);
return false;
} else {
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
lastActivatedProcessId = Date.now();
await this._applicationService.createProcess({
id: lastActivatedProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${processes.length + 1}`,
});
}
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
return false;
}
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Artikelsuche
async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
const newProcessId = Date.now();
await this._applicationService.createProcess({
id: newProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
}
// Bei offener Warenausgabe und Klick auf Footer Artikelsuche
async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
const buyer = await this._checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
const customerFeatures = await this._checkoutService.getCustomerFeatures({ processId }).pipe(first()).toPromise();
const name = buyer
? customerFeatures?.b2b
? buyer.organisation?.name
? buyer.organisation?.name
: buyer.lastName
: buyer.lastName
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`;
// Ändere type goods-out zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name,
});
// Navigation
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
}
// Bei offener Bestellbestätigung und Klick auf Footer Artikelsuche
async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
this._checkoutService.removeProcess({ processId });
// Ändere type cart-checkout zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
data: {},
});
// Navigation
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
}
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
url.push(...route.url.map((segment) => segment.path));
if (route.firstChild) {
@@ -109,18 +34,4 @@ export class CanActivateProductGuard implements CanActivate {
}
return url.filter((segment) => !!segment);
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}
}
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { first } from 'rxjs/operators';
@@ -24,13 +25,6 @@ export class CanActivateRemissionGuard implements CanActivate {
}
this._applicationService.activateProcess(this._config.get('process.ids.remission'));
if (!!process?.data?.active && !state.url.includes(`/filiale/remission/${process?.data?.active}`)) {
const queryParams = process?.data?.queryParams ?? {};
await this._router.navigate(['/filiale', 'remission', process?.data?.active, 'list'], { queryParams });
return false;
} else {
return true;
}
return true;
}
}

View File

@@ -3,10 +3,10 @@ export * from './can-activate-cart.guard';
export * from './can-activate-customer-with-process-id.guard';
export * from './can-activate-customer.guard';
export * from './can-activate-goods-in.guard';
export * from './can-activate-goods-out-with-process-id.guard';
export * from './can-activate-goods-out.guard';
export * from './can-activate-product-with-process-id.guard';
export * from './can-activate-product.guard';
export * from './can-activate-remission.guard';
export * from './can-activate-task-calendar.guard';
export * from './init-store.guard';
export * from './is-authenticated.guard';

View File

@@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { RootStateService } from '../store/root-state.service';
@Injectable({ providedIn: 'root' })
export class InitStoreGuard implements CanActivate {
constructor(private readonly _rootStateService: RootStateService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (await this._rootStateService.load()) {
window.location.reload();
}
return true;
}
}

View File

@@ -18,11 +18,11 @@ export class IsAuthenticatedGuard implements CanActivate {
const authenticated = await this._authService.isAuthenticated();
if (!authenticated) {
const token = await this.scanAndGetToken();
// const token = await this.scanAndGetToken();
if (token) {
this._authService.setKeyCardToken(token);
}
// if (token) {
// this._authService.setKeyCardToken(token);
// }
this._authService.login();
}

View File

@@ -1,9 +1,8 @@
import { HttpErrorInterceptor } from './http-error.interceptor';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
import { UiMessageModalComponent, UiModalResult, UiModalService } from '@ui/modal';
import { of, Subject, throwError } from 'rxjs';
import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
import { throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
import { AuthService } from '@core/auth';
describe('HttpErrorInterceptor', () => {
let spectator: SpectatorService<HttpErrorInterceptor>;
@@ -12,17 +11,13 @@ describe('HttpErrorInterceptor', () => {
const createService = createServiceFactory({
service: HttpErrorInterceptor,
mocks: [UiModalService, AuthService],
mocks: [UiModalService],
});
beforeEach(() => {
spectator = createService();
httpErrorInterceptor = spectator.service;
modalMock = spectator.inject(UiModalService);
modalMock.open.and.returnValue({
afterClosed$: of({} as UiModalResult<any>),
} as any);
});
it('should be created', () => {
@@ -36,7 +31,6 @@ describe('HttpErrorInterceptor', () => {
statusText: '',
url: '',
});
const handleErrorSpy = spyOn(httpErrorInterceptor, 'handleError').and.callThrough();
httpErrorInterceptor.intercept(null, { handle: () => throwError(error) }).subscribe({
error: () => {
@@ -58,8 +52,8 @@ describe('HttpErrorInterceptor', () => {
httpErrorInterceptor.handleError(error as any);
expect(modalMock.open).toHaveBeenCalledWith({
content: UiMessageModalComponent,
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
title: 'Die Netzwerkverbindung wurde unterbrochen',
data: { message: 'Bitte überprüfen Sie Ihre Netzwerkverbindung.' },
});
});
});

View File

@@ -2,14 +2,11 @@ import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { catchError, mergeMap, tap } from 'rxjs/operators';
import { AuthService } from '@core/auth';
import { IsaLogProvider } from '../providers';
import { LogLevel } from '@core/logger';
import { catchError } from 'rxjs/operators';
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
constructor(private _modal: UiModalService, private _auth: AuthService, private _isaLogProvider: IsaLogProvider) {}
constructor(private _modal: UiModalService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(catchError((error: HttpErrorResponse, caught: any) => this.handleError(error)));
@@ -17,30 +14,13 @@ export class HttpErrorInterceptor implements HttpInterceptor {
handleError(error: HttpErrorResponse): Observable<any> {
if (error.status === 0) {
return this._modal
.open({
content: UiMessageModalComponent,
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
})
.afterClosed$.pipe(mergeMap(() => throwError(error)));
} else if (error.status === 401) {
return this._modal
.open({
content: UiMessageModalComponent,
title: 'Sie sind nicht mehr angemeldet',
data: { message: 'Sie werden neu angemeldet' },
})
.afterClosed$.pipe(
tap(() => {
this._auth.login();
}),
mergeMap(() => throwError(error))
);
this._modal.open({
content: UiMessageModalComponent,
title: 'Die Netzwerkverbindung wurde unterbrochen',
data: { message: 'Bitte überprüfen Sie Ihre Netzwerkverbindung.' },
});
}
this._isaLogProvider.log(LogLevel.ERROR, 'Http Error', error);
return throwError(error);
}
}

View File

@@ -1,49 +1,12 @@
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable } from '@angular/core';
import { AuthService } from '@core/auth';
import { DialogModel, UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
import { IsaLogProvider } from './isa.log-provider';
import { LogLevel } from '@core/logger';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
@Injectable({ providedIn: 'root' })
@Injectable()
export class IsaErrorHandler implements ErrorHandler {
constructor(private _modal: UiModalService, private _authService: AuthService, private _isaLogProvider: IsaLogProvider) {}
constructor(private _modal: UiModalService) {}
async handleError(error: any): Promise<void> {
handleError(error: any): void {
console.error(error);
// Bei Klick auf Abbrechen auf der Login Seite erneut zur Login Seite weiterleiten
if (error?.type === 'token_error') {
this._authService.login();
return;
}
if (error instanceof HttpErrorResponse && error?.status === 401) {
await this._modal
.open({
content: UiDialogModalComponent,
title: 'Sitzung abgelaufen',
data: {
handleCommand: false,
content: 'Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an',
actions: [{ command: 'CLOSE', selected: true, label: 'Erneut anmelden' }],
} as DialogModel,
})
.afterClosed$.toPromise();
this._authService.logout();
return;
}
this._isaLogProvider.log(LogLevel.ERROR, 'Client Error', error);
this._modal.open({
content: UiErrorModalComponent,
title:
!navigator.onLine || (error instanceof HttpErrorResponse && error?.status === 0)
? 'Sie sind offline, keine Verbindung zum Netzwerk'
: 'Unbekannter Fehler',
data: error,
});
this._modal.open({ content: UiErrorModalComponent, title: 'Unbekannter Fehler', data: error });
}
}

View File

@@ -1,26 +1,20 @@
import { Injectable, Injector } from '@angular/core';
import { Injectable } from '@angular/core';
import { LogLevel, LogProvider } from '@core/logger';
import { UserStateService } from '@swagger/isa';
import { environment } from '../../environments/environment';
@Injectable({ providedIn: 'root' })
@Injectable()
export class IsaLogProvider implements LogProvider {
static InfoService: UserStateService | undefined;
constructor(private readonly _infoService: UserStateService) {}
constructor() {}
log(logLevel: LogLevel, message: string, error: Error, ...optionalParams: any[]): void {
log(logLevel: LogLevel, message: string, ...optionalParams: any[]): void {
if (!environment.production && (logLevel === LogLevel.WARN || logLevel === LogLevel.ERROR)) {
IsaLogProvider.InfoService?.UserStateSaveLog({
logType: logLevel,
message: message,
content: JSON.stringify({
error: error?.name,
message: error?.message,
stack: error?.stack,
data: optionalParams,
}),
})
this._infoService
.UserStateSaveLog({
logType: logLevel,
message: message,
content: JSON.stringify(optionalParams),
})
.toPromise()
.catch(() => {});
}

View File

@@ -61,7 +61,7 @@
<ui-icon icon="box_return" size="24px"></ui-icon>
Abholfach
</a>
<a [routerLink]="[remissionUrl$ | async]" [queryParams]="remissionQueryParams$ | async" routerLinkActive="active">
<a [routerLink]="['/filiale/remission']" routerLinkActive="active">
<ui-icon icon="documents_refresh" size="24px"></ui-icon>
Remission
</a>

View File

@@ -5,7 +5,6 @@ import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { Config } from '@core/config';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainDashboardService } from '@domain/isa';
@@ -57,14 +56,13 @@ describe('ShellComponent', () => {
MockComponent(ShellProcessTabComponent),
MockComponent(UiIconComponent),
],
mocks: [BreadcrumbService, DomainAvailabilityService, AuthService, DomainDashboardService, Config],
mocks: [BreadcrumbService, DomainAvailabilityService, AuthService, DomainDashboardService],
});
beforeEach(() => {
applicationServiceMock = createSpyObject(ApplicationService);
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of([]));
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
applicationServiceMock.getLastActivatedProcessWithSectionAndType$.and.returnValue(of({}));
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({}));
@@ -227,63 +225,6 @@ describe('ShellComponent', () => {
});
});
describe('remissionProcess$', () => {
it('should call _appService.getProcessById$() with Remission Id and return its value', async () => {
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
await spectator.component.remissionProcess$.pipe(first()).toPromise();
expect(applicationServiceMock.getProcessById$).toHaveBeenCalled();
});
});
describe('remissionUrl$', () => {
it('should return the correct url if process.data.active is available', async () => {
const process = {
id: 4000,
data: {
active: 9999,
},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
expect(url).toBe('/filiale/remission/9999/list');
});
it('should return the correct url if process.data.active is not available', async () => {
const process = {
id: 4000,
data: {},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
expect(url).toBe('/filiale/remission');
});
});
describe('remissionQueryParams$', () => {
it('should return the correct queryParams if process.data.active and process.data.queryParams are available', async () => {
const process = {
id: 4000,
data: {
active: 9999,
queryParams: { filter: 'test' },
},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
expect(queryParams).toEqual(process.data.queryParams);
});
it('should return the correct queryParams if process.data.active and process.data.queryParams are not available', async () => {
const process = {
id: 4000,
data: {},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
expect(queryParams).toEqual({});
});
});
describe('setSection()', () => {
it('should call _appService.setSection() with the argument section', async () => {
await spectator.component.setSection('customer');
@@ -343,7 +284,7 @@ describe('ShellComponent', () => {
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
await spectator.component.closeProcess(1);
expect(router.navigate).not.toHaveBeenCalledWith(['/kunde', 'dashboard']);
expect(router.navigate).not.toHaveBeenCalled();
});
it('should activate the next process when it was not the last process', async () => {
@@ -385,7 +326,7 @@ describe('ShellComponent', () => {
spyOn(router, 'navigate');
await spectator.component.activateProcess(1);
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 1, 'product']);
expect(router.navigate).toHaveBeenCalledWith(['/kunde']);
});
});

View File

@@ -1,6 +1,6 @@
import { Component, ChangeDetectionStrategy, ViewChildren, QueryList } from '@angular/core';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { first, map, shareReplay, switchMap } from 'rxjs/operators';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { UiModalService } from '@ui/modal';
@@ -10,7 +10,6 @@ import { combineLatest } from 'rxjs';
import { AuthService } from '@core/auth';
import { DomainAvailabilityService } from '@domain/availability';
import { ShellProcessTabComponent } from '@shell/process';
import { Config } from '@core/config';
@Component({
selector: 'app-shell',
@@ -27,11 +26,7 @@ export class ShellComponent {
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
get activatedProcessId$() {
return this._appService.getActivatedProcessId$().pipe(
tap((activatedProcessId) => {
this.processTabs?.find((process) => process?.process?.id === activatedProcessId && !process?.isActive)?.slideIntoView();
})
);
return this._appService.getActivatedProcessId$().pipe(shareReplay());
}
get section$() {
@@ -42,22 +37,6 @@ export class ShellComponent {
return this.section$.pipe(switchMap((section) => this._appService.getProcesses$(section)));
}
get remissionProcess$() {
return this._appService.getProcessById$(this._config.get('process.ids.remission'));
}
get remissionUrl$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active ? `/filiale/remission/${process.data.active}/list` : '/filiale/remission'))
);
}
get remissionQueryParams$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active && process?.data?.queryParams ? process.data.queryParams : {}))
);
}
get addProcessLabel$() {
return combineLatest([this.section$, this.processes$]).pipe(
map(([section, processes]) => (section === 'customer' && processes.length === 0 ? 'VORGANG STARTEN' : ''))
@@ -74,7 +53,6 @@ export class ShellComponent {
constructor(
private readonly _appService: ApplicationService,
private readonly _config: Config,
private readonly _notificationsHub: NotificationsHub,
private readonly _modal: UiModalService,
private readonly _router: Router,
@@ -101,11 +79,12 @@ export class ShellComponent {
}
async activateProcess(activatedProcessId: number) {
const latestCrumb = await this._breadcrumbService?.getLastActivatedBreadcrumbByKey$(activatedProcessId)?.pipe(first()).toPromise();
const latestCrumb = await this._breadcrumbService.getLastActivatedBreadcrumbByKey$(activatedProcessId).pipe(first()).toPromise();
if (latestCrumb) {
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
} else {
await this._router.navigate(['/kunde', activatedProcessId, 'product']);
await this._router.navigate(['/kunde']);
}
}

View File

@@ -17,13 +17,10 @@ export class RootStateService {
window['clearUserState'] = () => {
this.clear();
console.log('UserState wurde geleert. Bitte Seite neu laden.');
};
}
}
async init() {
await this.load();
this._store.dispatch({ type: 'HYDRATE', payload: RootStateService.LoadFromLocalStorage() });
this.initSave();
}
@@ -70,7 +67,6 @@ export class RootStateService {
.toPromise()
.catch((error) => this._logger.log(LogLevel.ERROR, error));
RootStateService.RemoveFromLocalStorage();
window.location.reload();
}
static SaveToLocalStorage(state: RootState) {

View File

@@ -15,9 +15,7 @@ export class TokenLoginComponent implements OnInit {
if (this._route.snapshot.params.token && !this._authService.isAuthenticated()) {
this._authService.setKeyCardToken(this._route.snapshot.params.token);
this._authService.login();
} else if (!this._authService.isAuthenticated()) {
this._authService.login();
} else if (this._authService.isAuthenticated()) {
} else {
this._router.navigate(['/']);
}
}

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