Merge branch 'release/1.1'

This commit is contained in:
Michael Auer
2020-11-13 14:14:13 +01:00
1515 changed files with 47543 additions and 9181 deletions

16
.npmrc
View File

@@ -1,13 +1,3 @@
registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
always-auth=true
; Treat this auth token like a password. Do not share it with anyone, including Microsoft support. This token expires on or before 25.08.2020.
; begin auth token
//pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/:username=hugendubel
//pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/:_password=M2JkaDdwNjIzbWVoZGlmeDU3N2Ficjc3M252NXBkaWg1M2VtaW94dXp5amwyejNkaW5yYQ==
//pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/:email=npm requires email to be set but doesn't use the value
//pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/:username=hugendubel
//pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/:_password=M2JkaDdwNjIzbWVoZGlmeDU3N2Ficjc3M252NXBkaWg1M2VtaW94dXp5amwyejNkaW5yYQ==
//pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/:email=npm requires email to be set but doesn't use the value
; end auth token
@isa:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
@cmf:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
always-auth=true

10
.prettierignore Normal file
View File

@@ -0,0 +1,10 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/helmvalues
/apps/swagger
/ng-swagger-gen
*.json
*.yml

5
.prettierrc.json Normal file
View File

@@ -0,0 +1,5 @@
{
"singleQuote": true,
"printWidth": 140
}

9
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"recommendations": [
"johnpapa.angular2",
"esbenp.prettier-vscode",
"angular.ng-template",
"ms-vscode.vscode-typescript-tslint-plugin",
"eg2.vscode-npm-script"
]
}

View File

@@ -1,9 +1,11 @@
#stage 1
FROM node:10-stretch as node
ARG IS_PRODUCTION=false
ARG SEMVERSION=1.0.0
WORKDIR /app
COPY . .
RUN umask 0022
RUN npm version ${SEMVERSION}
RUN npm install --always-auth=false
RUN if [ "${IS_PRODUCTION}" = "true" ] ; then npm run-script build-prod ; else npm run-script build ; fi

4
TASKS.md Normal file
View File

@@ -0,0 +1,4 @@
- Neue Icon Module (z.B. mit SVG sprites)
- Breadcrumb Navigation (Neu)
- Remissions Produkt Liste (Refactoring / Neu)
- Angular Version (Upgrade)

View File

@@ -31,7 +31,9 @@
"libs/ui/tsconfig.lib.json",
"libs/ui/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -47,6 +49,7 @@
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/sales",
"outputHashing": "all",
"index": "apps/sales/src/index.html",
"main": "apps/sales/src/main.ts",
"polyfills": "apps/sales/src/polyfills.ts",
@@ -55,11 +58,16 @@
"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"],
"styles": [
"apps/sales/src/styles.scss"
],
"stylePreprocessorOptions": {
"includePaths": ["apps/sales/src/scss"]
"includePaths": [
"apps/sales/src/scss"
]
},
"scripts": []
},
@@ -89,25 +97,7 @@
],
"serviceWorker": true
},
"development": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
],
"serviceWorker": true
}
"development": {}
}
},
"serve": {
@@ -143,7 +133,14 @@
"polyfills": "apps/sales/src/polyfills.ts",
"tsConfig": "apps/sales/tsconfig.spec.json",
"karmaConfig": "apps/sales/karma.conf.js",
"styles": ["apps/sales/src/styles.scss"],
"styles": [
"apps/sales/src/styles.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"apps/sales/src/scss"
]
},
"scripts": [],
"assets": [
"apps/sales/src/favicon.ico",
@@ -159,7 +156,9 @@
"apps/sales/tsconfig.app.json",
"apps/sales/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -188,7 +187,9 @@
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "apps/sales-e2e/tsconfig.e2e.json",
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -221,7 +222,9 @@
"libs/sso/tsconfig.lib.json",
"libs/sso/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -254,7 +257,9 @@
"apps/swagger/availability/tsconfig.lib.json",
"apps/swagger/availability/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -287,7 +292,9 @@
"apps/swagger/checkout/tsconfig.lib.json",
"apps/swagger/checkout/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -320,7 +327,9 @@
"apps/swagger/crm/tsconfig.lib.json",
"apps/swagger/crm/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -353,7 +362,9 @@
"apps/swagger/isa/tsconfig.lib.json",
"apps/swagger/isa/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -386,7 +397,9 @@
"apps/swagger/oms/tsconfig.lib.json",
"apps/swagger/oms/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -419,7 +432,9 @@
"apps/swagger/print/tsconfig.lib.json",
"apps/swagger/print/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -452,7 +467,44 @@
"apps/swagger/cat/tsconfig.lib.json",
"apps/swagger/cat/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@swagger/eis": {
"projectType": "library",
"root": "apps/swagger/eis",
"sourceRoot": "apps/swagger/eis/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "apps/swagger/eis/tsconfig.lib.json",
"project": "apps/swagger/eis/ng-package.json"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/swagger/eis/src/test.ts",
"tsConfig": "apps/swagger/eis/tsconfig.spec.json",
"karmaConfig": "apps/swagger/eis/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/swagger/eis/tsconfig.lib.json",
"apps/swagger/eis/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
@@ -491,7 +543,112 @@
}
}
}
},
"@isa/remission": {
"projectType": "library",
"root": "apps/isa/remission",
"sourceRoot": "apps/isa/remission/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "apps/isa/remission/tsconfig.lib.json",
"project": "apps/isa/remission/ng-package.json"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/isa/remission/src/test.ts",
"tsConfig": "apps/isa/remission/tsconfig.spec.json",
"karmaConfig": "apps/isa/remission/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/isa/remission/tsconfig.lib.json",
"apps/isa/remission/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@domain/crm": {
"projectType": "library",
"root": "apps/domain/crm",
"sourceRoot": "apps/domain/crm/src",
"prefix": "crm",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "apps/domain/crm/tsconfig.lib.json",
"project": "apps/domain/crm/ng-package.json"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/domain/crm/src/test.ts",
"tsConfig": "apps/domain/crm/tsconfig.spec.json",
"karmaConfig": "apps/domain/crm/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/domain/crm/tsconfig.lib.json",
"apps/domain/crm/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@domain/checkout": {
"projectType": "library",
"root": "apps/domain/checkout",
"sourceRoot": "apps/domain/checkout/src",
"prefix": "checkout",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "apps/domain/checkout/tsconfig.lib.json",
"project": "apps/domain/checkout/ng-package.json"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/domain/checkout/src/test.ts",
"tsConfig": "apps/domain/checkout/tsconfig.spec.json",
"karmaConfig": "apps/domain/checkout/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/domain/checkout/tsconfig.lib.json",
"apps/domain/checkout/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "sales"
}
}

View File

@@ -0,0 +1,25 @@
# Checkout
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.14.
## Code scaffolding
Run `ng generate component component-name --project checkout` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project checkout`.
> Note: Don't forget to add `--project checkout` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build checkout` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build checkout`, go to the dist folder `cd dist/checkout` and run `npm publish`.
## Running unit tests
Run `ng test checkout` 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 README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/domain/checkout'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

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

View File

@@ -0,0 +1,8 @@
{
"name": "@domain/checkout",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14"
}
}

View File

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

View File

@@ -0,0 +1,16 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'checkout-checkout',
template: `
<p>
checkout works!
</p>
`,
styles: [],
})
export class CheckoutComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { CheckoutComponent } from './checkout.component';
@NgModule({
declarations: [CheckoutComponent],
imports: [],
exports: [CheckoutComponent],
})
export class CheckoutModule {}

View File

@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { CheckoutService } from './checkout.service';
describe('CheckoutService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: CheckoutService = TestBed.get(CheckoutService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class CheckoutService {
constructor() {}
}

View File

@@ -0,0 +1,7 @@
/*
* Public API Surface of checkout
*/
export * from './lib/checkout.service';
export * from './lib/checkout.component';
export * from './lib/checkout.module';

View File

@@ -0,0 +1,15 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,26 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"checkout",
"camelCase"
],
"component-selector": [
true,
"element",
"checkout",
"kebab-case"
]
}
}

25
apps/domain/crm/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Crm
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.14.
## Code scaffolding
Run `ng generate component component-name --project crm` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project crm`.
> Note: Don't forget to add `--project crm` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build crm` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build crm`, go to the dist folder `cd dist/crm` and run `npm publish`.
## Running unit tests
Run `ng test crm` 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 README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/domain/crm'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

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

View File

@@ -0,0 +1,8 @@
{
"name": "@domain/crm",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14"
}
}

View File

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

View File

@@ -0,0 +1,16 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'crm-crm',
template: `
<p>
crm works!
</p>
`,
styles: [],
})
export class CrmComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { CrmComponent } from './crm.component';
@NgModule({
declarations: [CrmComponent],
imports: [],
exports: [CrmComponent],
})
export class CrmModule {}

View File

@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { CrmService } from './crm.service';
describe('CrmService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: CrmService = TestBed.get(CrmService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class CrmService {
constructor() {}
}

View File

@@ -0,0 +1,7 @@
/*
* Public API Surface of crm
*/
export * from './lib/crm.service';
export * from './lib/crm.component';
export * from './lib/crm.module';

View File

@@ -0,0 +1,15 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,26 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"crm",
"camelCase"
],
"component-selector": [
true,
"element",
"crm",
"kebab-case"
]
}
}

View File

