mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
171 Commits
fix/3051-R
...
2.0.531
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12fb774b73 | ||
|
|
8cfb160989 | ||
|
|
6325167eda | ||
|
|
8925eae4c5 | ||
|
|
9282bcd779 | ||
|
|
5aa6499598 | ||
|
|
f766781928 | ||
|
|
02834b7102 | ||
|
|
a9e3430505 | ||
|
|
4b9a23001a | ||
|
|
8de7ec9124 | ||
|
|
19a0a3c7c3 | ||
|
|
42a7d6e4b7 | ||
|
|
66818b1647 | ||
|
|
bc8ba9adc8 | ||
|
|
4ae5759361 | ||
|
|
b5cfcf8036 | ||
|
|
061982cf2c | ||
|
|
0e1422c2c4 | ||
|
|
3e534029a0 | ||
|
|
8d9ee9fe5c | ||
|
|
675aa04564 | ||
|
|
88c8885a81 | ||
|
|
151760aef9 | ||
|
|
6c89969b60 | ||
|
|
0fd5e66c33 | ||
|
|
c8aa526e4d | ||
|
|
f2c492c6ea | ||
|
|
11cf845235 | ||
|
|
ae6fbc7c64 | ||
|
|
71eda539f6 | ||
|
|
f43b948ac9 | ||
|
|
1b77020b6a | ||
|
|
1f62040560 | ||
|
|
cc5c3167b1 | ||
|
|
b9b79b949f | ||
|
|
a0d729fe6d | ||
|
|
f618dd3865 | ||
|
|
3fd3f972db | ||
|
|
2311655e5e | ||
|
|
c589836097 | ||
|
|
dbc641cfce | ||
|
|
f13bc58925 | ||
|
|
94d5892cf1 | ||
|
|
8e32b15f26 | ||
|
|
fe5f0ef2eb | ||
|
|
daa27d5f2d | ||
|
|
bb7626609e | ||
|
|
9ed58b685b | ||
|
|
4eb81ad30a | ||
|
|
a1f2cb57b3 | ||
|
|
62b8e387ca | ||
|
|
07498db711 | ||
|
|
2adc8c6f5d | ||
|
|
f15a43f303 | ||
|
|
e35aea5a7e | ||
|
|
0e1ed9d8cc | ||
|
|
f62ef06e51 | ||
|
|
30f4d4588f | ||
|
|
e102396dab | ||
|
|
f60815ef63 | ||
|
|
7b11b53774 | ||
|
|
abff7715ee | ||
|
|
83c0a20d61 | ||
|
|
a4de2391e9 | ||
|
|
732566eacd | ||
|
|
2c98128531 | ||
|
|
9a45823bdb | ||
|
|
fa43a08831 | ||
|
|
6fb8bdaff1 | ||
|
|
35e6d60ac0 | ||
|
|
ecfc241fb5 | ||
|
|
866a5100b9 | ||
|
|
3d972fd740 | ||
|
|
59c9275cc4 | ||
|
|
3ada83efff | ||
|
|
e263048a35 | ||
|
|
63980298b6 | ||
|
|
b904e94156 | ||
|
|
78f91b937f | ||
|
|
1ecd08d053 | ||
|
|
8c03accae7 | ||
|
|
456d3f2f3a | ||
|
|
fa2838ea5c | ||
|
|
e228490812 | ||
|
|
13d0ac0cbd | ||
|
|
09bed1456e | ||
|
|
c6827e499e | ||
|
|
d54cc7a2fd | ||
|
|
67dae94524 | ||
|
|
3273a21246 | ||
|
|
b40b61a46c | ||
|
|
813f611843 | ||
|
|
1abacb75be | ||
|
|
42028a2777 | ||
|
|
0a25eeadbe | ||
|
|
70a4451f90 | ||
|
|
d859395f50 | ||
|
|
97948df14e | ||
|
|
dc84efb3ef | ||
|
|
12676a4314 | ||
|
|
3aafb9f8e7 | ||
|
|
4518db2bdd | ||
|
|
2cb161b62e | ||
|
|
9d052284be | ||
|
|
98d9029dde | ||
|
|
040c67215e | ||
|
|
d577312b24 | ||
|
|
fa10bc9c30 | ||
|
|
055339956a | ||
|
|
6cffa53ea9 | ||
|
|
fe83ef56ea | ||
|
|
abd1cacdc0 | ||
|
|
85b448ab85 | ||
|
|
5749f0018c | ||
|
|
b65d2c5ff3 | ||
|
|
348b2c4aca | ||
|
|
dfc3f32086 | ||
|
|
68d331864d | ||
|
|
24a008b20d | ||
|
|
0e5c35fae4 | ||
|
|
4d4b989dcd | ||
|
|
2983d5a068 | ||
|
|
efdc365b90 | ||
|
|
c4a8e3eb96 | ||
|
|
8cb3c98b8d | ||
|
|
9c12eda210 | ||
|
|
90afcc008d | ||
|
|
7409d096fe | ||
|
|
a4c4a3c0c8 | ||
|
|
2148af7b63 | ||
|
|
d984bc04ec | ||
|
|
1b7dfcc3ac | ||
|
|
c762871cce | ||
|
|
007ea92bd5 | ||
|
|
688b758da2 | ||
|
|
39147d7afa | ||
|
|
50cc17a44b | ||
|
|
73f592df74 | ||
|
|
821042d8b6 | ||
|
|
7123f6cc15 | ||
|
|
6c2e9906b0 | ||
|
|
37648b79c3 | ||
|
|
6b00c2c81a | ||
|
|
cd5599ff1c | ||
|
|
e870eb241b | ||
|
|
fbf8c282e8 | ||
|
|
811e363dd4 | ||
|
|
d206ba1606 | ||
|
|
c529134cd2 | ||
|
|
dafb1d335e | ||
|
|
445899e731 | ||
|
|
b21fbc974c | ||
|
|
4c98a73204 | ||
|
|
e99d669086 | ||
|
|
913ffbda97 | ||
|
|
ec2bd0bd5d | ||
|
|
08fefb1c4b | ||
|
|
e7f20bc553 | ||
|
|
f5993ca5c4 | ||
|
|
9f0c81c20f | ||
|
|
42199c8cda | ||
|
|
bc525fbffc | ||
|
|
dd6821642f | ||
|
|
b2dd96f044 | ||
|
|
0ca8a1fabf | ||
|
|
d46643cf8c | ||
|
|
fc3bea28e8 | ||
|
|
b29323864e | ||
|
|
c4cad5d56f | ||
|
|
1d83c82919 |
295
angular.json
295
angular.json
@@ -3,256 +3,6 @@
|
||||
"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",
|
||||
@@ -3310,6 +3060,11 @@
|
||||
"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",
|
||||
@@ -3335,13 +3090,12 @@
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
"maximumWarning": "25kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
@@ -3844,7 +3598,38 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"defaultProject": "isa-app"
|
||||
}
|
||||
@@ -181,7 +181,8 @@ describe('ApplicationService', () => {
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith({
|
||||
type: actions.patchProcess.type,
|
||||
process: {
|
||||
processId: process.id,
|
||||
changes: {
|
||||
...process,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('applicationReducer', () => {
|
||||
type: 'cart',
|
||||
};
|
||||
|
||||
const action = actions.patchProcess({ process: { ...process, name: 'Test' } });
|
||||
const action = actions.patchProcess({ processId: process.id, changes: { ...process, name: 'Test' } });
|
||||
const state = applicationReducer(
|
||||
{
|
||||
...initialState,
|
||||
@@ -81,7 +81,7 @@ describe('applicationReducer', () => {
|
||||
type: 'cart',
|
||||
};
|
||||
|
||||
const action = actions.patchProcess({ process: { ...process, id: 2 } });
|
||||
const action = actions.patchProcess({ processId: process.id, changes: { ...process, id: 2 } });
|
||||
const state = applicationReducer(
|
||||
{
|
||||
...initialState,
|
||||
|
||||
@@ -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 };
|
||||
return { ...process, ...changes, id: processId };
|
||||
}
|
||||
return process;
|
||||
});
|
||||
|
||||
11
apps/core/cache/src/lib/cache.service.ts
vendored
11
apps/core/cache/src/lib/cache.service.ts
vendored
@@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CacheOptions } from './cache-options';
|
||||
import { Cached } from './cached';
|
||||
import { sha1 } from 'object-hash';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -60,7 +59,15 @@ export class CacheService {
|
||||
}
|
||||
|
||||
private getKey(token: Object) {
|
||||
return sha1(token);
|
||||
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);
|
||||
}
|
||||
|
||||
private serialize(data: Cached): string {
|
||||
|
||||
25
apps/core/toast/README.md
Normal file
25
apps/core/toast/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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.
|
||||
@@ -9,23 +9,32 @@ module.exports = function (config) {
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../../coverage'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true,
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true, // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, '../../../coverage/core/toast'),
|
||||
subdir: '.',
|
||||
reporters: [{ type: 'html' }, { type: 'text-summary' }],
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['ChromeHeadless'],
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
7
apps/core/toast/ng-package.json
Normal file
7
apps/core/toast/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../../dist/core/toast",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
11
apps/core/toast/package.json
Normal file
11
apps/core/toast/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@core/toast",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^12.2.0",
|
||||
"@angular/core": "^12.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './focus.directive';
|
||||
export * from './toast-animation';
|
||||
// end:ng42.barrel
|
||||
14
apps/core/toast/src/lib/animation/toast-animation.ts
Normal file
14
apps/core/toast/src/lib/animation/toast-animation.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
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';
|
||||
4
apps/core/toast/src/lib/defs/index.ts
Normal file
4
apps/core/toast/src/lib/defs/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './toast';
|
||||
export * from './toast-ref';
|
||||
// end:ng42.barrel
|
||||
17
apps/core/toast/src/lib/defs/toast-ref.ts
Normal file
17
apps/core/toast/src/lib/defs/toast-ref.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
11
apps/core/toast/src/lib/defs/toast.ts
Normal file
11
apps/core/toast/src/lib/defs/toast.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
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
|
||||
}
|
||||
8
apps/core/toast/src/lib/index.ts
Normal file
8
apps/core/toast/src/lib/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// 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
|
||||
15
apps/core/toast/src/lib/toast.component.html
Normal file
15
apps/core/toast/src/lib/toast.component.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<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>
|
||||
12
apps/core/toast/src/lib/toast.component.scss
Normal file
12
apps/core/toast/src/lib/toast.component.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
.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;
|
||||
}
|
||||
24
apps/core/toast/src/lib/toast.component.spec.ts
Normal file
24
apps/core/toast/src/lib/toast.component.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
48
apps/core/toast/src/lib/toast.component.ts
Normal file
48
apps/core/toast/src/lib/toast.component.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
apps/core/toast/src/lib/toast.module.ts
Normal file
12
apps/core/toast/src/lib/toast.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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 {}
|
||||
16
apps/core/toast/src/lib/toast.service.spec.ts
Normal file
16
apps/core/toast/src/lib/toast.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
79
apps/core/toast/src/lib/toast.service.ts
Normal file
79
apps/core/toast/src/lib/toast.service.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
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';
|
||||
}
|
||||
}
|
||||
4
apps/core/toast/src/lib/tokens.ts
Normal file
4
apps/core/toast/src/lib/tokens.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { Toast } from './defs';
|
||||
|
||||
export const TOAST_CONFIG_TOKEN = new InjectionToken<Toast>('TOAST_DATA');
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Public API Surface of sso
|
||||
* Public API Surface of toast
|
||||
*/
|
||||
|
||||
export * from './lib';
|
||||
@@ -1,15 +1,24 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'core-js/es7/reflect';
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: any;
|
||||
declare const require: {
|
||||
context(
|
||||
path: string,
|
||||
deep?: boolean,
|
||||
filter?: RegExp
|
||||
): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { teardown: { destroyAfterEach: true } });
|
||||
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
20
apps/core/toast/tsconfig.lib.json
Normal file
20
apps/core/toast/tsconfig.lib.json
Normal file
@@ -0,0 +1,20 @@
|
||||
/* 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"
|
||||
]
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"declarationMap": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": false
|
||||
"compilationMode": "partial"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
apps/core/toast/tsconfig.spec.json
Normal file
17
apps/core/toast/tsconfig.spec.json
Normal file
@@ -0,0 +1,17 @@
|
||||
/* 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"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ItemDTO, SearchService } from '@swagger/cat';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, StoreCheckoutService, SupplierDTO } from '@swagger/checkout';
|
||||
import { combineLatest, Observable, of } from 'rxjs';
|
||||
import {
|
||||
@@ -37,7 +37,7 @@ export class DomainAvailabilityService {
|
||||
@memorize()
|
||||
getTakeAwaySupplier(): Observable<SupplierDTO> {
|
||||
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
|
||||
map(({ result }) => result.find((supplier) => supplier?.supplierNumber === 'F')),
|
||||
map(({ result }) => result?.find((supplier) => supplier?.supplierNumber === 'F')),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
@@ -125,8 +125,13 @@ export class DomainAvailabilityService {
|
||||
|
||||
getTakeAwayAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
|
||||
return this.getCurrentStock().pipe(
|
||||
switchMap((s) => this._stock.StockInStock({ articleIds: [item.itemId], stockId: s.id })),
|
||||
withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
|
||||
switchMap((s) =>
|
||||
combineLatest([
|
||||
this._stock.StockInStock({ articleIds: [item.itemId], stockId: s.id }),
|
||||
this.getTakeAwaySupplier(),
|
||||
this.getCurrentBranch(),
|
||||
])
|
||||
),
|
||||
map(([response, supplier, branch]) => {
|
||||
const price = item?.price;
|
||||
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
|
||||
|
||||
@@ -877,6 +877,10 @@ export class DomainCheckoutService {
|
||||
return this.store.select(DomainCheckoutSelectors.selectOrders);
|
||||
}
|
||||
|
||||
removeAllOrders() {
|
||||
this.store.dispatch(DomainCheckoutActions.removeAllOrders());
|
||||
}
|
||||
|
||||
setSpecialComment({ processId, agentComment }: { processId: number; agentComment: string }) {
|
||||
this.store.dispatch(DomainCheckoutActions.setSpecialComment({ processId, agentComment }));
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ export const removeProcess = createAction(`${prefix} Remove Process`, props<{ pr
|
||||
|
||||
export const setOrders = createAction(`${prefix} Add Orders`, props<{ orders: DisplayOrderDTO[] }>());
|
||||
|
||||
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 }>());
|
||||
|
||||
@@ -72,7 +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 })),
|
||||
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders: [...s.orders, ...orders] })),
|
||||
on(DomainCheckoutActions.removeAllOrders, (s) => ({
|
||||
...s,
|
||||
orders: [],
|
||||
})),
|
||||
on(DomainCheckoutActions.setOlaError, (s, { processId, olaErrorIds }) => {
|
||||
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
|
||||
entity.olaErrorIds = olaErrorIds;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// 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';
|
||||
|
||||
4
apps/domain/isa/src/lib/defs/info-feed-item.ts
Normal file
4
apps/domain/isa/src/lib/defs/info-feed-item.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface InfoFeedItem {
|
||||
heading: string;
|
||||
text: string;
|
||||
}
|
||||
7
apps/domain/isa/src/lib/defs/info-feed.ts
Normal file
7
apps/domain/isa/src/lib/defs/info-feed.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { FeedDTO } from '@swagger/isa';
|
||||
import { InfoFeedItem } from './info-feed-item';
|
||||
|
||||
export interface InfoFeed extends FeedDTO {
|
||||
type: 'info';
|
||||
items: InfoFeedItem[];
|
||||
}
|
||||
@@ -36,4 +36,5 @@ 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
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,16 @@ 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) {
|
||||
constructor(
|
||||
private _command: CommandService,
|
||||
private _domainCheckoutService: DomainCheckoutService,
|
||||
private _uiModal: UiModalService,
|
||||
private _toastService: ToastService
|
||||
) {
|
||||
super('REORDER');
|
||||
}
|
||||
|
||||
@@ -20,7 +26,7 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
content: ReorderModalComponent,
|
||||
title: 'Artikel nachbestellen',
|
||||
data: {
|
||||
item: orderItem,
|
||||
item: { ...orderItem, quantity: data.itemQuantity?.get(orderItem.orderItemSubsetId) ?? orderItem.quantity },
|
||||
showReasons: true,
|
||||
},
|
||||
})
|
||||
@@ -30,6 +36,8 @@ 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,
|
||||
@@ -57,6 +65,27 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
return { ...data, items: updatedItems };
|
||||
}
|
||||
|
||||
createToast(comment?: string) {
|
||||
let text: string = '';
|
||||
|
||||
switch (comment) {
|
||||
case 'Artikel unverkäuflich, (und physisch in der Filiale vorhanden)':
|
||||
text = 'Der Beschädigte Artikel wurde auf die Remi-Liste gesetzt.';
|
||||
break;
|
||||
case 'Falscher Titel geliefert (richtiges Etikett)':
|
||||
text = 'Die Falschlieferung wurde auf die Remi-Liste gesetzt.';
|
||||
break;
|
||||
default:
|
||||
text = '';
|
||||
break;
|
||||
}
|
||||
|
||||
this._toastService.create({
|
||||
title: 'Artikel wurde nachbestellt',
|
||||
text,
|
||||
});
|
||||
}
|
||||
|
||||
async reorder(orderItem: OrderItemListItemDTO, availability: AvailabilityDTO2, comment: string) {
|
||||
return await this._domainCheckoutService
|
||||
.reorder(orderItem.orderId, orderItem.orderItemId, orderItem.orderItemSubsetId, {
|
||||
|
||||
@@ -25,24 +25,42 @@ describe('DomainGoodsInServie', () => {
|
||||
expect(spectator.service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
describe('searchWareneingang', () => {
|
||||
it('should call AbholfachService.AbholfachWareneingang', () => {
|
||||
const queryTokenDto: QueryTokenDTO = {};
|
||||
abholfachServiceMock.AbholfachWareneingang.and.returnValue(hot('-a|', { a: {} }));
|
||||
expect(spectator.service.search(queryTokenDto)).toBeObservable(cold('-a|', { a: {} }));
|
||||
expect(spectator.service.searchWareneingang(queryTokenDto)).toBeObservable(cold('-a|', { a: {} }));
|
||||
expect(abholfachServiceMock.AbholfachWareneingang).toHaveBeenCalledWith(queryTokenDto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('complete', () => {
|
||||
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', () => {
|
||||
it('should call AbholfachService.AbholfachWareneingangAutocomplete', () => {
|
||||
const autocompleteToken: AutocompleteTokenDTO = {};
|
||||
abholfachServiceMock.AbholfachWareneingangAutocomplete.and.returnValue(hot('-a|', { a: {} }));
|
||||
expect(spectator.service.complete(autocompleteToken)).toBeObservable(cold('-a|', { a: {} }));
|
||||
expect(spectator.service.wareneingangComplete(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 = {
|
||||
@@ -58,7 +76,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.search).toHaveBeenCalledWith(queryToken);
|
||||
expect(spectator.service.searchWareneingang).toHaveBeenCalledWith(queryToken);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,15 +7,23 @@ import { shareReplay } from 'rxjs/operators';
|
||||
export class DomainGoodsService {
|
||||
constructor(private abholfachService: AbholfachService, private dateAdapter: DateAdapter) {}
|
||||
|
||||
search(queryToken: QueryTokenDTO) {
|
||||
searchWareneingang(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachWareneingang(queryToken);
|
||||
}
|
||||
|
||||
complete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
searchWarenausgabe(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachWarenausgabe(queryToken);
|
||||
}
|
||||
|
||||
wareneingangComplete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
return this.abholfachService.AbholfachWareneingangAutocomplete(autocompleteToken);
|
||||
}
|
||||
|
||||
getItemByOrderNumber(orderNumber: string) {
|
||||
warenausgabeComplete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
return this.abholfachService.AbholfachWarenausgabeAutocomplete(autocompleteToken);
|
||||
}
|
||||
|
||||
getWareneingangItemByOrderNumber(orderNumber: string) {
|
||||
return this.abholfachService.AbholfachWareneingang({
|
||||
filter: { all_branches: 'true', archive: 'true' },
|
||||
input: {
|
||||
@@ -24,8 +32,17 @@ export class DomainGoodsService {
|
||||
});
|
||||
}
|
||||
|
||||
getItemByCompartment(compartmentCode: string) {
|
||||
return this.abholfachService.AbholfachWareneingang({
|
||||
getWarenausgabeItemByOrderNumber(orderNumber: string) {
|
||||
return this.abholfachService.AbholfachWarenausgabe({
|
||||
filter: { all_branches: 'true', archive: 'true' },
|
||||
input: {
|
||||
qs: orderNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWarenausgabeItemByCompartment(compartmentCode: string) {
|
||||
return this.abholfachService.AbholfachWarenausgabe({
|
||||
filter: { all_branches: 'true', archive: 'true' },
|
||||
input: {
|
||||
qs: compartmentCode,
|
||||
@@ -33,12 +50,12 @@ export class DomainGoodsService {
|
||||
});
|
||||
}
|
||||
|
||||
getItemByCustomerNumber(customerNumber: string) {
|
||||
getWareneingangItemByCustomerNumber(customerNumber: string) {
|
||||
// Suche anhand der Kundennummer mit Status Bestellt, nachbestellt, eingetroffen, weitergeleitet intern
|
||||
return this.abholfachService.AbholfachWareneingang({
|
||||
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
|
||||
input: {
|
||||
qs: customerNumber,
|
||||
customer_name: customerNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -56,7 +73,7 @@ export class DomainGoodsService {
|
||||
skip: 0,
|
||||
take: 20,
|
||||
};
|
||||
return this.search(queryToken);
|
||||
return this.searchWareneingang(queryToken);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
|
||||
@@ -5,8 +5,10 @@ import {
|
||||
CheckoutPrintService,
|
||||
ItemDTO,
|
||||
OMSPrintService,
|
||||
PriceQRCodeDTO,
|
||||
PrintRequestOfIEnumerableOfItemDTO,
|
||||
PrintRequestOfIEnumerableOfLong,
|
||||
PrintRequestOfIEnumerableOfPriceQRCodeDTO,
|
||||
PrintService,
|
||||
ResponseArgs,
|
||||
} from '@swagger/print';
|
||||
@@ -228,4 +230,8 @@ export class DomainPrinterService {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
printQrCode(data: PrintRequestOfIEnumerableOfPriceQRCodeDTO) {
|
||||
return this.oMSPrintService.OMSPrintPriceQRCode(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ReceiptDTO,
|
||||
ReturnDTO,
|
||||
ReturnQueryTokenDTO,
|
||||
BatchResponseArgsOfReturnItemDTOAndReturnItemDTO,
|
||||
} from '@swagger/remi';
|
||||
import { memorize } from '@utils/common';
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
@@ -140,17 +141,21 @@ export class DomainRemissionService {
|
||||
)
|
||||
);
|
||||
} else if (arg.source === 'Abteilungsremission') {
|
||||
result$ = this.getCurrentStock().pipe(
|
||||
switchMap((stock) =>
|
||||
this.getItemsForAbteilungsremission({
|
||||
queryToken: {
|
||||
stockId: stock.id,
|
||||
supplierId: arg.supplierId,
|
||||
...arg.queryToken,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
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,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._logger.error('Unbekannte Quelle', arg.source);
|
||||
return throwError(new Error(`Unknown source: ${arg.source}`));
|
||||
@@ -196,7 +201,7 @@ export class DomainRemissionService {
|
||||
);
|
||||
}
|
||||
|
||||
getStockInformation(items: RemissionListItem[]) {
|
||||
getStockInformation(items: RemissionListItem[], recalculate: boolean = false) {
|
||||
return this.getCurrentStock().pipe(
|
||||
switchMap((stock) =>
|
||||
this._stockService
|
||||
@@ -226,14 +231,13 @@ export class DomainRemissionService {
|
||||
|
||||
let { remainingQuantity, remissionQuantity } = item;
|
||||
|
||||
if (!remissionQuantity) {
|
||||
if (!remissionQuantity || recalculate) {
|
||||
remissionQuantity = inStock - (remainingQuantity || 0);
|
||||
if (remissionQuantity < 0) {
|
||||
remissionQuantity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!remainingQuantity) {
|
||||
if (!remainingQuantity || recalculate) {
|
||||
remainingQuantity = inStock - (remissionQuantity || 0);
|
||||
if (remainingQuantity < 0) {
|
||||
remainingQuantity = 0;
|
||||
@@ -295,6 +299,12 @@ 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({
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Inject, Injectable, InjectionToken } from '@angular/core';
|
||||
import { SignalrHub, SignalRHubOptions } from '@core/signalr';
|
||||
import { merge, of } from 'rxjs';
|
||||
import { filter, shareReplay, tap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, merge, of } from 'rxjs';
|
||||
import { filter, map, shareReplay, tap, withLatestFrom } 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);
|
||||
}
|
||||
@@ -16,6 +17,14 @@ 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)
|
||||
);
|
||||
@@ -33,4 +42,13 @@ 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.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
CanActivateCustomerWithProcessIdGuard,
|
||||
CanActivateGoodsInGuard,
|
||||
CanActivateGoodsOutGuard,
|
||||
CanActivateGoodsOutWithProcessIdGuard,
|
||||
CanActivateProductGuard,
|
||||
CanActivateProductWithProcessIdGuard,
|
||||
CanActivateRemissionGuard,
|
||||
CanActivateTaskCalendarGuard,
|
||||
InitStoreGuard,
|
||||
IsAuthenticatedGuard,
|
||||
} from './guards';
|
||||
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
||||
@@ -21,7 +21,10 @@ import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
children: [{ path: ':token', component: TokenLoginComponent }],
|
||||
children: [
|
||||
{ path: ':token', component: TokenLoginComponent },
|
||||
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
@@ -29,12 +32,11 @@ const routes: Routes = [
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
canActivate: [InitStoreGuard],
|
||||
canActivate: [],
|
||||
children: [
|
||||
{
|
||||
path: 'kunde',
|
||||
component: ShellComponent,
|
||||
canActivate: [InitStoreGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
@@ -77,6 +79,12 @@ 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 },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { ActionReducer, INIT, MetaReducer, StoreModule } from '@ngrx/store';
|
||||
import { ActionReducer, 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 === INIT) {
|
||||
if (action.type === 'HYDRATE') {
|
||||
const initialState = RootStateService.LoadFromLocalStorage();
|
||||
|
||||
if (initialState?.version === packageInfo.version) {
|
||||
|
||||
@@ -6,17 +6,22 @@ import { ApplicationService } from '@core/application';
|
||||
import { of } from 'rxjs';
|
||||
import { Renderer2 } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ServiceWorkerModule, SwUpdate } from '@angular/service-worker';
|
||||
import { NotificationsHub } from '@hub/notifications';
|
||||
import { discardPeriodicTasks, fakeAsync, flush, tick } from '@angular/core/testing';
|
||||
|
||||
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],
|
||||
mocks: [Config, SwUpdate],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -25,6 +30,10 @@ 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 },
|
||||
@@ -32,6 +41,8 @@ describe('AppComponent', () => {
|
||||
provide: Renderer2,
|
||||
useValue: renderer,
|
||||
},
|
||||
{ provide: NotificationsHub, useValue: notificationsHubMock },
|
||||
{ provide: SwUpdate, useValue: swUpdateMock },
|
||||
],
|
||||
});
|
||||
config = spectator.inject(Config);
|
||||
@@ -77,13 +88,48 @@ describe('AppComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 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();
|
||||
// --------------------------------------------------------
|
||||
// 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();
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -11,17 +16,37 @@ import packageInfo from 'package';
|
||||
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 _renderer: Renderer2,
|
||||
private readonly _swUpdate: SwUpdate,
|
||||
private readonly _notifications: NotificationsHub,
|
||||
private readonly _platform: Platform
|
||||
) {
|
||||
this.updateClient();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setTitle();
|
||||
this.logVersion();
|
||||
this.determinePlatform();
|
||||
|
||||
this._appService.getSection$().subscribe(this.sectionChangeHandler.bind(this));
|
||||
}
|
||||
@@ -34,6 +59,18 @@ 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');
|
||||
@@ -43,4 +80,74 @@ 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();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { APP_INITIALIZER, ErrorHandler, LOCALE_ID, NgModule } from '@angular/core';
|
||||
import { APP_INITIALIZER, ErrorHandler, Injector, 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';
|
||||
@@ -28,14 +29,24 @@ 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';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
|
||||
export function _appInitializerFactory(config: Config, auth: AuthService) {
|
||||
export function _appInitializerFactory(config: Config, auth: AuthService, injector: Injector) {
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,13 +82,14 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
registrationStrategy: 'registerWhenStable:30000',
|
||||
}),
|
||||
ScanAdapterModule.forRoot(!environment.production),
|
||||
PlatformModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: _appInitializerFactory,
|
||||
multi: true,
|
||||
deps: [Config, AuthService],
|
||||
deps: [Config, AuthService, Injector],
|
||||
},
|
||||
{
|
||||
provide: NOTIFICATIONS_HUB_OPTIONS,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -25,11 +25,16 @@ export class CanActivateCartWithProcessIdGuard implements CanActivate {
|
||||
id: +route.params.processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
name: `Vorgang ${this.processNumber(processes)}`,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService) {}
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const process = await this._applicationService
|
||||
@@ -25,11 +26,42 @@ export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
|
||||
id: +route.params.processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,117 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateCustomerGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
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;
|
||||
if (!lastActivatedProcessId) {
|
||||
lastActivatedProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: lastActivatedProcessId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
});
|
||||
|
||||
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']);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,183 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { Config } from '@core/config';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateGoodsOutGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
|
||||
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
|
||||
// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
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',
|
||||
});
|
||||
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;
|
||||
}
|
||||
this._applicationService.activateProcess(this._config.get('process.ids.goodsOut'));
|
||||
return true;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateProductWithProcessIdGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService) {}
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const process = await this._applicationService
|
||||
@@ -25,11 +26,45 @@ export class CanActivateProductWithProcessIdGuard implements CanActivate {
|
||||
id: +route.params.processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateProductGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _router: Router
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
@@ -13,20 +18,90 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
if (!lastActivatedProcessId) {
|
||||
lastActivatedProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: lastActivatedProcessId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
});
|
||||
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)]));
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -34,4 +109,18 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -27,7 +26,8 @@ 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}`)) {
|
||||
await this._router.navigate(['/filiale', 'remission', process?.data?.active, 'list']);
|
||||
const queryParams = process?.data?.queryParams ?? {};
|
||||
await this._router.navigate(['/filiale', 'remission', process?.data?.active, 'list'], { queryParams });
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,8 @@ describe('HttpErrorInterceptor', () => {
|
||||
httpErrorInterceptor.handleError(error as any);
|
||||
expect(modalMock.open).toHaveBeenCalledWith({
|
||||
content: UiMessageModalComponent,
|
||||
title: 'Die Netzwerkverbindung wurde unterbrochen',
|
||||
data: { message: 'Bitte überprüfen Sie Ihre Netzwerkverbindung.' },
|
||||
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
|
||||
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,8 +16,8 @@ export class HttpErrorInterceptor implements HttpInterceptor {
|
||||
if (error.status === 0) {
|
||||
this._modal.open({
|
||||
content: UiMessageModalComponent,
|
||||
title: 'Die Netzwerkverbindung wurde unterbrochen',
|
||||
data: { message: 'Bitte überprüfen Sie Ihre Netzwerkverbindung.' },
|
||||
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
|
||||
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,44 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ErrorHandler, Injectable } from '@angular/core';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
|
||||
@Injectable()
|
||||
export class IsaErrorHandler implements ErrorHandler {
|
||||
constructor(private _modal: UiModalService) {}
|
||||
constructor(private _modal: UiModalService, private _authService: AuthService) {}
|
||||
|
||||
handleError(error: any): void {
|
||||
async handleError(error: any): Promise<void> {
|
||||
console.error(error);
|
||||
this._modal.open({ content: UiErrorModalComponent, title: 'Unbekannter Fehler', data: 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: {
|
||||
content: 'Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an',
|
||||
actions: [{ command: 'CLOSE', selected: true, label: 'Erneut anmelden' }],
|
||||
},
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
|
||||
this._authService.logout();
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<ui-icon icon="box_return" size="24px"></ui-icon>
|
||||
Abholfach
|
||||
</a>
|
||||
<a [routerLink]="['/filiale/remission']" routerLinkActive="active">
|
||||
<a [routerLink]="[remissionUrl$ | async]" [queryParams]="remissionQueryParams$ | async" routerLinkActive="active">
|
||||
<ui-icon icon="documents_refresh" size="24px"></ui-icon>
|
||||
Remission
|
||||
</a>
|
||||
|
||||
@@ -5,6 +5,7 @@ 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';
|
||||
@@ -56,13 +57,14 @@ describe('ShellComponent', () => {
|
||||
MockComponent(ShellProcessTabComponent),
|
||||
MockComponent(UiIconComponent),
|
||||
],
|
||||
mocks: [BreadcrumbService, DomainAvailabilityService, AuthService, DomainDashboardService],
|
||||
mocks: [BreadcrumbService, DomainAvailabilityService, AuthService, DomainDashboardService, Config],
|
||||
});
|
||||
|
||||
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({}));
|
||||
@@ -225,6 +227,63 @@ 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');
|
||||
@@ -284,7 +343,7 @@ describe('ShellComponent', () => {
|
||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
||||
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
|
||||
await spectator.component.closeProcess(1);
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
expect(router.navigate).not.toHaveBeenCalledWith(['/kunde', 'dashboard']);
|
||||
});
|
||||
|
||||
it('should activate the next process when it was not the last process', async () => {
|
||||
@@ -326,7 +385,7 @@ describe('ShellComponent', () => {
|
||||
spyOn(router, 'navigate');
|
||||
|
||||
await spectator.component.activateProcess(1);
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/kunde']);
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 1, 'product']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Component, ChangeDetectionStrategy, ViewChildren, QueryList } from '@angular/core';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||
import { NotificationsHub } from '@hub/notifications';
|
||||
import { ModalNotificationsComponent } from '@modal/notifications';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { ConfirmModalData, UiConfirmModalComponent, UiModalService } from '@ui/modal';
|
||||
import { Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
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',
|
||||
@@ -26,7 +27,11 @@ export class ShellComponent {
|
||||
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
|
||||
|
||||
get activatedProcessId$() {
|
||||
return this._appService.getActivatedProcessId$().pipe(shareReplay());
|
||||
return this._appService.getActivatedProcessId$().pipe(
|
||||
tap((activatedProcessId) => {
|
||||
this.processTabs?.find((process) => process?.process?.id === activatedProcessId && !process?.isActive)?.slideIntoView();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get section$() {
|
||||
@@ -37,6 +42,22 @@ 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' : ''))
|
||||
@@ -53,6 +74,7 @@ export class ShellComponent {
|
||||
|
||||
constructor(
|
||||
private readonly _appService: ApplicationService,
|
||||
private readonly _config: Config,
|
||||
private readonly _notificationsHub: NotificationsHub,
|
||||
private readonly _modal: UiModalService,
|
||||
private readonly _router: Router,
|
||||
@@ -79,12 +101,11 @@ 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']);
|
||||
await this._router.navigate(['/kunde', activatedProcessId, 'product']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,13 @@ 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();
|
||||
}
|
||||
|
||||
@@ -67,6 +70,7 @@ export class RootStateService {
|
||||
.toPromise()
|
||||
.catch((error) => this._logger.log(LogLevel.ERROR, error));
|
||||
RootStateService.RemoveFromLocalStorage();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
static SaveToLocalStorage(state: RootState) {
|
||||
|
||||
@@ -15,7 +15,9 @@ 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 {
|
||||
} else if (!this._authService.isAuthenticated()) {
|
||||
this._authService.login();
|
||||
} else if (this._authService.isAuthenticated()) {
|
||||
this._router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,63 @@
|
||||
{
|
||||
"title": "ISA - Local",
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
"responseType": "id_token token",
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
||||
"enableAutomaticReconnect": false,
|
||||
"httpOptions": {
|
||||
"transport": 1,
|
||||
"logMessageContent": true,
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
}
|
||||
"title": "ISA - Feature",
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
"responseType": "id_token token",
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
||||
"enableAutomaticReconnect": false,
|
||||
"httpOptions": {
|
||||
"transport": 1,
|
||||
"logMessageContent": true,
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"title": "ISA - Integration",
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
@@ -7,6 +10,36 @@
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/catsearch/v5"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/ava/v4"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/checkout/v3"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/crm/v3"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/oms/v4"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem-integration.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/inv/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-integration.paragon-data.net/isa/v1/rt",
|
||||
@@ -17,5 +50,14 @@
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -58,5 +58,6 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"title": "ISA - Production",
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
@@ -7,6 +10,36 @@
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/catsearch/v5"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/ava/v4"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/checkout/v3"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/crm/v3"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/oms/v4"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/inv/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa.paragon-systems.de/isa/v1/rt",
|
||||
@@ -17,5 +50,14 @@
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"title": "ISA - Staging",
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
@@ -7,6 +10,36 @@
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/catsearch/v5"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/ava/v4"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/checkout/v3"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/crm/v3"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/oms/v4"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem-staging.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-staging.paragon-systems.de/isa/v1/rt",
|
||||
@@ -17,5 +50,14 @@
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -1,62 +1,63 @@
|
||||
{
|
||||
"title": "ISA - Test",
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
"responseType": "id_token token",
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
||||
"enableAutomaticReconnect": false,
|
||||
"httpOptions": {
|
||||
"transport": 1,
|
||||
"logMessageContent": true,
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
}
|
||||
"title": "ISA - Test",
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
"responseType": "id_token token",
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
||||
"enableAutomaticReconnect": false,
|
||||
"httpOptions": {
|
||||
"transport": 1,
|
||||
"logMessageContent": true,
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>IsaApp</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,700" rel="stylesheet" />
|
||||
<link rel="manifest" href="manifest.webmanifest" />
|
||||
@@ -13,9 +13,15 @@
|
||||
<body>
|
||||
<app-root>
|
||||
<div class="grid place-items-center h-screen">
|
||||
<img class="animate-spin" src="/assets/images/spinner.svg" alt="spinner animation" />
|
||||
<div class="grid grid-flow-row gap-4 items-center justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<img class="animate-spin" src="/assets/images/spinner.svg" alt="spinner animation" />
|
||||
</div>
|
||||
<div id="init-status" class="text-center">App wird geladen</div>
|
||||
</div>
|
||||
</div>
|
||||
</app-root>
|
||||
|
||||
<noscript>Please enable JavaScript to continue using this application.</noscript>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
@@ -62,3 +62,4 @@ import 'zone.js'; // Included with Angular CLI.
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
import 'hammerjs';
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
body.branch {
|
||||
--bg-color: #edeff0;
|
||||
|
||||
// @isa-app/scrollbar
|
||||
--scrollbar-color: #596470;
|
||||
|
||||
// @shell/header
|
||||
--shell-header-button-color: #89949e;
|
||||
--shell-header-button-color-active: #586470;
|
||||
@@ -23,4 +26,7 @@ body.branch {
|
||||
--shell-process-text-inactive: #9ca5b0;
|
||||
--shell-process-badge-background: #edeff0;
|
||||
--shell-process-badge-active: #596470;
|
||||
|
||||
// @core/toast
|
||||
--toast-background: #596470;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
body.customer {
|
||||
--bg-color: #e6eff9;
|
||||
|
||||
// @isa-app/scrollbar
|
||||
--scrollbar-color: #1f466c;
|
||||
|
||||
// @shell/header
|
||||
--shell-header-button-color: #9db2c6;
|
||||
--shell-header-button-color-active: #557596;
|
||||
@@ -26,4 +29,7 @@ body.customer {
|
||||
|
||||
// @page/dashboard
|
||||
--page-dashboard-card-title-color: #1f466c;
|
||||
|
||||
// @core/toast
|
||||
--toast-background: #1f466c;
|
||||
}
|
||||
|
||||
@@ -15,4 +15,7 @@
|
||||
--shell-process-add-label: #f70400;
|
||||
--shell-process-border-active: #f70400;
|
||||
--shell-process-background: #fff;
|
||||
|
||||
// @core/toast
|
||||
--toast-background: #1f466c;
|
||||
}
|
||||
|
||||
@@ -21,3 +21,23 @@ body {
|
||||
width: 0; // remove scrollbar space
|
||||
background: transparent; // optional: just make scrollbar invisible */
|
||||
}
|
||||
|
||||
.desktop .scroll-bar::-webkit-scrollbar,
|
||||
.desktop pdf-viewer ::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.desktop .scroll-bar::-webkit-scrollbar-thumb,
|
||||
.desktop pdf-viewer ::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
-webkit-box-shadow: inset 0 0 6px rgb(0 0 0 / 10%);
|
||||
background-color: var(--scrollbar-color);
|
||||
}
|
||||
|
||||
.desktop .scroll-bar::-webkit-scrollbar-track,
|
||||
.desktop pdf-viewer ::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 4px rgb(0 0 0 / 10%);
|
||||
border-radius: 10px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ hr {
|
||||
|
||||
.branch-info {
|
||||
@apply flex flex-row;
|
||||
max-width: 505px;
|
||||
max-width: 485px;
|
||||
|
||||
.branch-name {
|
||||
@apply font-bold whitespace-nowrap;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<div class="header">
|
||||
<div class="notification-icon">
|
||||
<span class="notification-counter">{{ notifications.length }}</span>
|
||||
<ui-icon icon="notification" size="26px"></ui-icon>
|
||||
</div>
|
||||
|
||||
<h2>ISA-Update</h2>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="notification-list scroll-bar">
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<div class="notification-headline">
|
||||
<h1>{{ notification.headline }}</h1>
|
||||
</div>
|
||||
<div class="notification-text">{{ notification.text }}</div>
|
||||
<hr />
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="cta-primary" (click)="reload()">
|
||||
Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { ModalNotificationsUpdateGroupComponent } from './notifications-update-group.component';
|
||||
|
||||
describe('ModalNotificationsUpdateGroupComponent', () => {
|
||||
let spectator: Spectator<ModalNotificationsUpdateGroupComponent>;
|
||||
|
||||
const createComponent = createComponentFactory({
|
||||
component: ModalNotificationsUpdateGroupComponent,
|
||||
imports: [CommonModule, UiIconModule],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createComponent({ props: { notifications: [] } });
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(spectator.component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('notifications input', () => {
|
||||
it('should display the right notification-counter value based on the length of the input array', () => {
|
||||
spectator.setInput({ notifications: [{}, {}, {}] });
|
||||
expect(spectator.query('.notification-counter')).toHaveText('3');
|
||||
});
|
||||
|
||||
it('should not display notification-counter if input array has length 0', () => {
|
||||
spectator.setInput({ notifications: [] });
|
||||
expect(spectator.query('.notification-counter')).toHaveText('');
|
||||
});
|
||||
|
||||
it('should render notification-headline and notification-text based on the input array', () => {
|
||||
const notifications = [{}, {}];
|
||||
spectator.setInput({ notifications });
|
||||
spectator.detectComponentChanges();
|
||||
expect(spectator.queryAll('.notification-headline')).toHaveLength(notifications.length);
|
||||
expect(spectator.queryAll('.notification-text')).toHaveLength(notifications.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
|
||||
@Component({
|
||||
selector: 'modal-notifications-update-group',
|
||||
templateUrl: 'notifications-update-group.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ModalNotificationsUpdateGroupComponent {
|
||||
@Input()
|
||||
notifications: MessageBoardItemDTO[];
|
||||
|
||||
constructor() {}
|
||||
|
||||
reload() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,10 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container [ngSwitch]="activeCard$ | async">
|
||||
<modal-notifications-update-group
|
||||
*ngSwitchCase="'ISA-Update'"
|
||||
[notifications]="activeNotifications$ | async"
|
||||
></modal-notifications-update-group>
|
||||
<modal-notifications-reservation-group
|
||||
*ngSwitchCase="'Reservierungsanfragen'"
|
||||
[notifications]="activeNotifications$ | async"
|
||||
|
||||
@@ -12,7 +12,8 @@ modal-notifications {
|
||||
|
||||
modal-notifications-remission-group,
|
||||
modal-notifications-reservation-group,
|
||||
modal-notifications-task-calendar-group {
|
||||
modal-notifications-task-calendar-group,
|
||||
modal-notifications-update-group {
|
||||
@apply flex flex-col relative pb-2;
|
||||
|
||||
.header {
|
||||
@@ -57,7 +58,8 @@ modal-notifications {
|
||||
}
|
||||
}
|
||||
|
||||
modal-notifications-list-item {
|
||||
modal-notifications-list-item,
|
||||
modal-notifications-update-group {
|
||||
@apply flex flex-col relative py-1 px-4;
|
||||
|
||||
.notification-headline {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ModalNotificationsListItemComponent } from './notifications-list-item/n
|
||||
import { ModalNotificationsRemissionGroupComponent } from './notifications-remission-group/notifications-remission-group.component';
|
||||
import { ModalNotificationsReservationGroupComponent } from './notifications-reservation-group/notifications-reservation-group.component';
|
||||
import { ModalNotificationsTaskCalendarGroupComponent } from './notifications-task-calendar-group/notifications-task-calendar-group.component';
|
||||
import { ModalNotificationsUpdateGroupComponent } from './notifications-update-group/notifications-update-group.component';
|
||||
import { ModalNotificationsComponent } from './notifications.component';
|
||||
|
||||
@NgModule({
|
||||
@@ -16,6 +17,7 @@ import { ModalNotificationsComponent } from './notifications.component';
|
||||
ModalNotificationsReservationGroupComponent,
|
||||
ModalNotificationsRemissionGroupComponent,
|
||||
ModalNotificationsTaskCalendarGroupComponent,
|
||||
ModalNotificationsUpdateGroupComponent,
|
||||
ModalNotificationsListItemComponent,
|
||||
],
|
||||
exports: [
|
||||
@@ -23,6 +25,7 @@ import { ModalNotificationsComponent } from './notifications.component';
|
||||
ModalNotificationsReservationGroupComponent,
|
||||
ModalNotificationsRemissionGroupComponent,
|
||||
ModalNotificationsTaskCalendarGroupComponent,
|
||||
ModalNotificationsUpdateGroupComponent,
|
||||
ModalNotificationsListItemComponent,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -53,7 +53,7 @@ hr {
|
||||
}
|
||||
|
||||
span.number {
|
||||
@apply text-right;
|
||||
@apply text-center;
|
||||
}
|
||||
|
||||
span {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<div class="product-details">
|
||||
<div class="product-image">
|
||||
<button class="image-button" (click)="showImages()">
|
||||
<img [src]="item.imageId | productImage: 195:315:true" alt="product image" />
|
||||
<ui-icon icon="search_add" size="22px"></ui-icon>
|
||||
<img (load)="loadImage()" [src]="item.imageId | productImage: 195:315:true" alt="product image" />
|
||||
<ui-icon *ngIf="imageLoaded$ | async" icon="search_add" size="22px"></ui-icon>
|
||||
</button>
|
||||
|
||||
<button (click)="showReviews()" class="recessions" *ngIf="item.reviews?.length > 0">
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div>
|
||||
<div class="format">
|
||||
<div class="format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
class="format-icon"
|
||||
@@ -84,18 +84,33 @@
|
||||
<div data-name="product-ean">{{ item.product?.ean }}</div>
|
||||
<div class="right">
|
||||
<div class="availability-icons">
|
||||
<div class="fetching medium" *ngIf="fetchingAvailabilities$ | async"></div>
|
||||
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"></div>
|
||||
<ng-template #showAvailabilityTakeAwayIcon>
|
||||
<ui-icon *ngIf="store.isTakeAwayAvailabilityAvailable$ | async" icon="shopping_bag" size="18px"></ui-icon>
|
||||
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
|
||||
<ui-icon class="truck" *ngIf="showDeliveryTruck$ | async" icon="truck" size="30px"></ui-icon>
|
||||
<ui-icon class="truck_b2b" *ngIf="showDeliveryB2BTruck$ | async" icon="truck_b2b" size="40px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<span *ngIf="store.isDownload$ | async" class="download-icon">
|
||||
<ui-icon icon="download" size="18px"></ui-icon>
|
||||
<span class="label">Download</span>
|
||||
</span>
|
||||
</ng-container>
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"></div>
|
||||
<ng-template #showAvailabilityPickUpIcon>
|
||||
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"></div>
|
||||
<ng-template #showAvailabilityDeliveryIcon>
|
||||
<ui-icon *ngIf="showDeliveryTruck$ | async" class="truck" icon="truck" size="30px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="fetching xsmall"
|
||||
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityDeliveryB2BIcon>
|
||||
<ui-icon *ngIf="showDeliveryB2BTruck$ | async" class="truck_b2b" icon="truck_b2b" size="40px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<span *ngIf="store.isDownload$ | async" class="download-icon">
|
||||
<ui-icon icon="download" size="18px"></ui-icon>
|
||||
<span class="label">Download</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -108,6 +108,10 @@
|
||||
animation: load 0.75s linear infinite;
|
||||
}
|
||||
|
||||
.xsmall {
|
||||
@apply w-6;
|
||||
}
|
||||
|
||||
.small {
|
||||
@apply w-16;
|
||||
}
|
||||
@@ -117,7 +121,7 @@
|
||||
}
|
||||
|
||||
.availability-icons {
|
||||
@apply flex flex-row justify-end text-dark-cerulean mt-4;
|
||||
@apply flex flex-row items-center justify-end text-dark-cerulean mt-4;
|
||||
|
||||
ui-icon {
|
||||
@apply mx-1;
|
||||
@@ -168,7 +172,7 @@
|
||||
}
|
||||
|
||||
.product-text {
|
||||
@apply flex flex-col whitespace-pre-line mb-px-100;
|
||||
@apply flex flex-col whitespace-pre-line mb-px-100 break-words;
|
||||
|
||||
h3 {
|
||||
@apply my-4;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { UiModalService } from '@ui/modal';
|
||||
import { ModalReviewsComponent } from '@modal/reviews';
|
||||
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal';
|
||||
import { PurchasingOptions } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal/purchasing-options-modal.store';
|
||||
import { combineLatest, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { filter, first, map, shareReplay } from 'rxjs/operators';
|
||||
import { ArticleDetailsStore } from './article-details.store';
|
||||
import { ModalImagesComponent } from 'apps/modal/images/src/public-api';
|
||||
@@ -32,6 +32,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
private readonly subscriptions = new Subscription();
|
||||
showRecommendations: boolean;
|
||||
|
||||
imageLoaded$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
fetchingAvailabilities$ = combineLatest([
|
||||
this.store.fetchingDeliveryAvailability$,
|
||||
this.store.fetchingDeliveryB2BAvailability$,
|
||||
@@ -268,4 +270,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
const element = this.elementRef.nativeElement.closest('.main-wrapper');
|
||||
element?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
loadImage() {
|
||||
this.imageLoaded$.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainAvailabilityService, ItemData } from '@domain/availability';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { ItemDTO, ResponseArgsOfItemDTO } from '@swagger/cat';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { combineLatest, Observable, of } from 'rxjs';
|
||||
import { catchError, filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||
import { catchError, filter, first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
export interface ArticleDetailsState {
|
||||
fetchingItem?: boolean;
|
||||
@@ -241,7 +245,11 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
|
||||
|
||||
constructor(
|
||||
private readonly domainCatalogService: DomainCatalogService,
|
||||
private readonly domainAvailabilityService: DomainAvailabilityService
|
||||
private readonly domainAvailabilityService: DomainAvailabilityService,
|
||||
private readonly _appService: ApplicationService,
|
||||
private readonly _router: Router,
|
||||
private readonly _breadcrumb: BreadcrumbService,
|
||||
private readonly _modal: UiModalService
|
||||
) {
|
||||
super({});
|
||||
}
|
||||
@@ -253,9 +261,15 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
|
||||
switchMap((id) => this.domainCatalogService.getDetailsById({ id })),
|
||||
tapResponse<ResponseArgsOfItemDTO>(
|
||||
(response) => this.patchState({ item: response.result, fetchingItem: false }),
|
||||
(err) => {
|
||||
console.error('loadItemById failed', err);
|
||||
async (err) => {
|
||||
this.patchState({ item: undefined, fetchingItem: false });
|
||||
const errorModalRef = this._modal.open({
|
||||
content: UiErrorModalComponent,
|
||||
title: 'Fehler beim Laden des Artikels über die ID',
|
||||
data: { message: 'Sie kehren nun auf die ursprüngliche Artikeldetailseite zurück' },
|
||||
});
|
||||
await errorModalRef.afterClosed$.toPromise();
|
||||
await this.navigateBack();
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -268,11 +282,26 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
|
||||
switchMap((ean) => this.domainCatalogService.getDetailsByEan({ ean })),
|
||||
tapResponse<ResponseArgsOfItemDTO>(
|
||||
(response) => this.patchState({ item: response.result, fetchingItem: false }),
|
||||
(err) => {
|
||||
console.error('loadItemByEan failed', err);
|
||||
async (err) => {
|
||||
this.patchState({ item: undefined, fetchingItem: false });
|
||||
const errorModalRef = this._modal.open({
|
||||
content: UiErrorModalComponent,
|
||||
title: 'Fehler beim Laden des Artikels über die EAN',
|
||||
data: { message: 'Sie kehren nun auf die ursprüngliche Artikeldetailseite zurück' },
|
||||
});
|
||||
await errorModalRef.afterClosed$.toPromise();
|
||||
await this.navigateBack();
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
async navigateBack() {
|
||||
const crumb = await this._breadcrumb.getLastActivatedBreadcrumbByKey$(this._appService.activatedProcessId).pipe(first()).toPromise();
|
||||
if (crumb) {
|
||||
await this._router.navigate([crumb.path]);
|
||||
} else {
|
||||
await this._router.navigate(['/kunde', this._appService.activatedProcessId, 'product', 'search']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter(filter)">
|
||||
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)">
|
||||
Filter anwenden
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="fetching$ | async">
|
||||
<ui-spinner [show]="fetching$ | async">
|
||||
Filter anwenden
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
.cta-reset-filter,
|
||||
.cta-apply-filter {
|
||||
@apply text-lg font-bold px-6 py-3 rounded-full border-solid border-2 border-brand outline-none mx-2;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch cursor-not-allowed border-none text-white;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-reset-filter {
|
||||
|
||||
@@ -2,11 +2,12 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
|
||||
import { ArticleSearchFilterComponent } from './search-filter.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiFilterNextModule, UiIconModule],
|
||||
imports: [CommonModule, UiFilterNextModule, UiIconModule, UiSpinnerModule],
|
||||
exports: [ArticleSearchFilterComponent],
|
||||
declarations: [ArticleSearchFilterComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<a class="product-list-result-content" [routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'details', item?.id]">
|
||||
<div class="item-thumbnail">
|
||||
<img loading="lazy" *ngIf="item?.product?.ean | thumbnailUrl; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.title" />
|
||||
<img loading="lazy" *ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.title" />
|
||||
</div>
|
||||
|
||||
<div class="item-contributors">
|
||||
@@ -39,7 +39,7 @@
|
||||
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
|
||||
</div>
|
||||
|
||||
<div class="item-format">
|
||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="product-list scroll-bar"
|
||||
class="product-list scroll-bar scroll-bar-margin"
|
||||
[itemSize]="187"
|
||||
minBufferPx="1200"
|
||||
maxBufferPx="1200"
|
||||
|
||||
@@ -44,3 +44,9 @@
|
||||
@apply bg-brand text-white;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .desktop page-search-results {
|
||||
.scroll-bar-margin::-webkit-scrollbar-track {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AddToShoppingCartDTO } from '@swagger/checkout';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { UiErrorModalComponent, UiModalRef, UiModalService } from '@ui/modal';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { CacheService } from 'apps/core/cache/src/public-api';
|
||||
import { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ng-container *ngIf="(groupedItems$ | async)?.length <= 0">
|
||||
<ng-container *ngIf="(groupedItems$ | async)?.length <= 0 && !(fetching$ | async); else shoppingCart">
|
||||
<div class="card stretch card-empty">
|
||||
<div class="empty-message">
|
||||
<span class="cart-icon">
|
||||
@@ -20,7 +20,11 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="(groupedItems$ | async)?.length > 0">
|
||||
<div class="flex items-center justify-center card stretch card-empty" *ngIf="fetching$ | async">
|
||||
<ui-spinner show="true"> </ui-spinner>
|
||||
</div>
|
||||
|
||||
<ng-template #shoppingCart>
|
||||
<ng-container *ngIf="shoppingCart$ | async; let shoppingCart">
|
||||
<div class="card stretch">
|
||||
<div class="cta-print-wrapper">
|
||||
@@ -172,7 +176,15 @@
|
||||
</strong>
|
||||
<span class="shipping-cost-info">ohne Versandkosten</span>
|
||||
</div>
|
||||
<button class="cta-primary" (click)="order()" [disabled]="showOrderButtonSpinner">
|
||||
<button
|
||||
class="cta-primary"
|
||||
(click)="order()"
|
||||
[disabled]="
|
||||
showOrderButtonSpinner ||
|
||||
((primaryCtaLabel$ | async) === 'Bestellen' && !(checkNotificationChannelControl$ | async)) ||
|
||||
control.invalid
|
||||
"
|
||||
>
|
||||
<ui-spinner [show]="showOrderButtonSpinner">
|
||||
{{ primaryCtaLabel$ | async }}
|
||||
</ui-spinner>
|
||||
@@ -180,4 +192,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
@@ -70,7 +70,7 @@ button {
|
||||
@apply bg-brand text-white font-bold text-lg outline-none border-brand border-solid border-2 rounded-full px-6 py-3;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-customer border-none;
|
||||
@apply bg-inactive-customer border-solid border-inactive-customer cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
||||
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from '../modals/purchasing-options-modal';
|
||||
import { PurchasingOptions } from '../modals/purchasing-options-modal/purchasing-options-modal.store';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { first, map, shareReplay, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
@@ -25,6 +25,7 @@ import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
export interface CheckoutReviewComponentState {
|
||||
shoppingCart: ShoppingCartDTO;
|
||||
shoppingCartItems: ShoppingCartItemDTO[];
|
||||
fetching: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -36,6 +37,8 @@ export interface CheckoutReviewComponentState {
|
||||
export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewComponentState> implements OnInit {
|
||||
private _orderCompleted = new Subject<void>();
|
||||
|
||||
checkNotificationChannelControl$ = new BehaviorSubject<boolean>(true);
|
||||
|
||||
get shoppingCart() {
|
||||
return this.get((s) => s.shoppingCart);
|
||||
}
|
||||
@@ -52,6 +55,14 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
}
|
||||
readonly shoppingCartItems$ = this.select((s) => s.shoppingCartItems);
|
||||
|
||||
get fetching() {
|
||||
return this.get((s) => s.fetching);
|
||||
}
|
||||
set fetching(fetching: boolean) {
|
||||
this.patchState({ fetching });
|
||||
}
|
||||
readonly fetching$ = this.select((s) => s.fetching);
|
||||
|
||||
payer$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getPayer({ processId })),
|
||||
@@ -183,7 +194,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
|
||||
communicationDetails$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getBuyerCommunicationDetails({ processId }))
|
||||
switchMap((processId) => this.domainCheckoutService.getBuyerCommunicationDetails({ processId })),
|
||||
map((communicationDetails) => communicationDetails ?? { email: undefined, mobile: undefined })
|
||||
);
|
||||
|
||||
notificationChannelLoading$ = new Subject<boolean>();
|
||||
@@ -234,6 +246,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
super({
|
||||
shoppingCart: undefined,
|
||||
shoppingCartItems: [],
|
||||
fetching: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -249,6 +262,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
|
||||
loadShoppingCart = this.effect(($) =>
|
||||
$.pipe(
|
||||
tap(() => (this.fetching = true)),
|
||||
withLatestFrom(this.applicationService.activatedProcessId$),
|
||||
switchMap(([_, processId]) => {
|
||||
return this.domainCheckoutService.getShoppingCart({ processId, latest: true }).pipe(
|
||||
@@ -265,7 +279,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
() => {}
|
||||
)
|
||||
);
|
||||
})
|
||||
}),
|
||||
tap(() => (this.fetching = false))
|
||||
)
|
||||
);
|
||||
|
||||
@@ -305,26 +320,51 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
const communicationDetails = await this.communicationDetails$.pipe(first()).toPromise();
|
||||
this.control = fb.group({
|
||||
notificationChannel: new FormGroup({
|
||||
selected: new FormControl(notificationChannel),
|
||||
selected: new FormControl((notificationChannel & 3) === 3 || communicationDetails.email ? 1 : notificationChannel),
|
||||
email: new FormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
|
||||
mobile: new FormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async onNotificationChange(notificationChannels: NotificationChannel[]) {
|
||||
async onNotificationChange(notificationChannels?: NotificationChannel[]) {
|
||||
this.notificationChannelLoading$.next(true);
|
||||
|
||||
try {
|
||||
const control = this.control?.getRawValue();
|
||||
const notificationChannel = notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel;
|
||||
const notificationChannel = notificationChannels
|
||||
? (notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel)
|
||||
: control?.notificationChannel?.selected || 0;
|
||||
const processId = await this.applicationService.activatedProcessId$.pipe(first()).toPromise();
|
||||
const email = control?.notificationChannel?.email;
|
||||
const mobile = control?.notificationChannel?.mobile;
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email, mobile });
|
||||
|
||||
// Check if E-Mail and Mobilnumber is available if E-Mail or SMS checkbox is active
|
||||
if (notificationChannel === 3 && (!email || !mobile)) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else if (notificationChannel === 2 && !mobile) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else if (notificationChannel === 1 && !email) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else {
|
||||
this.checkNotificationChannelControl$.next(true);
|
||||
}
|
||||
|
||||
// NotificationChannel nur speichern, wenn Haken und Value gesetzt
|
||||
let setNotificationChannel = 0;
|
||||
if ((notificationChannel & 1) === 1 && email) {
|
||||
setNotificationChannel += 1;
|
||||
}
|
||||
if ((notificationChannel & 2) === 2 && mobile) {
|
||||
setNotificationChannel += 2;
|
||||
}
|
||||
|
||||
if (notificationChannel > 0) {
|
||||
this.setCommunicationDetails({ processId, notificationChannel, email, mobile });
|
||||
}
|
||||
this.domainCheckoutService.setNotificationChannels({
|
||||
processId,
|
||||
notificationChannels: (notificationChannel as NotificationChannel) || 0,
|
||||
notificationChannels: (setNotificationChannel as NotificationChannel) || 0,
|
||||
});
|
||||
} catch (error) {
|
||||
this.uiModal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim setzen des Benachrichtigungskanals' });
|
||||
@@ -333,6 +373,29 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
this.notificationChannelLoading$.next(false);
|
||||
}
|
||||
|
||||
setCommunicationDetails({
|
||||
processId,
|
||||
notificationChannel,
|
||||
email,
|
||||
mobile,
|
||||
}: {
|
||||
processId: number;
|
||||
notificationChannel: number;
|
||||
email: string;
|
||||
mobile: string;
|
||||
}) {
|
||||
const emailValid = this.control?.get('notificationChannel')?.get('email')?.valid;
|
||||
const mobileValid = this.control?.get('notificationChannel')?.get('mobile')?.valid;
|
||||
|
||||
if (notificationChannel === 3 && emailValid && mobileValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email, mobile });
|
||||
} else if (notificationChannel === 1 && emailValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email });
|
||||
} else if (notificationChannel === 2 && mobileValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, mobile });
|
||||
}
|
||||
}
|
||||
|
||||
openDummyModal(data?: any) {
|
||||
this.uiModal.open({
|
||||
content: CheckoutDummyComponent,
|
||||
@@ -749,10 +812,13 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
} else {
|
||||
try {
|
||||
this.showOrderButtonSpinner = true;
|
||||
await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
|
||||
// Ticket #3287 Um nur E-Mail und SMS Benachrichtigungen zu setzen und um alle anderen Benachrichtigungskanäle wie z.B. Brief zu deaktivieren
|
||||
await this.onNotificationChange();
|
||||
const orders = await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
|
||||
const orderIds = orders.map((order) => order.id).join(',');
|
||||
this._orderCompleted.next();
|
||||
await this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'cart', 'summary']);
|
||||
this.applicationService.removeProcess(this.applicationService.activatedProcessId);
|
||||
await this.patchProcess(processId);
|
||||
await this.router.navigate(['/kunde', processId, 'cart', 'summary', orderIds]);
|
||||
} catch (error) {
|
||||
const response = error?.error;
|
||||
let message: string = response?.message ?? '';
|
||||
@@ -771,8 +837,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
|
||||
if (error.status === 409) {
|
||||
this._orderCompleted.next();
|
||||
await this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'cart', 'summary']);
|
||||
this.applicationService.removeProcess(this.applicationService.activatedProcessId);
|
||||
await this.patchProcess(processId);
|
||||
await this.router.navigate(['/kunde', processId, 'cart', 'summary']);
|
||||
}
|
||||
} finally {
|
||||
this.showOrderButtonSpinner = false;
|
||||
@@ -780,4 +846,10 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async patchProcess(processId: number) {
|
||||
this.applicationService.patchProcess(processId, {
|
||||
type: 'cart-checkout',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user