@@ -0,0 +1,25 @@
# Remission
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.14.
## Code scaffolding
Run `ng generate component component-name --project remission` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project remission`.
> Note: Don't forget to add `--project remission` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build remission` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build remission`, go to the dist folder `cd dist/remission` and run `npm publish`.
## Running unit tests
Run `ng test remission` 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 README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/isa/remission'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

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

View File

@@ -0,0 +1,8 @@
{
"name": "@isa/remission",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14"
}
}

View File

@@ -0,0 +1,3 @@
// start:ng42.barrel
export * from './uid-generator.service';
// end:ng42.barrel

View File

@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
function* getUniqueNumber(): IterableIterator<number> {
let timeStamp = Date.now();
while (true) {
yield timeStamp++;
}
}
@Injectable({ providedIn: 'root' })
export class UidGeneratorService {
private generator = getUniqueNumber();
constructor() {}
generate(options: { type: 'string' }): string;
generate(options: { type: 'number' }): number;
generate(options: { type: 'string' | 'number' } = { type: 'string' }): string | number {
const id = this.generator.next();
if (options.type === 'string') {
return id.value.toString(32);
}
return id.value;
}
}

View File

@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { Mapper } from './mapper';
@Injectable()
export class FeaturesToAssortmentMapping implements Mapper<{ key: string; name: string; value?: string }[], string> {
get sourceName(): string {
return 'Features';
}
get targetName(): string {
return 'Assortment';
}
map(source: { key: string; name: string; value?: string }[]): string {
if (!source || (!!source.length && !source.find((feat) => feat.hasOwnProperty('key')))) {
return '';
}
return source.reduce((acc, curr) => acc + `${curr.name || ''}|${curr.key.slice(curr.key.length - 1, curr.key.length)}`, '');
}
}

View File

@@ -0,0 +1,41 @@
import { Injectable } from '@angular/core';
import { Mapper } from './mapper';
import { ItemDTO } from '@isa/catsearch-api';
import { Product } from '../models/product';
@Injectable()
export class ItemDtoToProductMapping implements Mapper<ItemDTO, Product> {
get sourceName(): string {
return 'ItemDTO';
}
get targetName(): string {
return 'Product';
}
map(source: ItemDTO): Product {
return {
// tslint:disable-next-line: no-string-literal
id: (source.ids && source.ids[0]) || source['id'],
imageId: source.product.ean,
contributors: source.product.contributors,
name: source.product.name,
format: source.product.format,
formatDetail: source.product.formatDetail,
ean: source.product.ean,
productGroup: source.product.productGroup,
productGroupName: '',
features:
source.features &&
source.features.map((feature) => ({
name: feature.value,
key: feature.key[feature.key.length - 1],
})),
// tslint:disable-next-line: no-string-literal TODO update ItemDTO interface
sourceProduct: { ...source.product, catalogProductNumber: source['id'] },
sourcePrice:
(source.storeAvailabilities && source.storeAvailabilities[0] && source.storeAvailabilities[0].price) ||
(source.catalogAvailability && source.catalogAvailability.price),
} as Product;
}
}

View File

@@ -0,0 +1,7 @@
export abstract class Mapper<TSource = any, TTarget = any> {
abstract get sourceName(): string;
abstract get targetName(): string;
abstract map(source: TSource): TTarget;
}

View File

@@ -0,0 +1,12 @@
import { Injectable, Injector } from '@angular/core';
import { Mapper } from './mapper';
@Injectable({ providedIn: 'root' })
export class MappingService {
constructor(private injector: Injector) {}
get<T extends Mapper>(sourceName: string, targetName: string): T {
const mappers = this.injector.get(Mapper) as Mapper[];
return mappers.find((f) => f.sourceName === sourceName && f.targetName === targetName) as T;
}
}

View File

@@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { PriceDTO } from '@cmf/inventory-api';
import { Mapper } from './mapper';
import { Product } from '../models/product';
@Injectable()
export class ProductToPriceDTOMapping implements Mapper<Product, PriceDTO> {
defaultPrice: PriceDTO = null;
constructor() {}
get sourceName(): string {
return 'Product';
}
get targetName(): string {
return 'PriceDTO';
}
map(source: Product): PriceDTO {
if (source.sourcePrice) {
return source.sourcePrice;
} else {
return this.defaultPrice;
throw Error('Keine Preisinformationen vorhanden');
}
}
}

View File

@@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { ProductDTO } from '@cmf/inventory-api';
import { Mapper } from './mapper';
import { Product } from '../models/product';
@Injectable()
export class ProductToProductDTOMapping implements Mapper<Product, ProductDTO> {
constructor() {}
get sourceName(): string {
return 'Product';
}
get targetName(): string {
return 'ProductDTO';
}
map(source: Product): ProductDTO {
if (source.sourceProduct) {
return source.sourceProduct;
} else {
throw Error('Keine Produktinformationen vorhanden');
}
}
}

View File

@@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { Mapper } from './mapper';
import { ReceiptDTO } from '@cmf/inventory-api';
import { ReceiptItemDtoToRemissionProductMapping } from './receipt-item-dto-to-remission-product.mapping';
import { MappingService } from './mapping.service';
import { ShippingDocument } from '../models/shipping-document';
@Injectable()
export class ReceiptDTOToShippingDocumentMapping
implements Mapper<ReceiptDTO, ShippingDocument> {
constructor(private mappingService: MappingService) {}
get sourceName() {
return 'ReceiptDTO';
}
get targetName() {
return 'ShippingDocument';
}
map(source: ReceiptDTO): ShippingDocument {
const productMapper = this.mappingService.get<
ReceiptItemDtoToRemissionProductMapping
>('ReceiptItemDTO', 'RemissionProduct');
return {
id: source.id,
shippingDocumentNumber: source.receiptNumber,
packageNumber:
source.packages &&
source.packages[0] &&
source.packages[0].data &&
source.packages[0].data.packageNumber,
products: source.items.map((item) =>
productMapper.map(item.data || item)
),
isCompleted: !!source.completed || source.status === 4 ? true : false,
isDeleted: source.status === 4,
};
}
}

View File

@@ -0,0 +1,38 @@
import { Mapper } from './mapper';
import { ReceiptItemDTO } from '@cmf/inventory-api';
import { Injectable } from '@angular/core';
import { RemissionProduct } from '../models/remission-product';
@Injectable()
export class ReceiptItemDtoToRemissionProductMapping implements Mapper<ReceiptItemDTO, RemissionProduct> {
get sourceName() {
return 'ReceiptItemDTO';
}
get targetName() {
return 'RemissionProduct';
}
map(source: ReceiptItemDTO): RemissionProduct {
return {
id: source.id,
imageId: source.product && source.product.ean,
contributors: source.product && source.product.contributors,
name: source.product && source.product.name,
format: source.product && source.product.format,
formatDetail: source.product && source.product.formatDetail,
ean: source.product && source.product.ean,
price: source.price && source.price.value.value,
currency: source.price && source.price.value.currency,
productGroup: source.product && source.product.productGroup,
productGroupName: '',
inStock: null,
remainingQuantity: null,
remissionQuantity: source.quantity,
placementType: 'Leistung',
remissionReason: '',
edition: source.product && source.product.volume,
features: [],
} as RemissionProduct;
}
}

View File

@@ -0,0 +1,73 @@
import { Injectable } from '@angular/core';
import { ReturnItemDTO } from '@cmf/inventory-api';
import { Mapper } from './mapper';
import { RemissionProduct } from '../models/remission-product';
@Injectable()
export class ReturnItemDtoToRemissionProductMapping implements Mapper<ReturnItemDTO, RemissionProduct> {
get sourceName(): string {
return 'ReturnItemDTO';
}
get targetName(): string {
return 'RemissionProduct';
}
private getFeatures(assortment: string) {
const featuresString = assortment && assortment.split('|');
let features = [];
if (featuresString) {
features = featuresString.slice(1, featuresString.length).map((str, index) => {
return {
[str.split(';')[0]]: assortment.split('|')[index].split(';')[1] || assortment.split('|')[index],
};
});
}
const uniqueKeys = Array.from(new Set(features.map((feature) => Object.keys(feature)).reduce((acc, curr) => acc.concat(curr), [])));
const result = uniqueKeys.map((key) => {
return {
[key]: features
.map((feature) => {
if (feature.hasOwnProperty(key)) {
return feature[key];
}
})
.filter((f) => !!f)
.join(', ')
.toString()
.trim(),
};
});
return result;
}
map(source: ReturnItemDTO): RemissionProduct {
return {
id: source.id,
imageId: source.product.ean,
contributors: source.product.contributors,
name: source.product.name,
format: source.product.format,
formatDetail: source.product.formatDetail,
ean: source.product.ean,
price: source.retailPrice.value.value,
currency: source.retailPrice.value.currency,
productGroup: source.product.productGroup,
productGroupName: '',
inStock: null, // info retrieved separately
remissionQuantity: source.predefinedReturnQuantity,
remainingQuantity: source.remainingQuantityInStock,
placementType: source.placementType,
remissionReason: source.returnReason,
edition: source.product.volume,
isManuallyAdded: source.source && source.source === 'manually-added' ? true : false,
catalogProductNumber: source.product.catalogProductNumber,
features: this.getFeatures(source.assortment), // assortment (semicolon separiert ohne 'SO') z.B. "Wirtschaft|B"
isResidual: source.descendantOf && source.descendantOf.enabled ? true : false || !!source.impediment,
} as RemissionProduct;
}
}

View File

@@ -0,0 +1,43 @@
import { ReturnSuggestionDTO } from '@cmf/inventory-api';
import { Injectable } from '@angular/core';
import { Mapper } from './mapper';
import { RemissionProduct } from '../models/remission-product';
@Injectable()
export class ReturnSuggestionDtoToRemissionProductMapping implements Mapper<ReturnSuggestionDTO, RemissionProduct> {
get sourceName(): string {
return 'ReturnSuggestionDTO';
}
get targetName(): string {
return 'RemissionProduct';
}
map(source: ReturnSuggestionDTO): RemissionProduct {
return {
id: source.id,
imageId: source.product.ean,
contributors: source.product.contributors,
name: source.product.name,
format: source.product.format,
formatDetail: source.product.formatDetail,
ean: source.product.ean,
price: source.retailPrice.value.value,
currency: source.retailPrice.value.currency,
productGroup: source.product.productGroup,
productGroupName: '',
inStock: 0,
remissionQuantity: source.returnItem && source.returnItem.data.predefinedReturnQuantity,
remainingQuantity: 0,
placementType: source.placementType,
remissionReason: source.returnReason,
edition: source.product.volume,
catalogProductNumber: source.product.catalogProductNumber,
features: [],
isResidual:
!!source.impediment ||
// tslint:disable-next-line: no-string-literal
(source['descendantOf'] && !!source['descendantOf'].enabled),
} as RemissionProduct;
}
}

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { SupplierDTO } from '@cmf/inventory-api';
import { RemissionSupplier } from '../models';
import { Mapper } from './mapper';
@Injectable()
export class SupplierDtoToRemissionSupplier implements Mapper<SupplierDTO, RemissionSupplier> {
get sourceName(): string {
return 'SupplierDTO';
}
get targetName(): string {
return 'RemissionSupplier';
}
map(source: SupplierDTO): RemissionSupplier {
return {
name: source.name || '',
supplierNumber: source.supplierNumber || '',
id: source.id,
};
}
}

View File

@@ -0,0 +1,53 @@
import { createRandomId } from '../util';
import { VATType } from '@cmf/trade-api';
import { Product } from '../../models/product';
export const product: Product = {
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Sascha Kersken',
name: 'Remission für Fortgeschrittene',
format: 'HC',
formatDetail: 'Buch (Kartoniert, Paperback)',
ean: '2638489561882',
productGroup: '111',
productGroupName: 'Krimi/Spannung',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
{
key: 'SOE',
name: 'Extrasortiment',
},
],
sourcePrice: {
value: {
value: 100,
currency: 'EUR',
currencySymbol: '€',
},
vat: {
inPercent: 7,
vatType: VATType.MediumRate,
},
},
sourceProduct: {
name: 'Remission für Fortgeschrittene',
ean: '2638489561882',
contributors: 'Sascha Kersken',
supplierProductNumber: '123ABC',
catalogProductNumber: '123XYZ',
manufacturer: 'Hugendubel',
format: 'HC',
formatDetail: 'Buch (Kartoniert, Paperback)',
},
};
export function removeSourcePriceAndProductFromProduct(prod: Product): Partial<Product> {
delete prod.sourcePrice;
delete prod.sourceProduct;
return prod;
}

View File

@@ -0,0 +1,16 @@
import { CapacityType } from '../../models/capacity-type';
export const remissionCapacities: CapacityType[] = [
{
name: 'Leistunsgplätze',
available: 100,
utilized: Math.floor(Math.random() * 100),
label: 'Titel',
},
{
name: 'Stapelplätze',
available: 100,
utilized: Math.floor(Math.random() * 100),
label: 'Exemplaren',
},
];

View File

@@ -0,0 +1,88 @@
import { remissionTargets } from './remission-targets';
import { remissionSources } from './remission-sources';
import { Filter } from '../../models/filter';
import { RemissionFilter } from '../../models/remission-filter';
export const filter: Filter[] = [
{
id: 'ce9ee0be',
name: 'Aktionen',
options: [
{
id: '6e99',
name: 'Weihnachtsaktion',
},
{ id: 'a6a0', name: 'Osteraktion' },
{ id: '035d', name: 'Herbstaktion' },
],
},
{
id: '421228ce',
name: 'Abteilung & Warengruppe',
options: [
{
id: 'a7zd70',
name: 'Romane',
options: [
{
id: '111',
name: 'Romane HC',
},
{
id: '112',
name: 'Romane TB',
},
{
id: '113',
name: 'Historische Romane HC',
},
],
},
{
id: 'ah55wm',
name: 'Krimi',
options: [
{
id: '120',
name: 'Krimi HC',
},
{
id: '121',
name: 'Krimi TB',
},
{
id: '122',
name: 'Krimi allgemein',
},
],
},
],
},
];
export const filterUeberlauf: Filter[] = [
{
id: 'bi1uc0az',
name: 'Abteilung',
options: [
{
id: '6e99',
name: 'DVD',
},
{ id: 'a6a0', name: 'Hörbuch' },
{ id: '035d', name: 'Kinderwelt' },
{ id: '6t78', name: 'Bewusster Leben' },
{ id: '2k49', name: 'Bewusster Leben' },
],
},
];
export const remissionFilter: RemissionFilter = {
filter: {},
skip: 0,
take: 25,
target: remissionTargets[1],
source: remissionSources[0],
};

View File

@@ -0,0 +1,3 @@
import { RemissionPlacementType } from '../../types/remission-placement-types';
export const remissionPlacementTypes: RemissionPlacementType[] = ['Stapel', 'Leistung'];

View File

@@ -0,0 +1,18 @@
import { Printer } from '../../models/printer';
export const printers: Printer[] = [
{
enabled: true,
key: '\\DBHPSS8301.dbh.localETI_HILBLE',
value: 'SE Label-Drucker',
selected: true,
description: 'Label-Drucker in der SE',
},
{
enabled: true,
key: '\\dbhpss8301.dbh.localkkm_se_igel_01',
value: 'SE Label-Drucker über IGEL',
selected: false,
description: 'Label-Drucker in der SE, am IGEL angeschlossen',
},
];

View File

@@ -0,0 +1,22 @@
import { RemissionProcess } from '../../models/remission-process';
import { remissionCapacities } from './remission-capacities';
import { createRandomStringId, createRandomId } from '../util';
import { remissionFilter } from './remission-filter';
import { ShippingDocument } from '../../models';
export const remissionProcessTemplateMock: RemissionProcess = {
id: createRandomId(),
returnGroup: createRandomId(),
stockId: createRandomId(),
capacities: remissionCapacities,
filter: remissionFilter,
};
export const remissionProcessTemplates: RemissionProcess[] = [remissionProcessTemplateMock];
export function createRemissionProcessFromTemplate(remission: RemissionProcess): RemissionProcess {
const shippingDocuments = [{ id: createRandomId(), shippingDocumentNumber: createRandomStringId(), products: [] } as ShippingDocument];
const remissionId = createRandomId();
return { ...remission, externalId: remissionId, shippingDocuments };
}

View File

@@ -0,0 +1,490 @@
import { createRandomId } from '../util';
import { RemissionProduct } from '../../models/remission-product';
export const remissionProducts: RemissionProduct[] = [
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'Max Mustermann',
name: 'Remission für Dummies',
format: 'HC',
formatDetail: 'Buch (Gebunden)',
ean: '6294261363805',
price: 38.9,
currency: 'EUR',
productGroup: '121',
productGroupName: 'Krimi/Spannung',
inStock: 40,
remissionQuantity: 25,
remainingQuantity: 15,
placementType: 'Stapel',
remissionReason: 'Kulanz',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Sascha Kersken',
name: 'Remission für Fortgeschrittene',
format: 'HC',
formatDetail: 'Buch (Kartoniert, Paperback)',
ean: '2638489561882',
price: 10,
currency: 'EUR',
productGroup: '111',
productGroupName: 'Krimi/Spannung',
inStock: 10,
remissionQuantity: 9,
remainingQuantity: 1,
placementType: 'Leistung',
remissionReason: 'Falschlieferung',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
{
key: 'SOE',
name: 'Extrasortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Hugen Dubel',
name: 'So werden Sie zum Remissions-Experten',
format: 'TB',
formatDetail: 'Taschenbuch',
ean: '4638419565833',
price: 15.29,
currency: 'EUR',
productGroup: '133',
productGroupName: 'Esoterik',
inStock: 100,
remissionQuantity: 1,
remainingQuantity: 99,
placementType: 'Leistung',
remissionReason: 'Herstellerfehler',
edition: '3.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
{
key: 'SOZ',
name: 'Zentralsortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Pötzsch, Oliver',
name: 'Der Spielmann',
format: 'TB',
formatDetail: 'Taschenbuch',
ean: '4169898411136',
price: 5.99,
currency: 'EUR',
productGroup: '935',
productGroupName: 'Fantasie',
inStock: 2,
remissionQuantity: 1,
remainingQuantity: 1,
placementType: 'Leistung',
remissionReason: 'Herstellerfehler',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Homann, Eberhard, Homann, Klaudia',
name: 'Individualreiseführer mit Extra-Reisekarte und Karten-Download',
format: 'HC',
formatDetail: 'Buch (Kartoniert, Paperback)',
ean: '9723831038123',
price: 19.95,
currency: 'EUR',
productGroup: '112',
productGroupName: 'Krimi',
inStock: 5,
remissionQuantity: 5,
remainingQuantity: 0,
placementType: 'Leistung',
remissionReason: 'Herstellerfehler',
edition: '',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
{
key: 'SOE',
name: 'Extrasortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Blümm, Florian',
name: 'Mit wenig Geld um die Welt',
format: 'TB',
formatDetail: 'Taschenbuch',
ean: '6169892433336',
price: 7,
currency: 'EUR',
productGroup: '312',
productGroupName: 'Abenteuer/Reise',
inStock: 45,
remissionQuantity: 3,
remainingQuantity: 42,
placementType: 'Leistung',
remissionReason: 'Kulanz',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Murakami, Haruki, Gräfe, Ursula',
name: 'Wovon ich rede, wenn ich vom Laufen rede',
format: 'TB',
formatDetail: 'Taschenbuch (Kartoniert, Paperback)',
ean: '4642419565833',
price: 11.49,
currency: 'EUR',
productGroup: '112',
productGroupName: 'Krimi',
inStock: 250,
remissionQuantity: 10,
remainingQuantity: 240,
placementType: 'Leistung',
remissionReason: 'Herstellerfehler',
edition: '2.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
{
key: 'SOZ',
name: 'Zentralsortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'McKeown, Patrick',
name: 'Erfolgsfaktor Sauerstoff',
format: 'EB',
formatDetail: 'E-Book (E-Book)',
ean: '4638419765833',
price: 15.99,
currency: 'EUR',
productGroup: '973',
productGroupName: 'Wissenschaft',
inStock: 12,
remissionQuantity: 8,
remainingQuantity: 4,
placementType: 'Stapel',
remissionReason: 'Kulanz',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
{
key: 'SOZ',
name: 'Zentralsortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'Lobrecht, Felix',
name: 'Sonne und Beton',
format: 'HC',
formatDetail: 'Buch (Gebunden)',
ean: '6294261363805',
price: 3.9,
currency: 'EUR',
productGroup: '112',
productGroupName: 'Krimi/Spannung',
inStock: 33,
remissionQuantity: 11,
remainingQuantity: 22,
placementType: 'Stapel',
remissionReason: 'Kulanz',
edition: '4.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Dorléans, Marie',
name: 'Auf leisen Sohlen durch die Nacht',
format: 'TB',
formatDetail: 'Taschenbuch',
ean: '4638529565833',
price: 15.29,
currency: 'EUR',
productGroup: '133',
productGroupName: 'Esoterik',
inStock: 100,
remissionQuantity: 1,
remainingQuantity: 99,
placementType: 'Leistung',
remissionReason: 'Herstellerfehler',
edition: '6.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
{
key: 'SOZ',
name: 'Zentralsortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'May, Sabine',
name: 'ADAC Reiseführer Algarve',
format: 'HC',
formatDetail: 'Buch (Gebunden)',
ean: '6294261363305',
price: 6.95,
currency: 'EUR',
productGroup: '312',
productGroupName: 'Abenteuer/Reise',
inStock: 30,
remissionQuantity: 10,
remainingQuantity: 20,
placementType: 'Stapel',
remissionReason: 'Kulanz',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'Schützsack, Lara',
name: 'Sonne, Moon und Sterne',
format: 'HC',
formatDetail: 'Buch (Gebunden)',
ean: '6374261363305',
price: 6.95,
currency: 'EUR',
productGroup: '312',
productGroupName: 'Abenteuer/Reise',
inStock: 30,
remissionQuantity: 10,
remainingQuantity: 20,
placementType: 'Stapel',
remissionReason: 'Kulanz',
edition: '2.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'Taleb, Nassim Nicholas',
name: 'Skin in the Game',
format: 'HC',
formatDetail: 'Buch (Kartoniert, Paperback)',
ean: '6294266323805',
price: 14.49,
currency: 'EUR',
productGroup: '112',
productGroupName: 'Krimi/Spannung',
inStock: 10,
remissionQuantity: 3,
remainingQuantity: 7,
placementType: 'Stapel',
remissionReason: 'Herstellerfehler',
edition: '4.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'Gehrmann, Markus Don Alfred',
name: 'Fraktale',
format: 'HC',
formatDetail: 'Buch (Kartoniert, Paperback)',
ean: '6294261363805',
price: 208.99,
currency: 'EUR',
productGroup: '111',
productGroupName: 'Krimi/Spannung',
inStock: 2,
remissionQuantity: 2,
remainingQuantity: 0,
placementType: 'Stapel',
remissionReason: 'Herstellerfehler',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'Fowler, Martin',
name: 'Refactoring',
format: 'EB',
formatDetail: 'E-Book (E-Book)',
ean: '3214231363805',
price: 42,
currency: 'EUR',
productGroup: '113',
productGroupName: 'Krimi/Spannung',
inStock: 3,
remissionQuantity: 2,
remainingQuantity: 1,
placementType: 'Stapel',
remissionReason: 'Herstellerfehler',
edition: '10.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783836270236',
contributors: 'Frankl, Viktor E.',
name: "Man's Search for Meaning",
format: 'TB',
formatDetail: 'Taschenbuch (Kartoniert, Paperback)',
ean: '7638229565833',
price: 12.9,
currency: 'EUR',
productGroup: '133',
productGroupName: 'Esoterik',
inStock: 5,
remissionQuantity: 1,
remainingQuantity: 4,
placementType: 'Leistung',
remissionReason: 'Herstellerfehler',
edition: '6.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
{
key: 'SOZ',
name: 'Zentralsortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'Heinemeyer, Christoph',
name: 'Brandsicherheit durch Konstruktion und Stahlsortenwahl',
format: 'EB',
formatDetail: 'E-Book (E-Book)',
ean: '4212431233805',
price: 16.99,
currency: 'EUR',
productGroup: '113',
productGroupName: 'Krimi/Spannung',
inStock: 49,
remissionQuantity: 4,
remainingQuantity: 45,
placementType: 'Stapel',
remissionReason: 'Herstellerfehler',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
{
id: createRandomId(),
imageId: '9783864906466',
contributors: 'Heinemeyer, Elfriede; Ottenjann, Helmut',
name: 'Alte Bauernmöbel aus dem nordwestlichen Niedersachsen',
format: 'EB',
formatDetail: 'E-Book (E-Book)',
ean: '4212477733805',
price: 16.99,
currency: 'EUR',
productGroup: '113',
productGroupName: 'Krimi/Spannung',
inStock: 28,
remissionQuantity: 14,
remainingQuantity: 14,
placementType: 'Stapel',
remissionReason: 'Herstellerfehler',
edition: '1.',
features: [
{
key: 'SOB',
name: 'Basissortiment',
},
],
},
];
export function createRemissionProduct(product: Partial<RemissionProduct>): RemissionProduct {
const baseProduct = remissionProducts[0];
const newProduct = { ...baseProduct, ...product };
return newProduct;
}

View File

@@ -0,0 +1,11 @@
export const remissionReasons = [
'Beschädigtes Exemplar',
'Kulanz',
'Falschlieferung',
'Zu spät geliefert',
'Alte oder falsche Auflage geliefert',
'Herstellerfehler',
'LFA',
'Veranstaltungsüberhang',
'Anderer Remigrund',
];

View File

@@ -0,0 +1,3 @@
import { RemissionSourceType } from '../../types/remission-source.type';
export const remissionSources: RemissionSourceType[] = ['zentral', 'ueberlauf'];

View File

@@ -0,0 +1,14 @@
import { RemissionSupplier } from '../../models/remission-supplier';
export const remissionTargets: RemissionSupplier[] = [
{
id: 123,
name: 'Libri',
supplierNumber: 'abc-yyz',
},
{
id: 456,
name: 'Blank',
supplierNumber: '123-890',
},
];

View File

@@ -0,0 +1,9 @@
import { createRandomStringId, createRandomId } from '../util';
import { ShippingDocument } from '../../models/shipping-document';
export const shippingDocument: ShippingDocument = {
id: createRandomId(),
shippingDocumentNumber: createRandomStringId(),
products: [],
isCompleted: false,
};

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './mock-remission.service';
export * from './util';
// end:ng42.barrel

View File

@@ -0,0 +1,629 @@
import { Injectable } from '@angular/core';
import {
Observable,
of,
BehaviorSubject,
Subject,
merge,
combineLatest,
} from 'rxjs';
import { delay, distinctUntilChanged, map } from 'rxjs/operators';
import { createRandomStringId, createRandomId } from './util';
import {
product,
removeSourcePriceAndProductFromProduct,
} from './data/product';
import { RemissionService } from '../services/remission.service';
import { RemissionProcess } from '../models/remission-process';
import { RemissionProduct } from '../models/remission-product';
import {
remissionProducts,
createRemissionProduct,
} from './data/remission-products';
import { remissionProcessTemplateMock } from './data/remission-process-template';
import { ShippingDocument } from '../models/shipping-document';
import { shippingDocument } from './data/shipping-document';
import { RemissionFilter } from '../models/remission-filter';
import { Printer } from '../models/printer';
import { printers } from './data/remission-printers';
import { Product } from '../models/product';
import { RemissionSourceType } from '../types/remission-source.type';
import { Filter } from '../models/filter';
import { filterUeberlauf, filter } from './data/remission-filter';
import { RemissionPlacementType } from '../types/remission-placement-types';
import { remissionPlacementTypes } from './data/remission-placement-types';
import { remissionReasons } from './data/remission-reasons';
import { RemissionSupplier } from '../models/remission-supplier';
import { remissionTargets } from './data/remission-targets';
import { remissionSources } from './data/remission-sources';
import { ActionResult, CapacityType } from '../models';
@Injectable({ providedIn: 'root' })
export class MockRemissionService extends RemissionService {
private delayInMs = 100;
private remissionSubjectStore = new Map<
number,
BehaviorSubject<RemissionProcess>
>();
private remissionSubjectIdRef = new Map<number, number>();
private reloadProductsSubject = new Subject();
private productSubject = new BehaviorSubject<RemissionProduct[]>(
remissionProducts
);
private getRemissionFromStore = (
remissionProcessId: number
): BehaviorSubject<RemissionProcess> => {
const remission =
this.remissionSubjectStore.get(remissionProcessId) ||
this.remissionSubjectStore.get(
this.remissionSubjectIdRef.get(remissionProcessId)
);
return remission;
};
private saveRemissionInStore = (
remissionProcessId: number,
remissionSubject: BehaviorSubject<RemissionProcess>
) => {
this.remissionSubjectStore.set(remissionProcessId, remissionSubject);
};
createProcess(source?: RemissionProcess): Observable<RemissionProcess> {
const process = { ...remissionProcessTemplateMock, ...source };
const processSubject = new BehaviorSubject<RemissionProcess>(process);
this.saveRemissionInStore(process.id, processSubject);
return processSubject.pipe(distinctUntilChanged(), delay(this.delayInMs));
}
getRemissionProducts(params: {
remissionProcessId: number;
}): Observable<{
skip?: number;
take?: number;
hits?: number;
items: RemissionProduct[];
completed: boolean;
}> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
}
const process$ = merge(
processSubject,
this.reloadProductsSubject.pipe(map((_) => processSubject.value))
);
const products$ = this.productSubject.asObservable();
return combineLatest(process$, products$).pipe(
map(([process, products]) => {
// tslint:disable-next-line: no-shadowed-variable
const filter = process.filter;
const result: {
skip?: number;
take?: number;
hits?: number;
items: RemissionProduct[];
completed: boolean;
} = {
skip: filter.skip,
take: filter.take,
hits: 0,
items: [...products],
completed: false,
};
const productGroupFilter = filter.filter['421228ce'] || [];
if (productGroupFilter.length > 0) {
result.items = result.items.filter((product) =>
productGroupFilter.some((s) => product.productGroup === s)
);
}
const actionFilter = filter.filter['ce9ee0be'] || [];
result.hits = result.items.length;
result.items = result.items.slice(
result.skip,
result.skip + result.take
);
result.completed = result.skip + result.take >= result.hits;
return result;
})
);
}
startRemission(params: {
remissionProcessId: number;
config?: {
createShippingDocument?: boolean;
};
}): Observable<RemissionProcess> {
const processSubject = this.remissionSubjectStore.get(
params.remissionProcessId
);
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
}
const process = processSubject.value;
const id = createRandomId();
const doc: ShippingDocument = shippingDocument;
this.remissionSubjectIdRef.set(id, process.id);
processSubject.next({
...process,
id,
shippingDocuments: [doc],
});
return of(processSubject.value).pipe(delay(this.delayInMs));
}
continueProcess(source: RemissionProcess): Observable<RemissionProcess> {
let processSubject = this.getRemissionFromStore(source.id);
if (!processSubject) {
processSubject = new BehaviorSubject<RemissionProcess>(source);
}
this.saveRemissionInStore(processSubject.value.id, processSubject);
return processSubject.pipe(distinctUntilChanged(), delay(this.delayInMs));
}
continueProcessFromScan(containerId: string): Observable<RemissionProcess> {
return of();
}
updateRemissionFilter({
remissionProcessId,
changes,
}: {
remissionProcessId: number;
changes: Partial<RemissionFilter>;
}): Observable<RemissionFilter> {
const processSubject = this.getRemissionFromStore(remissionProcessId);
if (!!processSubject) {
const process = processSubject.value;
const nextProcess = {
...process,
filter: { ...process.filter, ...changes },
};
processSubject.next(nextProcess);
return of(nextProcess.filter).pipe(delay(this.delayInMs));
} else {
throw new Error('Prozess nicht gefunden');
}
}
completeRemission(params: {
remissionProcessId: number;
}): Observable<boolean> {
let processSubject = this.remissionSubjectStore.get(
params.remissionProcessId
);
if (!processSubject) {
const refId = this.remissionSubjectIdRef.get(params.remissionProcessId);
if (!!refId) {
processSubject = this.remissionSubjectStore.get(refId);
}
processSubject.next(processSubject.value);
processSubject.complete();
return of(false).pipe(delay(this.delayInMs));
} else {
return of(true).pipe(delay(this.delayInMs));
}
}
getRemission(params: {
remissionProcessId: number;
}): Observable<RemissionProcess> {
const remission = this.getRemissionFromStore(params.remissionProcessId);
if (!remission) {
throw new Error('Prozess nicht gefunden');
}
return remission.pipe(distinctUntilChanged(), delay(this.delayInMs));
}
isPrintingRequired({
remissionProcessId,
}: {
remissionProcessId: number;
}): Observable<boolean> {
const remission = this.getRemissionFromStore(remissionProcessId);
if (!remission) {
return of(false).pipe(delay(this.delayInMs));
}
switch (remission.value.filter.target.name) {
case 'Libri':
return of(true).pipe(delay(this.delayInMs));
case 'Blank':
return of(false).pipe(delay(this.delayInMs));
}
}
getPrinters(): Observable<Printer[]> {
return of(printers).pipe(delay(this.delayInMs));
}
printRemissionList(params: {
remissionProcessId: number;
printerKey: string;
}): Observable<boolean> {
const remission = this.getRemissionFromStore(params.remissionProcessId);
if (!remission) {
return of(false).pipe(delay(this.delayInMs));
}
return of(true).pipe(delay(this.delayInMs));
}
printShippingDocument(params: {
remissionProcessId: number;
shippingDocumentId?: number;
printerKey: string;
}): Observable<boolean> {
const remission = this.getRemissionFromStore(params.remissionProcessId);
if (!remission || !params.printerKey) {
return of(false).pipe(delay(this.delayInMs));
}
return of(true).pipe(delay(this.delayInMs));
}
searchProduct({ ean }: { ean: string }): Observable<Product> {
const searchResult = product;
return of(searchResult).pipe(delay(this.delayInMs));
}
getFilters(params: { remissionProcessId: number }): Observable<Filter[]> {
const remission = this.getRemissionFromStore(params.remissionProcessId);
const filterForGivenSourceType =
remission.value.filter.source === 'zentral' ? filter : filterUeberlauf;
return of(filterForGivenSourceType).pipe(delay(this.delayInMs));
}
getPlacementTypes(): Observable<RemissionPlacementType[]> {
return of(remissionPlacementTypes).pipe(delay(this.delayInMs));
}
getRemissionReasons(): Observable<string[]> {
return of(remissionReasons).pipe(delay(this.delayInMs));
}
getRemissionTargets(): Observable<RemissionSupplier[]> {
return of(remissionTargets).pipe(delay(this.delayInMs));
}
getRemissionSources(): Observable<RemissionSourceType[]> {
return of(remissionSources).pipe(delay(this.delayInMs));
}
getShippingDocuments(params: {
remissionProcessId: number;
shippingDocumentId: number;
}): Observable<ShippingDocument> {
const remissionSub = this.getRemissionFromStore(params.remissionProcessId);
return remissionSub.pipe(
map((remission) =>
remission.shippingDocuments.find(
(shippingDoc) => shippingDoc.id === params.shippingDocumentId
)
),
distinctUntilChanged(),
delay(this.delayInMs)
);
}
deleteProductFromRemissionList(input: {
remissionProcessId: number;
remissionProductId: number;
}): Observable<ActionResult<boolean>> {
return of({
error: false,
result: true,
});
}
addProductToRemit(input: {
product: Product;
remissionReason: string;
remissionQuantity: number;
}): Observable<RemissionProduct> {
const productToAdd = removeSourcePriceAndProductFromProduct({ ...product });
const newProduct = createRemissionProduct({
remissionReason: input.remissionReason,
remissionQuantity: input.remissionQuantity,
...productToAdd,
});
const newProductSubject = [...this.productSubject.value, newProduct];
this.productSubject.next(newProductSubject);
return of(newProduct).pipe(delay(this.delayInMs));
}
addProductToShippingDocument(params: {
remissionProcessId: number;
remissionProductId: number;
shippingDocumentId: number;
placementType?: RemissionPlacementType;
quantity?: number;
inStock?: number;
}): Observable<ActionResult<boolean>> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
const process = processSubject.value;
const products = this.productSubject.value;
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
}
let productToAdd = products.find(
(product) => product.id === params.remissionProductId
);
let newProducts = products.filter(
(product) => product.id !== params.remissionProductId
);
if (!!params.placementType || !!params.quantity) {
const productToUpdateInProductList = {
...productToAdd,
remissionQuantity: productToAdd.remissionQuantity - params.quantity,
};
newProducts =
productToAdd.remissionQuantity - params.quantity < 1
? newProducts
: [...newProducts, productToUpdateInProductList];
productToAdd = {
...productToAdd,
remissionQuantity: Number(params.quantity),
};
}
const shippingDocumentToUpdate = process.shippingDocuments.find(
(document) => document.id === params.shippingDocumentId
);
const updatedShippingDocument = {
...shippingDocumentToUpdate,
products: [...shippingDocumentToUpdate.products, productToAdd],
};
const updatedProcess = {
...process,
shippingDocuments: [
...process.shippingDocuments.filter(
(document) => document.id !== params.shippingDocumentId
),
updatedShippingDocument,
],
};
processSubject.next(updatedProcess);
this.productSubject.next(newProducts);
return of<ActionResult<boolean>>({
error: false,
result: true,
}).pipe(delay(this.delayInMs));
}
removeProductFromShippingDocument(params: {
remissionProcessId: number;
shippingDocumentId: number;
remissionProductId: number;
placementType?: string;
}): Observable<boolean> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
const process = processSubject.value;
const products = this.productSubject.value;
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
}
const shippingDocumentToUpdate = process.shippingDocuments.find(
(document) => document.id === params.shippingDocumentId
);
const updatedShippingDocument = {
...shippingDocumentToUpdate,
products: shippingDocumentToUpdate.products.filter(
(product) => product.id !== params.remissionProductId
),
};
const productToRemove = shippingDocumentToUpdate.products.find(
(product) => product.id === params.remissionProductId
);
const productInProductList = products.find(
(product) => product.id === params.remissionProductId
);
const updatedProductInProductList = !!productInProductList
? {
...productInProductList,
remissionQuantity:
Number(productInProductList.remissionQuantity) +
Number(productToRemove.remissionQuantity),
}
: productToRemove;
const updatedProcess = {
...process,
shippingDocuments: [
...process.shippingDocuments.filter(
(document) => document.id !== params.shippingDocumentId
),
updatedShippingDocument,
],
};
const updatedProducts = [
...this.productSubject.value.filter(
(product) => product.id !== updatedProductInProductList.id
),
updatedProductInProductList,
];
processSubject.next(updatedProcess);
this.productSubject.next(updatedProducts);
return of(true).pipe(delay(this.delayInMs));
}
reloadProducts(): void {
this.reloadProductsSubject.next();
}
createShippingDocument(params: {
remissionProcessId: number;
}): Observable<ShippingDocument> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
}
const process = processSubject.value;
const newShippingDocument: ShippingDocument = {
...shippingDocument,
id: createRandomId(),
shippingDocumentNumber: createRandomStringId(),
};
const updatedShippingDocuments = [
...process.shippingDocuments,
newShippingDocument,
];
const updatedProcess = {
...process,
shippingDocuments: updatedShippingDocuments,
};
processSubject.next(updatedProcess);
return of(newShippingDocument).pipe(delay(this.delayInMs));
}
completeShippingDocument(params: {
remissionProcessId: number;
shippingDocumentId: number;
containerId: string;
}): Observable<ActionResult<boolean>> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
}
const process = processSubject.value;
const shippingContainerToUpdate = process.shippingDocuments.find(
(document) => document.id === params.shippingDocumentId
);
const updatedShippingContainer = {
...shippingContainerToUpdate,
isCompleted: true,
};
const updatedProcess = {
...process,
shippingDocuments: [
...process.shippingDocuments.filter(
(document) => document.id !== params.shippingDocumentId
),
updatedShippingContainer,
],
};
processSubject.next(updatedProcess);
return of({
error: false,
message: '',
}).pipe(delay(this.delayInMs));
}
completeRemissions(params: {
remissionProcessId: number;
}): Observable<boolean> {
return;
}
getAllRemissions(params: {
showOpen?: boolean;
showOpenLastWeekOnly?: boolean;
showCompleted?: boolean;
showCompletedLastWeekOnly?: boolean;
receiptNumber?: string;
returnGroup?: number;
take?: number;
}): Observable<RemissionProcess[]> {
return;
}
getUncompletedRemissions(): Observable<RemissionProcess[]> {
return of([]);
}
deleteRemission(): Observable<boolean> {
return of(true);
}
deleteShippingDocument(): Observable<
ActionResult<{ deleted: boolean; completedRemissionsExist: boolean }>
> {
return of({
result: {
deleted: true,
completedRemissionsExist: false,
},
});
}
getAllShippingDocuments(): Observable<ShippingDocument[]> {
return;
}
getCapacities(params: {
selectedFilters?: { [filterId: string]: string[] };
supplierId: number;
}): Observable<CapacityType[]> {
return of([]);
}
}

View File

@@ -0,0 +1,7 @@
export function createRandomId(): number {
return Math.floor(Math.random() * Math.pow(10, 10));
}
export function createRandomStringId(): string {
return createRandomId().toString();
}

View File

@@ -0,0 +1,11 @@
import { StringDictionary } from '@cmf/core';
export interface ActionResult<T> {
error?: boolean;
errorReasons?: StringDictionary<string>;
http?: {
code: number;
};
message?: string;
result?: T;
}

View File

@@ -0,0 +1,21 @@
export interface CapacityType {
/**
* Kapazitätsbezeichnung / Name (z.B. Leistungsplatz)
*/
name: string;
/**
* Kapazitätsauslastung (abs.)
*/
utilized: number;
/**
* Verfügbare Kapazität
*/
available: number;
/**
* Kapazitätsname (z.B. Titel)
*/
label: string;
}

View File

@@ -0,0 +1,43 @@
export interface Filter {
/**
* ID der Filterkategorie
*/
id: string;
/**
* Name der Filterkategorie
*/
name: string;
/**
* Liste der Filteroptionen (die Filteroptionen der jeweiligen Filterkategorie)
*/
options: FilterOption[];
/**
* Maximale Anzahl ausgewählter Filter Optionen
*/
max?: number;
}
export interface FilterOption {
/**
* ID der Filteroption
*/
id: string;
/**
* Name der Filteroption
*/
name: string;
/**
* Key der Filteroption
*/
key?: string;
/**
* Liste der Filteroptionen (die Filteroptionen der jeweiligen Filterkategorie)
*/
options?: FilterOption[];
}

View File

@@ -0,0 +1,14 @@
// start:ng42.barrel
export * from './action-result';
export * from './capacity-type';
export * from './filter';
export * from './printer';
export * from './product';
export * from './remission-filter';
export * from './remission-process';
export * from './remission-product';
export * from './remission-products-filter';
export * from './remission-supplier';
export * from './remission-selected-filters';
export * from './shipping-document';
// end:ng42.barrel

View File

@@ -0,0 +1,26 @@
export interface Printer {
/**
* Ist Drucker aktiv
*/
enabled: boolean;
/**
* Pfad des Druckers
*/
key: string;
/**
* Name des Druckers
*/
value: string;
/**
* Ist Drucker ausgewählt
*/
selected: boolean;
/**
* Beschreibung des Druckers
*/
description: string;
}

View File

@@ -0,0 +1,63 @@
import { PriceDTO, ProductDTO } from '@cmf/inventory-api';
export interface Product {
/**
* Produkt PK
*/
id?: number;
/**
* Autor(en)
*/
contributors?: string;
/**
* Bild Identifikationsnummber
*/
imageId?: string;
/**
* Name des Artikels
*/
name?: string;
/**
* Format des Buchs Kurzform (z.B: HC)
*/
format?: string;
/**
* Format des Buchs Langform (z.B: Buch (gebunden))
*/
formatDetail?: string;
/**
* Artikelnummer
*/
ean?: string;
/**
* Produktgruppe (z.B. 112)
*/
productGroup?: string;
/**
* Name der Produktgruppe (z.B. Romane)
*/
productGroupName?: string;
/**
* Artikel-Features (z.B. key: 'SOE')
*/
features: { key: string; name: string }[];
/**
* Produkt-Stammdaten
*/
sourceProduct?: ProductDTO;
/**
* Preis (VK)
*/
sourcePrice?: PriceDTO;
}

View File

@@ -0,0 +1,30 @@
import { RemissionSupplier } from './remission-supplier';
import { RemissionSourceType } from '../types/remission-source.type';
import { RemissionSelectedFilters } from './remission-selected-filters';
export interface RemissionFilter {
/**
* Liste aller Produktfilter
*/
filter?: RemissionSelectedFilters;
/**
* Artikel-Ursprung (zentrales Sortiment oder Überlauf)
*/
source: RemissionSourceType;
/**
* Remissions-Ziel / Lieferant (z.B. Libri)
*/
target: RemissionSupplier;
/**
* Skip
*/
skip: number;
/**
* Take
*/
take: number;
}

View File

@@ -0,0 +1,50 @@
import { CapacityType } from './capacity-type';
import { ShippingDocument } from './shipping-document';
import { RemissionFilter } from './remission-filter';
export interface RemissionProcess {
/**
* RemissionProcess PK
*/
id: number;
/**
* Group ID
*/
returnGroup: number;
/**
* Remissionsnummer
*/
externalId?: number;
/**
* PK Lager
*/
stockId: number;
/**
* Kapazitäts-Informationen (z.B. Leistungsplatz 2 von 5 Artikel)
*/
capacities: CapacityType[];
/**
* Liste aller Produktfilter
*/
filter: RemissionFilter;
/**
* Warenbegleitschein
*/
shippingDocuments?: ShippingDocument[];
/**
* Beginn der Retourenerstellung / Remission
*/
startDate?: string;
/**
* Bereits Abgeschlossen
*/
completed?: boolean;
}

View File

@@ -0,0 +1,115 @@
import { RemissionPlacementType } from '../types/remission-placement-types';
import { RemissionSupplier } from './remission-supplier';
export interface RemissionProduct {
/**
* Produkt PK
*/
id: number;
/**
* Bild Identifikationsnummber
*/
imageId: string;
/**
* Autor(en)
*/
contributors: string;
/**
* Name des Artikels
*/
name: string;
/**
* Format des Buchs Kurzform (z.B: HC)
*/
format: string;
/**
* Format des Buchs Langform (z.B: Buch (gebunden))
*/
formatDetail: string;
/**
* Artikelnummer
*/
ean: string;
/**
* Preis des Artikels
*/
price: number;
/**
* Währeng des Artikelpreis
*/
currency: string;
/**
* Produktgruppe (z.B. 112)
*/
productGroup: string;
/**
* Name der Produktgruppe (z.B. Romane)
*/
productGroupName: string;
/**
* Aktueller Bestand des Artikels
*/
inStock: number;
/**
* Remi-Menge
*/
remissionQuantity: number;
/**
* Aktueller Bestand minus Remi-Menge
*/
remainingQuantity: number;
/**
* Artikel-Platzierung (z.B. Stapelplatz)
*/
placementType: RemissionPlacementType;
/**
* Grund für Remissions (Remi-Grund)
*/
remissionReason: string;
/**
* Ausgabe / Edition
*/
edition: string;
/**
* Artikel-Features (z.B. key: 'SOE')
*/
features: { key: string; name: string }[];
/**
* Produktnummer des internen Warenkatalogs
*/
catalogProductNumber?: string;
/**
* Restmenge (=Teilmenge des Artikels auf Warenbegleitschein)
*/
isResidual?: boolean;
/**
* Remissions-Ziel / Lieferant (z.B. Libri)
*/
target?: RemissionSupplier;
/**
* Wurde das Produkt manuell zur Remissionsliste hinzugefügt
*/
isManuallyAdded?: boolean;
}

View File

@@ -0,0 +1,31 @@
import { RemissionSupplier } from './remission-supplier';
import { CapacityType } from './capacity-type';
import { Filter } from './filter';
import { RemissionSourceType } from '../types/remission-source.type';
export interface RemissionProductsFilter {
/**
* PK Lager
*/
stockId: number;
/**
* Artikel-Ursprung (zentrales Sortiment oder Überlauf)
*/
source: RemissionSourceType;
/**
* Remissions-Ziel / Lieferant (z.B. Libri)
*/
target: RemissionSupplier;
/**
* Kapazitäts-Informationen (z.B. Leistungsplatz 2 von 5 Artikel)
*/
capacities: CapacityType[];
/**
* Liste aller Produktfilter
*/
filter: Filter[];
}

View File

@@ -0,0 +1,10 @@
export interface RemissionSelectedFilters {
ueberlauf?: {
Blank?: { [filterId: string]: string[] };
ZL?: { [filterId: string]: string[] };
};
zentral?: {
Blank?: { [filterId: string]: string[] };
ZL?: { [filterId: string]: string[] };
};
}

View File

@@ -0,0 +1,16 @@
export interface RemissionSupplier {
/**
* PK
*/
id: number;
/**
* Name des Lieferanten (z.B. ZL)
*/
name: string;
/**
* Lieferantennummer
*/
supplierNumber: string;
}

View File

@@ -0,0 +1,35 @@
import { RemissionProduct } from './remission-product';
export interface ShippingDocument {
/**
* ShippingDocument ID
*/
id?: number;
/**
* Warenbegleitscheinnummer
*/
shippingDocumentNumber?: string;
/**
* Liste aller Produkte auf dem Warenbegleitschein
*/
products?: RemissionProduct[];
/**
* Status des Wannenbegleitschein
* True = Wanne ist abgeschlossen
*/
isCompleted?: boolean;
/**
* Wurde die Wanne bereits gelöscht
* True = Wanne ist gelöscht
*/
isDeleted?: boolean;
/**
* Wannennummer (Paketnummer)
*/
packageNumber?: string;
}

View File

@@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
@Injectable()
export class RemissionModuleOptions {
/**
* Sollen Testdaten (Services) verwendet werden
*/
useMock?: boolean;
endpoints?: {
catsearch?: string;
remi?: string;
print?: string;
};
}

View File

@@ -0,0 +1,142 @@
import { RemissionModuleOptions } from './remission-module.options';
import { CatsearchApiRequestOptions } from '@isa/catsearch-api';
import { RemiApiRequestOptions } from '@isa/remi-api';
import { PrintApiRequestOptions } from '@isa/print-api';
import { NgModule, ModuleWithProviders } from '@angular/core';
import { RestRemissionService } from './services/rest-remission.service';
import { MockRemissionService } from './mock/mock-remission.service';
import { Mapper } from './mappings/mapper';
import { ReturnSuggestionDtoToRemissionProductMapping } from './mappings/return-suggestion-dto-to-remission-product.mapping';
import { ItemDtoToProductMapping } from './mappings/item-dto-to-product.mapping';
import { ProductToPriceDTOMapping } from './mappings/product-to-price-dto';
import { ProductToProductDTOMapping } from './mappings/product-to-product-dto';
import { ReturnItemDtoToRemissionProductMapping } from './mappings/return-item-dto-to-remission-product.mapping';
import { ReceiptItemDtoToRemissionProductMapping } from './mappings/receipt-item-dto-to-remission-product.mapping';
import { ReceiptDTOToShippingDocumentMapping } from './mappings/receipt-dto-to-shipping-document.mapping';
import { RemissionService } from './services/remission.service';
import { BaseUrlInterceptor, CatchResponseArgsErrorInterceptor, LogErrorInterceptor } from '@cmf/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { FeaturesToAssortmentMapping } from './mappings/features-to-assortment.mapping';
import { SupplierDtoToRemissionSupplier } from './mappings/supplier-dto-to-remssion-supplier.mapping';
export function catsearchApiRequestOptionsFactory(options: RemissionModuleOptions): CatsearchApiRequestOptions {
return {
baseUrl: (options.endpoints && options.endpoints.catsearch) || '',
catchResponseArgsError: true,
};
}
export function remiApiRequestOptionsFactory(options: RemissionModuleOptions): RemiApiRequestOptions {
return {
baseUrl: (options.endpoints && options.endpoints.remi) || '',
catchResponseArgsError: true,
};
}
export function printApiRequestOptionsFactory(options: RemissionModuleOptions): PrintApiRequestOptions {
return {
baseUrl: (options.endpoints && options.endpoints.print) || '',
catchResponseArgsError: true,
};
}
export function remissionServiceFactory(options: RemissionModuleOptions, rest: RestRemissionService, mock: MockRemissionService) {
return options.useMock ? mock : rest;
}
@NgModule({
declarations: [],
imports: [],
exports: [],
})
export class RemissionModule {
static forRoot(options: RemissionModuleOptions): ModuleWithProviders {
return {
ngModule: RemissionModule,
providers: [
{
provide: RemissionModuleOptions,
useValue: options,
},
{
provide: CatsearchApiRequestOptions,
useFactory: catsearchApiRequestOptionsFactory,
deps: [RemissionModuleOptions],
},
{
provide: RemiApiRequestOptions,
useFactory: remiApiRequestOptionsFactory,
deps: [RemissionModuleOptions],
},
{
provide: PrintApiRequestOptions,
useFactory: printApiRequestOptionsFactory,
deps: [RemissionModuleOptions],
},
{
provide: Mapper,
useClass: ReturnSuggestionDtoToRemissionProductMapping,
multi: true,
},
{
provide: Mapper,
useClass: ItemDtoToProductMapping,
multi: true,
},
{
provide: Mapper,
useClass: ProductToPriceDTOMapping,
multi: true,
},
{
provide: Mapper,
useClass: ProductToProductDTOMapping,
multi: true,
},
{
provide: Mapper,
useClass: ReturnItemDtoToRemissionProductMapping,
multi: true,
},
{
provide: Mapper,
useClass: ReceiptItemDtoToRemissionProductMapping,
multi: true,
},
{
provide: Mapper,
useClass: ReceiptDTOToShippingDocumentMapping,
multi: true,
},
{
provide: Mapper,
useClass: FeaturesToAssortmentMapping,
multi: true,
},
{
provide: Mapper,
useClass: SupplierDtoToRemissionSupplier,
multi: true,
},
{
provide: RemissionService,
useFactory: remissionServiceFactory,
deps: [RemissionModuleOptions, RestRemissionService, MockRemissionService],
},
{
provide: HTTP_INTERCEPTORS,
useClass: BaseUrlInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: CatchResponseArgsErrorInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: LogErrorInterceptor,
multi: true,
},
],
};
}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './rest-filter.service';
export * from './rest-remission-products.service';
// end:ng42.barrel

View File

@@ -0,0 +1,127 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { StringDictionary, InputDTO, OptionDTO } from '@cmf/core';
import { RemiService, FilterParams } from '@isa/remi-api';
import { map, shareReplay } from 'rxjs/operators';
import { FilterOption, Filter } from '../models/filter';
import { RemissionSourceType } from '../types/remission-source.type';
import { RemissionFilter, RemissionSelectedFilters } from '../models';
@Injectable({ providedIn: 'root' })
export class RestFilterService {
private cachedFilters$ = new Map<string, Observable<InputDTO[]>>();
constructor(private remiService: RemiService) {}
mapOptionToFilterOption(option: OptionDTO): FilterOption {
const result = {
id: option.value,
key: option.key,
name: option.label,
options: !!option.values && option.values.length > 0 ? option.values.map((i) => this.mapOptionToFilterOption(i)) : undefined,
};
return result;
}
getFilter(params: { remissionSourceType: RemissionSourceType; supplierId: number; stockId: number }): Observable<Filter[]> {
return this.getRestFilter({
remissionSourceType: params.remissionSourceType,
supplierId: params.supplierId,
stockId: params.stockId,
}).pipe(
map((response) =>
response.map((input) => ({
id: input.key,
key: input.key,
name: input.label,
max: input.options.max,
options: input.options.values.map((option) => this.mapOptionToFilterOption(option)),
}))
)
);
}
getRestFilter(params: { remissionSourceType: RemissionSourceType; supplierId: number; stockId: number }): Observable<InputDTO[]> {
const cacheIdentifier = params.remissionSourceType + params.supplierId;
if (!this.cachedFilters$.get(cacheIdentifier)) {
this.cachedFilters$.set(cacheIdentifier, this.getFiltersFromService(params).pipe(shareReplay()));
}
return this.cachedFilters$
.get(cacheIdentifier)
.pipe(map((result) => this.getSourceTypeSpecificFilters(result, params.remissionSourceType)));
}
createQueryRemiTokenFilter(params: {
remissionSourceType: RemissionSourceType;
remissionTargetName: string;
stockId: number;
filter: RemissionFilter;
}): Observable<StringDictionary<string>> {
const queryTokenFilter: StringDictionary<string> = {};
const filter =
params.filter.filter[params.remissionSourceType] && params.filter.filter[params.remissionSourceType][params.remissionTargetName];
if (!filter) {
return of({});
}
for (const key in filter) {
if (filter.hasOwnProperty(key) && Array.isArray(filter[key])) {
const values = filter[key].filter((value) => !!value);
queryTokenFilter[key] = values.join(';');
}
}
return of(queryTokenFilter);
}
getSourceTypeSpecificFilters(inputs: InputDTO[], remissionSourceType: RemissionSourceType): InputDTO[] {
return remissionSourceType === 'zentral' ? inputs : inputs.filter((option) => option.key !== 'aktionen');
}
private getFiltersFromService(params: {
remissionSourceType: RemissionSourceType;
supplierId: number;
stockId: number;
}): Observable<InputDTO[]> {
let filter$: Observable<InputDTO[]>;
const filterQuery: FilterParams = {
stockId: params.stockId,
supplierId: params.supplierId,
};
if (params.remissionSourceType === 'zentral') {
filter$ = this.remiService.getPflichtremissionFilter(filterQuery).pipe(map((response) => response.result));
} else {
filter$ = this.remiService.getUeberlaufFilter(filterQuery).pipe(map((response) => response.result));
}
return filter$;
}
checkFilterValidity(filter: RemissionSelectedFilters): boolean {
const isValidRemissionSelectedFilter = filter.hasOwnProperty('zentral') && filter.hasOwnProperty('ueberlauf');
return isValidRemissionSelectedFilter;
}
resetRemissionSelectedFilters(): RemissionSelectedFilters {
return {
ueberlauf: {
Blank: {},
ZL: {},
},
zentral: {
Blank: {},
ZL: {},
},
};
}
getSelectedFilters(filter: RemissionFilter): { [filterId: string]: string[] } {
return (filter.filter && filter.filter[filter.source] && filter.filter[filter.source][filter.target.name]) || {};
}
}

View File

@@ -0,0 +1,333 @@
import { Injectable } from '@angular/core';
import { Observable, combineLatest, BehaviorSubject, forkJoin, of } from 'rxjs';
import { RemiService, ReturnApiService } from '@isa/remi-api';
import { RestFilterService } from './rest-filter.service';
import {
switchMap,
map,
flatMap,
mergeMap,
startWith,
take,
shareReplay,
} from 'rxjs/operators';
import { filter as rxjsFilter } from 'rxjs/operators';
import { MappingService } from '../mappings/mapping.service';
import { Mapper } from '../mappings/mapper';
import { ReturnSuggestionDTO, ReturnItemDTO } from '@cmf/inventory-api';
import { RemissionFilter } from '../models/remission-filter';
import { RemissionProduct } from '../models/remission-product';
import { KeyValueDTO } from '@cmf/core';
@Injectable({ providedIn: 'root' })
export class RestRemissionProductsService {
constructor(
private remiService: RemiService,
private returnApi: ReturnApiService,
private restFilterService: RestFilterService,
private mapping: MappingService
) {}
// Store priority products for supplierId
priorityProducts = new Map<number, BehaviorSubject<number[]>>([
[1, new BehaviorSubject<number[]>([])],
[2, new BehaviorSubject<number[]>([])],
]);
productGroupNames$: Observable<KeyValueDTO<string, string>[]>;
// Products that should be shown on top right after adding to remission list
updatePriorityProducts(productId: number, supplierId: number) {
const applicablePriorityProducts$ = this.priorityProducts.get(supplierId);
if (!applicablePriorityProducts$) {
return;
}
applicablePriorityProducts$.next([
productId,
...applicablePriorityProducts$.value,
]);
}
// Delete priority products
deletePriorityProducts() {
this.priorityProducts = new Map<number, BehaviorSubject<number[]>>([
[1, new BehaviorSubject<number[]>([])],
[2, new BehaviorSubject<number[]>([])],
]);
}
// Products that need to be removed from priority list
removePriorityProducts(productId: number, supplierId: number) {
const applicablePriorityProducts$ = this.priorityProducts.get(supplierId);
if (!applicablePriorityProducts$) {
return;
}
applicablePriorityProducts$.next(
applicablePriorityProducts$.value.filter((id) => productId !== id)
);
// TODO Only temporary until applicable supplierId is known
const tempSupplierId = supplierId === 1 ? 2 : 1;
const applicablePriorityProducts2$ = this.priorityProducts.get(
tempSupplierId
);
applicablePriorityProducts2$.next(
applicablePriorityProducts$.value.filter((id) => productId !== id)
);
}
getProductGroupNames(
stockId: number
): Observable<KeyValueDTO<string, string>[]> {
if (!this.productGroupNames$) {
this.productGroupNames$ = this.remiService
.productgroups({ stockId })
.pipe(map((response) => response.result))
.pipe(shareReplay());
}
return this.productGroupNames$;
}
getRemissionproducts({
filter,
stockId,
}: {
filter: RemissionFilter;
stockId: number;
}) {
let allProducts$: Observable<{
skip?: number;
take?: number;
hits?: number;
items: RemissionProduct[];
completed: boolean;
}>;
const filter$ = this.restFilterService.createQueryRemiTokenFilter({
remissionSourceType: filter.source,
remissionTargetName: filter.target.name,
stockId,
filter,
});
if (filter.source === 'ueberlauf') {
const mapper = this.mapping.get<
Mapper<ReturnSuggestionDTO, RemissionProduct>
>('ReturnSuggestionDTO', 'RemissionProduct');
const regularProducts$ = filter$.pipe(
switchMap((qtFilter) =>
this.remiService.ueberlauf({
queryToken: {
stockId,
supplierId: filter.target.id,
filter: qtFilter,
skip: filter.skip,
take: filter.take,
},
})
)
);
allProducts$ = regularProducts$.pipe(
take(1),
map((regular) => ({
skip: regular.skip,
take: regular.take,
hits: regular.hits,
completed:
regular.completed || regular.skip + regular.take > regular.hits,
items: [...regular.result].map((item) => mapper.map(item)),
}))
);
} else if (filter.source === 'zentral') {
const mapper = this.mapping.get<Mapper<ReturnItemDTO, RemissionProduct>>(
'ReturnItemDTO',
'RemissionProduct'
);
const applicablePriorityProducts$ = this.priorityProducts.get(
filter.target.id
);
const showPriorityProducts = (priorityProductIds) => {
if (!priorityProductIds.length) {
return false;
}
if (!filter.filter || !filter.filter.zentral) {
return true;
}
const filterToCheck = filter.filter.zentral;
const filterGroups = Object.keys(filterToCheck[filter.target.name]);
let isFilterSet = false;
for (const group of filterGroups) {
if (!!filterToCheck[filter.target.name][group].length) {
isFilterSet = true;
}
}
return !isFilterSet;
};
const priorityItems$ = applicablePriorityProducts$.pipe(
switchMap((ids) => (!!showPriorityProducts(ids) ? of(ids) : of([]))),
mergeMap((ids) =>
forkJoin(
ids.map((id) => this.returnApi.getReturnItemById({ itemId: id }))
)
),
map((products) => products.map((product) => product.result)),
startWith([]),
map((products) => products.slice(filter.skip))
);
const regularProducts$ = filter$.pipe(
switchMap((qtFilter) =>
this.remiService.pflichtremissionsartikel({
queryToken: {
stockId,
supplierId: filter.target.id,
filter: qtFilter,
skip: filter.skip,
take: filter.take,
},
})
)
);
allProducts$ = combineLatest([regularProducts$, priorityItems$]).pipe(
map(([regular, priority]) => ({
skip: regular.skip,
take: regular.take,
hits: regular.hits,
completed:
regular.completed || regular.skip + regular.take > regular.hits,
items: [...priority, ...regular.result].map((item) =>
mapper.map(item)
),
}))
);
}
// Setzen der productGroup im RemissionProduct
const result1$ = allProducts$.pipe(
flatMap((products) => {
const itemsWithGroupnames$ = this.addGroupnameToProducts(
products.items,
stockId
);
return itemsWithGroupnames$.pipe(
map((items) => ({
...products,
items,
}))
);
})
);
// Setzen des Lagerbestands im Remissionproduct
const result2$ = result1$.pipe(
flatMap((result) =>
!result.items.length
? of({ items: [], completed: true })
: this.remiService
.inStock({
stockId,
articleIds:
result.items &&
result.items
.filter((item) => !!item.catalogProductNumber)
.map((product) => Number(product.catalogProductNumber)),
})
.pipe(
rxjsFilter((stocks) => !stocks.error),
map((stocks) => {
return {
...result,
items: result.items.map((item) => {
const stock = stocks.result.find(
(info) => info.itemId === +item.catalogProductNumber
);
if (!stock) {
const defaultStockData = {
inStock: 0,
remainingQuantity: 0,
remissionQuantity: item.remissionQuantity || 0,
};
return { ...item, ...defaultStockData };
}
// Available stock after stock in active remission
const availableStock =
stock.inStock - stock.removedFromStock;
const remainingStock =
availableStock < 0 ? 0 : availableStock;
return {
...item,
inStock: remainingStock,
remainingQuantity:
typeof item.remainingQuantity === 'number'
? item.remainingQuantity
: remainingStock - (item.remissionQuantity || 0) >=
0
? remainingStock - (item.remissionQuantity || 0)
: 0,
remissionQuantity:
typeof item.remissionQuantity === 'number'
? item.remissionQuantity
: remainingStock - (item.remainingQuantity || 0),
};
}),
};
})
)
),
map((products) => {
products.items = products.items.filter(
(item) => !!item && item.inStock !== (undefined || null)
);
return products;
})
);
return result2$;
}
addGroupnameToProducts(
products: RemissionProduct[],
stockId: number
): Observable<RemissionProduct[]> {
if (!products.length) {
return of(products);
}
const productGroupNames$ = this.getProductGroupNames(stockId);
return productGroupNames$.pipe(
map((groupNames) =>
products.map((product) => {
const proudctGroupNameMatch = groupNames.find(
(name) => name.key === product.productGroup
);
return {
...product,
productGroupName: proudctGroupNameMatch
? proudctGroupNameMatch.value
: '',
} as RemissionProduct;
})
)
);
}
}

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './process-store.service';
export * from './remission.service';
export * from './rest-remission.service';
// end:ng42.barrel

View File

@@ -0,0 +1,39 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { RemissionProcess } from '../models/remission-process';
import { compare } from '../utils/compare';
@Injectable({ providedIn: 'root' })
export class ProcessStoreService {
private readonly store: Map<number, BehaviorSubject<RemissionProcess>> = new Map();
constructor() {}
set(process: RemissionProcess) {
let processSub: BehaviorSubject<RemissionProcess> = this.get(process.id);
if (processSub instanceof BehaviorSubject) {
if (!compare(processSub.value, process)) {
processSub.next(process);
}
} else {
processSub = new BehaviorSubject(process);
this.store.set(process.id, processSub);
}
return processSub;
}
get(processId: number) {
return this.store.get(processId);
}
remove(processId: number) {
const processSub = this.get(processId);
if (processSub instanceof BehaviorSubject) {
processSub.complete();
this.store.delete(processId);
}
}
}

View File

@@ -0,0 +1,323 @@
import { Observable } from 'rxjs';
import { RemissionProcess } from '../models/remission-process';
import { RemissionProduct } from '../models/remission-product';
import { RemissionFilter } from '../models/remission-filter';
import { Printer } from '../models/printer';
import { Product } from '../models/product';
import { RemissionSourceType } from '../types/remission-source.type';
import { Filter } from '../models/filter';
import { RemissionPlacementType } from '../types/remission-placement-types';
import { RemissionSupplier } from '../models/remission-supplier';
import { ShippingDocument } from '../models/shipping-document';
import { ActionResult, CapacityType } from '../models';
export abstract class RemissionService {
/**
* Notwendige Informationen für View (Start-Screen) und Starten einer Remission
* @returns Remission Prozess Default Settings
*/
abstract createProcess(
source?: RemissionProcess
): Observable<RemissionProcess>;
/**
* Wiederaufnahme eines vorhandenen Prozesses
*/
abstract continueProcess(
source: RemissionProcess
): Observable<RemissionProcess>;
/**
* Wiederaufnahme eines vorhandenen Prozesses anhand der PachstückId
*/
abstract continueProcessFromScan(
containerId: string
): Observable<RemissionProcess>;
/**
* Starten einer Remission
* @param params.remissionProcessId Remissionsnummer
* @returns Remissionsprozess
*/
abstract startRemission(params: {
remissionProcessId: number;
createShippingDocument?: boolean;
}): Observable<RemissionProcess>;
/**
* Remission abschließen
* @param params.remissionProcessId Remissionsnummer
* @returns Ergebnis des Remissionsabschlusses
*/
protected abstract completeRemission(params: {
remissionProcessId: number;
}): Observable<boolean>;
/**
* Alle Remissionen abschließen
* @param params.remissionProcessId Remissionsnummer
* @returns Ergebnis des Remissionsabschlusses
*/
abstract completeRemissions(params: {
remissionProcessId: number;
}): Observable<boolean>;
/**
* Abruf einer Remission
* @param params.remissionProcessId Remissionsnummer
* @returns Remissionsprozess
*/
abstract getRemission(params: {
remissionProcessId: number;
}): Observable<RemissionProcess>;
/**
* Gibt anhand der remissionprocessId und der darin
* definierten Einstellungen die Produktliste zurück
* @param params param.remissionProcessId
* @param params param.skip
* @param params param.take
* @returns Produktliste und Metainformationen (skip, take, completed)
*/
abstract getRemissionProducts(params: {
remissionProcessId: number;
}): Observable<{
skip?: number;
take?: number;
hits?: number;
items: RemissionProduct[];
completed: boolean;
}>;
/**
* Aktualisieren der Remissions-Filter
* @param changes.remissionProcessId Remissionsnummer
* @param changes.changes Remissionsfilter
* @returns Remissionsfilter
*/
abstract updateRemissionFilter(changes: {
remissionProcessId: number;
changes: Partial<RemissionFilter>;
}): Observable<RemissionFilter>;
/**
* Abfrage ob für Remission Druckauftrag erforderlich
* @param remissionProcessId Remissionsnummer
* @returns Ob physischer Druck nötig
*/
abstract isPrintingRequired(params: {
remissionProcessId: number;
}): Observable<boolean>;
/**
* Abfrage der verfügbaren Drucker
* @returns Liste der verfügbaren Drucker
*/
abstract getPrinters(): Observable<Printer[]>;
/**
* Druckauftrag für Remissionsliste
* @param remissionProcessId Remissionsnummer
* @param params.printerKey Name des Druckers
* @returns Ob Druckauftrag ausgeführt wurde
*/
abstract printRemissionList(params: {
remissionProcessId: number;
printerKey: string;
}): Observable<boolean>;
/**
* Druckauftrag für Warenbegleitschein
* @param params.remissionProcessId Remissionsnummer
* @param params.shippingDocumentId ID des Warenbegleitscheins (default: alle Warenbegleitscheine)
* @param params.printerKey Name des Druckers
* @returns Ob Druckauftrag ausgeführt wurde
*/
abstract printShippingDocument(params: {
remissionProcessId: number;
shippingDocumentId?: number;
printerKey: string;
}): Observable<boolean>;
/**
* Produktsuche über EAN
* @param ean Artikelidentifikationsnummer
* @returns Produktinformation zur Artikelidentifikationsnummer
*/
abstract searchProduct(params: { ean: string }): Observable<Product>;
/**
* Anwendbare Filter für die Suche
* @param remissionSourceType Artikel-Ursprung (z.B. 'zentral')
* @returns Mögliche Filteroptionen (inkl. Sub-gruppen)
*/
abstract getFilters(params: {
remissionProcessId: number;
}): Observable<Filter[]>;
/**
* Arten der Produktplatzierung (z.B. Stapelplatz)
* @returns Mögliche Platzierungen eines Produkts
*/
abstract getPlacementTypes(): Observable<RemissionPlacementType[]>;
/**
* Gründe für Remission (z.B. beschädigtes Exemplar)
* @returns Mögliche Gründe für Remission
*/
abstract getRemissionReasons(): Observable<string[]>;
/**
* Remissions-Ziele / Lieferanten (z.B. Libri, Blank)
* @returns Mägliche Remissions-Ziele
*/
abstract getRemissionTargets(): Observable<RemissionSupplier[]>;
/**
* Artikel-Ursprung (z.B. zentrales Sortiment oder Überlauf)
* @returns Mögliche Artikel-Ursprünge
*/
abstract getRemissionSources(): Observable<RemissionSourceType[]>;
/**
* Abfrage der Kapazitätsauslastung
* @returns Kapazitätsauslastung
*/
abstract getCapacities(params: {
selectedFilters?: { [filterId: string]: string[] };
supplierId: number;
}): Observable<CapacityType[]>;
/**
* Abruf eines Warenbegleitscheins (Shipping Documents)
* @param params.remissionProcessId Remissionsnummer
* @param params.shippingDocumentId ID des Warenbegleitscheins
* @returns Warenbegleitscheins
*/
abstract getShippingDocuments(params: {
remissionProcessId: number;
shippingDocumentId: number;
}): Observable<ShippingDocument>;
/**
* Entfernen eiens Produkts von der Remissionsliste
* @param input.remissionProcessId Remissionsnummer
* @param input.product Remissionsprodukt
* @returns Ob Produkt entfernt werden konnte
*/
abstract deleteProductFromRemissionList(input: {
remissionProcessId: number;
remissionProductId: number;
}): Observable<ActionResult<boolean>>;
/**
* Produkt der Remissionsliste hinzufügen
* @param input.remissionProductId ID des hinzuzufügenden Produkts
* @param input.remissionReason Remissionsgrund
* @param input.remissionQuantity Anzahl der zur Remission hinzuzufügenden Exemplare
* @returns Produktinformation über Remissionsprodukt
*/
abstract addProductToRemit(input: {
product: Product;
remissionReason: string;
remissionQuantity: number;
}): Observable<RemissionProduct>;
/**
* Artikel zu Warenbegleitschein hinzufügen
* @param params.remissionProcessId Remissionsnummer
* @param params.remissionProductId ID des hinzuzufügenden Produkts
* @param params.shippingDocumentId ID des Warenbegleitscheins
* @param params.placementType Platzierungsart (z.B. Stapelplatz)
* @param params.quantity Anzahl der zu hinzuzufügenden Exemplare
* @returns Ob Artikel dem Warenbegleitschein hinzugefügt werden konnte
*/
abstract addProductToShippingDocument(params: {
remissionProcessId: number;
remissionProductId: number;
shippingDocumentId: number;
placementType?: string;
quantity?: number;
inStock?: number;
predefinedRemissionQuantity?: number;
}): Observable<ActionResult<boolean>>;
/**
* Artikel von Warenbegleitschein entfernen
* @param params.remissionProcessId Remissionsnummer
* @param params.shippingDocumentId ID des Warenbegleitscheins
* @param params.remissionProductId ID des entfernenden Produkts
* @param params.placementType Platzierungsart (default: alle)
* @returns Ob Artikel vom Warenbegleitschein entfernt werden konnte
*/
abstract removeProductFromShippingDocument(params: {
remissionProcessId: number;
remissionProductId: number;
shippingDocumentId: number;
placementType?: string;
}): Observable<boolean>;
/**
* Neuladen/Refresh der Remissions-Produkte (getRemission-Products Stream)
* Beispiel: CtA 'Filtern" in Filter
* @param params.remissionProcessId Remissionsnummer
*/
abstract reloadProducts(params: { remissionProcessId: number }): void;
/**
* Warenbegleitschein abschließen
* @param params.remissionProcessId Remissionsnummer
* @param params.shippingDocumentId ID des Warenbegleitscheins
* @param params.containerId Wannennummer
* @returns Ob Warenbegleitschein abgeschlossen werden konnte
*/
abstract completeShippingDocument(params: {
remissionProcessId: number;
shippingDocumentId: number;
containerId: string;
}): Observable<ActionResult<boolean>>;
/**
* Warenbegleitschein anlegen
* @param params.remissionProcessId Remissionsprozess
* @param params.shippingDocumentId PachstückId
* @returns Neu erstellten Warenbegleitschein
*/
abstract createShippingDocument(params: {
remissionProcessId: number;
}): Observable<ShippingDocument>;
/**
* Liste aller Remissionen
*/
abstract getAllRemissions(params: {
showOpen?: boolean;
showOpenLastWeekOnly?: boolean;
showCompleted?: boolean;
showCompletedLastWeekOnly?: boolean;
receiptNumber?: string;
returnGroup?: number;
take?: number;
}): Observable<RemissionProcess[]>;
/**
* Liste nicht abgeschlossener Remissionen
*/
abstract getUncompletedRemissions(): Observable<RemissionProcess[]>;
/**
* Remission abbrechen
*/
abstract deleteRemission(params: {
remissionProcessId: number;
externalId?: number;
}): Observable<boolean>;
abstract deleteShippingDocument(params: {
remissionProcessId: number;
shippingDocumentId: number;
externalId?: number;
}): Observable<
ActionResult<{ deleted: boolean; completedRemissionsExist: boolean }>
>;
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './remission-placement-types';
export * from './remission-source.type';
// end:ng42.barrel

View File

@@ -0,0 +1 @@
export type RemissionPlacementType = 'Leistung' | 'Stapel';

View File

@@ -0,0 +1 @@
export type RemissionSourceType = 'zentral' | 'ueberlauf';

View File

@@ -0,0 +1,6 @@
export function addDays(startDate: Date, daysToAdd: number): Date {
const baseDate = new Date(startDate);
const newDate = baseDate.setDate(startDate.getDate() + daysToAdd);
return new Date(newDate);
}

View File

@@ -0,0 +1,46 @@
export function compare<T>(val1: T, val2: T): boolean {
if (!val1 && !val2) {
return true;
}
if (!!val1 && !val2) {
return false;
}
if (typeof val1 !== typeof val2) {
return false;
}
if (Array.isArray(val1) && Array.isArray(val2)) {
if (val1.length !== val2.length) {
return false;
}
for (let index = 0; index < val1.length; index++) {
if (!compare(val1[index], val2[index])) {
return false;
}
}
return true;
}
if (typeof val1 === 'object') {
if (Object.keys(val1).length !== Object.keys(val2).length) {
return false;
}
for (const key in { ...val1, ...val2 }) {
if (val1.hasOwnProperty(key) && val2.hasOwnProperty(key)) {
if (!compare(val1[key], val2[key])) {
return false;
}
} else {
return false;
}
}
return true;
}
return val1 === val2;
}

View File

@@ -0,0 +1,11 @@
/*
* Public API Surface of remission
*/
export * from './lib/generators';
export * from './lib/mock';
export * from './lib/models';
export * from './lib/remission-module.options';
export * from './lib/remission.module';
export * from './lib/rest';
export * from './lib/services';
export * from './lib/types';

View File

@@ -0,0 +1,16 @@
// 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/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,26 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"lib",
"camelCase"
],
"component-selector": [
true,
"element",
"lib",
"kebab-case"
]
}
}

View File

@@ -5,7 +5,8 @@ This library was generated with [Angular CLI](https://github.com/angular/angular
## Code scaffolding
Run `ng generate component component-name --project native-container` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project native-container`.
> Note: Don't forget to add `--project native-container` or else it will be added to the default project in your `angular.json` file.
> Note: Don't forget to add `--project native-container` or else it will be added to the default project in your `angular.json` file.
## Build

View File

@@ -10,15 +10,15 @@ module.exports = function (config) {
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/native-container'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
@@ -27,6 +27,6 @@ module.exports = function (config) {
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
restartOnFileChange: true,
});
};

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