mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
161 Commits
fix/4550-H
...
scandit-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdc2553d73 | ||
|
|
05eb3cc756 | ||
|
|
6e1c434edf | ||
|
|
ed8e937924 | ||
|
|
1bd17fd887 | ||
|
|
8900a77d7a | ||
|
|
4b10dd96d9 | ||
|
|
1126e4f0c1 | ||
|
|
e9f24a88d6 | ||
|
|
d48680c59e | ||
|
|
1d472ce3df | ||
|
|
1d19779dac | ||
|
|
90e671d285 | ||
|
|
33fb44f20a | ||
|
|
8723f7aa7e | ||
|
|
03815586f7 | ||
|
|
86a11ff07a | ||
|
|
41be8533dc | ||
|
|
186afbc828 | ||
|
|
c3561339a9 | ||
|
|
5312073184 | ||
|
|
4dfe3bfa11 | ||
|
|
9b7a1b1c21 | ||
|
|
a290d3b249 | ||
|
|
ad348af551 | ||
|
|
f1bdba5d10 | ||
|
|
c4134e7f99 | ||
|
|
b7a16f5d30 | ||
|
|
4105709286 | ||
|
|
0c3b322fbd | ||
|
|
12096754c7 | ||
|
|
453d921a99 | ||
|
|
bad05fd098 | ||
|
|
363daf1e35 | ||
|
|
e0cb0974cf | ||
|
|
c3d9274766 | ||
|
|
bc16b841fb | ||
|
|
d5dc4e053d | ||
|
|
3c6833988c | ||
|
|
28fb4ebb48 | ||
|
|
2118bd996a | ||
|
|
8a6448cc17 | ||
|
|
f2c7d57ad6 | ||
|
|
9c9ddfaeec | ||
|
|
6eaa347de5 | ||
|
|
a16f355396 | ||
|
|
8b8db6e335 | ||
|
|
06e248d615 | ||
|
|
c68706b54f | ||
|
|
b271ce9711 | ||
|
|
94888213b1 | ||
|
|
1041d92486 | ||
|
|
43d8d220c9 | ||
|
|
e0993d9c46 | ||
|
|
82656d9b27 | ||
|
|
df36d0934d | ||
|
|
920b8eb8e3 | ||
|
|
4db28b1aa7 | ||
|
|
3a9820aa54 | ||
|
|
30ad99332e | ||
|
|
4b48275910 | ||
|
|
d3e3316459 | ||
|
|
4ef1bd4df6 | ||
|
|
0c2a23e5d2 | ||
|
|
36bd2c1eba | ||
|
|
a38d2eede6 | ||
|
|
ed7dc10246 | ||
|
|
f5251d9069 | ||
|
|
2bd21e168a | ||
|
|
3661bf7580 | ||
|
|
9f2a6633f7 | ||
|
|
3c4d0ea56c | ||
|
|
56bb784c83 | ||
|
|
c687570b1f | ||
|
|
afe5d3468a | ||
|
|
65f43d22ee | ||
|
|
67203a8506 | ||
|
|
92e522dedf | ||
|
|
fb46d329dc | ||
|
|
64d0a9fdb9 | ||
|
|
8f47163627 | ||
|
|
49f2a44461 | ||
|
|
a209d59ea9 | ||
|
|
03124d8736 | ||
|
|
a3330263f8 | ||
|
|
89092a5f6e | ||
|
|
42fa108bb6 | ||
|
|
2692588357 | ||
|
|
ec26b5f4c0 | ||
|
|
ff985bda64 | ||
|
|
ca255cb592 | ||
|
|
8df5052c76 | ||
|
|
c78ddb5c8c | ||
|
|
5d84b4a55a | ||
|
|
a6142a5d86 | ||
|
|
fdf50fe11e | ||
|
|
e8bf922a67 | ||
|
|
f202ff5291 | ||
|
|
0c25859b6b | ||
|
|
215cb89aff | ||
|
|
9256a79087 | ||
|
|
f1ff9c6c55 | ||
|
|
3f05e57554 | ||
|
|
2062bf3bab | ||
|
|
2d71a567ff | ||
|
|
547e615522 | ||
|
|
5d904e9d88 | ||
|
|
b7ccde4d44 | ||
|
|
b838f4c475 | ||
|
|
2bc97ee574 | ||
|
|
f054614cfe | ||
|
|
0aa1cddf72 | ||
|
|
d39521b9f2 | ||
|
|
f5468d7f8e | ||
|
|
4ad99270bd | ||
|
|
4e098ae962 | ||
|
|
8e00e646fb | ||
|
|
4fad5a7c2f | ||
|
|
5ece030ec8 | ||
|
|
54d7c525a9 | ||
|
|
41d4dc4663 | ||
|
|
c266c51572 | ||
|
|
4ea50f68d1 | ||
|
|
e5d61c8622 | ||
|
|
d06c64c08a | ||
|
|
91ebc3e27f | ||
|
|
d643c19642 | ||
|
|
afd1f5e302 | ||
|
|
4099aa0a57 | ||
|
|
ebe11b75d1 | ||
|
|
81f7270cf7 | ||
|
|
570a8800a0 | ||
|
|
25aecffafc | ||
|
|
7c48c63584 | ||
|
|
5bf32b2e72 | ||
|
|
f44fbe3fdb | ||
|
|
5df075f448 | ||
|
|
9cee33e286 | ||
|
|
42bf7e4120 | ||
|
|
77ff7ca1a8 | ||
|
|
7f195ee627 | ||
|
|
79bec55818 | ||
|
|
35093afaff | ||
|
|
358ba3963c | ||
|
|
d47e617f8c | ||
|
|
55bd001146 | ||
|
|
a9f11426a7 | ||
|
|
10b86756d2 | ||
|
|
262dd084c1 | ||
|
|
abc58c8a78 | ||
|
|
866cd23e41 | ||
|
|
fdcf12c022 | ||
|
|
432f1161af | ||
|
|
82dbce5744 | ||
|
|
a4b9f5fcf1 | ||
|
|
7ea9359c30 | ||
|
|
b9a4b0d315 | ||
|
|
7809e7a2b5 | ||
|
|
9a8c74b148 | ||
|
|
ad62e67771 | ||
|
|
6feb8079b7 |
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
||||
npm run pretty-quick
|
||||
4
TASKS.md
4
TASKS.md
@@ -1,4 +0,0 @@
|
||||
- Neue Icon Module (z.B. mit SVG sprites)
|
||||
- Breadcrumb Navigation (Neu)
|
||||
- Remissions Produkt Liste (Refactoring / Neu)
|
||||
- Angular Version (Upgrade)
|
||||
44
angular.json
44
angular.json
@@ -895,8 +895,7 @@
|
||||
"pdfjs-dist/build/pdf",
|
||||
"pdfjs-dist/web/pdf_viewer",
|
||||
"pdfjs-dist/es5/build/pdf",
|
||||
"pdfjs-dist/es5/web/pdf_viewer",
|
||||
"scandit-sdk"
|
||||
"pdfjs-dist/es5/web/pdf_viewer"
|
||||
],
|
||||
"outputPath": "dist/isa-app",
|
||||
"index": "apps/isa-app/src/index.html",
|
||||
@@ -912,7 +911,7 @@
|
||||
"apps/isa-app/src/manifest.webmanifest",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/scandit-sdk/build",
|
||||
"input": "node_modules/scandit-web-datacapture-barcode/build/engine",
|
||||
"output": "scandit"
|
||||
}
|
||||
],
|
||||
@@ -959,10 +958,10 @@
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "isa-app:build:production"
|
||||
"buildTarget": "isa-app:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "isa-app:build:development"
|
||||
"buildTarget": "isa-app:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
@@ -970,7 +969,7 @@
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "isa-app:build"
|
||||
"buildTarget": "isa-app:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
@@ -1470,39 +1469,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shell": {
|
||||
"projectType": "library",
|
||||
"root": "apps/shell",
|
||||
"sourceRoot": "apps/shell/src",
|
||||
"prefix": "shell",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||
"options": {
|
||||
"project": "apps/shell/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "apps/shell/tsconfig.lib.prod.json"
|
||||
},
|
||||
"development": {
|
||||
"tsConfig": "apps/shell/tsconfig.lib.json"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"tsConfig": "apps/shell/tsconfig.spec.json",
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
}
|
||||
|
||||
.scanner-container {
|
||||
width: 100vw;
|
||||
max-width: 95vw;
|
||||
max-height: calc(95vh - 120px);
|
||||
/* width: 100vw;
|
||||
height: 100vh;
|
||||
max-width: 100vh;
|
||||
max-height: 100vh; */
|
||||
}
|
||||
|
||||
.close-scanner {
|
||||
@apply block px-6 py-4 bg-white text-brand border-2 border-solid border-brand rounded-full text-lg font-bold mx-auto mt-4;
|
||||
@apply absolute bottom-12 left-[50%] -translate-x-[50%] block px-6 py-4 bg-white text-brand border-2 border-solid border-brand rounded-full text-lg font-bold mx-auto mt-4;
|
||||
@apply whitespace-nowrap;
|
||||
}
|
||||
|
||||
@screen desktop {
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
<div class="scanner-container" #scanContainer></div>
|
||||
<button class="close-scanner" type="button" (click)="close()">
|
||||
Scan abbrechen
|
||||
</button>
|
||||
<button class="close-scanner" type="button" (click)="close()">Scan abbrechen</button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy, ElementRef, ViewChild, NgZone, AfterViewInit, OnDestroy } from '@angular/core';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { Barcode, BarcodePicker, ScanResult, ScanSettings } from 'scandit-sdk';
|
||||
import { Component, ChangeDetectionStrategy, ElementRef, ViewChild, NgZone, AfterViewInit, OnDestroy, OnInit } from '@angular/core';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { BarcodeCapture, BarcodeCaptureSettings, Symbology } from 'scandit-web-datacapture-barcode';
|
||||
import { Camera, DataCaptureContext, DataCaptureView, FrameSourceState } from 'scandit-web-datacapture-core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-scandit-overlay',
|
||||
@@ -8,8 +9,11 @@ import { Barcode, BarcodePicker, ScanResult, ScanSettings } from 'scandit-sdk';
|
||||
styleUrls: ['scandit-overlay.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ScanditOverlayComponent implements AfterViewInit, OnDestroy {
|
||||
private _barcodePicker: BarcodePicker;
|
||||
export class ScanditOverlayComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private dataCaptureContext: DataCaptureContext;
|
||||
private dataCaptureView: DataCaptureView;
|
||||
private barcodeCapture: BarcodeCapture;
|
||||
private camera: Camera;
|
||||
|
||||
private _onScan?: (code: string) => void;
|
||||
|
||||
@@ -17,54 +21,61 @@ export class ScanditOverlayComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
@ViewChild('scanContainer', { read: ElementRef, static: true }) scanContainer: ElementRef;
|
||||
|
||||
constructor(private _zone: NgZone, private _modal: UiModalService) {}
|
||||
constructor(
|
||||
private _zone: NgZone,
|
||||
private _modal: UiModalService,
|
||||
) {}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.createBarcodePicker()
|
||||
.then(() => {
|
||||
this._barcodePicker.on('scan', (scanResult) => {
|
||||
this._zone.run(() => this.handleScanrResult(scanResult));
|
||||
ngOnInit(): void {
|
||||
this.dataCaptureView = new DataCaptureView();
|
||||
|
||||
this.dataCaptureView.connectToElement(this.scanContainer.nativeElement);
|
||||
|
||||
this.dataCaptureView.showProgressBar();
|
||||
}
|
||||
|
||||
async ngAfterViewInit() {
|
||||
this.dataCaptureContext = await DataCaptureContext.create();
|
||||
|
||||
this.dataCaptureView.setContext(this.dataCaptureContext);
|
||||
|
||||
this.barcodeCapture = await BarcodeCapture.forContext(this.dataCaptureContext, this.getScanSettings());
|
||||
|
||||
this.barcodeCapture.addListener({
|
||||
didScan: (_, session, __) => {
|
||||
this._zone.run(() => {
|
||||
const result = session.newlyRecognizedBarcode;
|
||||
|
||||
const code = result?.data ?? '';
|
||||
|
||||
this._onScan?.(code);
|
||||
});
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
this._modal
|
||||
.open({
|
||||
content: UiMessageModalComponent,
|
||||
title: 'Zugriff auf Kamera verweigert',
|
||||
data: { message: 'Falls Sie den Zugriff erlauben möchten, können Sie das über die Webseiteinstellung Ihres Browsers.' },
|
||||
})
|
||||
.afterClosed$.subscribe(() => {
|
||||
this._onClose?.();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async createBarcodePicker() {
|
||||
this._barcodePicker = await BarcodePicker.create(this.scanContainer.nativeElement, {
|
||||
playSoundOnScan: true,
|
||||
vibrateOnScan: true,
|
||||
},
|
||||
});
|
||||
|
||||
this._barcodePicker.applyScanSettings(this.getScanSettings());
|
||||
this.camera = Camera.default;
|
||||
|
||||
this.dataCaptureContext.setFrameSource(this.camera);
|
||||
|
||||
await this.camera.switchToDesiredState(FrameSourceState.On);
|
||||
this.dataCaptureView.hideProgressBar();
|
||||
}
|
||||
|
||||
getScanSettings(): ScanSettings {
|
||||
return new ScanSettings({
|
||||
blurryRecognition: false,
|
||||
getScanSettings(): BarcodeCaptureSettings {
|
||||
const settings = new BarcodeCaptureSettings();
|
||||
|
||||
enabledSymbologies: [
|
||||
Barcode.Symbology.EAN8,
|
||||
Barcode.Symbology.EAN13,
|
||||
Barcode.Symbology.UPCA,
|
||||
Barcode.Symbology.UPCE,
|
||||
Barcode.Symbology.CODE128,
|
||||
Barcode.Symbology.CODE39,
|
||||
Barcode.Symbology.CODE93,
|
||||
Barcode.Symbology.INTERLEAVED_2_OF_5,
|
||||
Barcode.Symbology.QR,
|
||||
],
|
||||
codeDuplicateFilter: 1000,
|
||||
});
|
||||
settings.enableSymbologies([
|
||||
Symbology.EAN8,
|
||||
Symbology.EAN13UPCA,
|
||||
Symbology.UPCE,
|
||||
Symbology.Code128,
|
||||
Symbology.Code39,
|
||||
Symbology.Code93,
|
||||
Symbology.InterleavedTwoOfFive,
|
||||
Symbology.QR,
|
||||
]);
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
onScan(fn: (code: string) => void) {
|
||||
@@ -75,26 +86,14 @@ export class ScanditOverlayComponent implements AfterViewInit, OnDestroy {
|
||||
this._onClose = fn;
|
||||
}
|
||||
|
||||
handleScanrResult(scanRestul: ScanResult) {
|
||||
let result: string | undefined;
|
||||
if (scanRestul.barcodes.length) {
|
||||
result = scanRestul.barcodes[0].data;
|
||||
} else if (scanRestul.texts.length) {
|
||||
result = scanRestul.texts[0].value;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
this._onScan?.(result);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this._onClose?.();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
this._barcodePicker?.destroy(true);
|
||||
this.barcodeCapture?.setEnabled(false);
|
||||
this.camera?.switchToDesiredState(FrameSourceState.Off);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,35 @@ import { Observable, Subscriber } from 'rxjs';
|
||||
import { ScanAdapter } from '../scan-adapter';
|
||||
import { Overlay } from '@angular/cdk/overlay';
|
||||
|
||||
import { configure } from 'scandit-sdk';
|
||||
import { configure } from 'scandit-web-datacapture-core';
|
||||
import { barcodeCaptureLoader } from 'scandit-web-datacapture-barcode';
|
||||
|
||||
// import { ScanditModalComponent } from './scandit-modal';
|
||||
import { Config } from '@core/config';
|
||||
import { ComponentPortal } from '@angular/cdk/portal';
|
||||
import { ScanditOverlayComponent } from './scandit-overlay.component';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { injectNetworkStatus$ } from 'apps/isa-app/src/app/services/network-status.service';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Injectable()
|
||||
export class ScanditScanAdapter implements ScanAdapter {
|
||||
readonly name = 'Scandit';
|
||||
|
||||
constructor(private readonly _config: Config, private _overlay: Overlay, private _environmentService: EnvironmentService) {}
|
||||
private $networkStatus = toSignal(injectNetworkStatus$());
|
||||
|
||||
constructor(
|
||||
private readonly _config: Config,
|
||||
private _overlay: Overlay,
|
||||
private _environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async init(): Promise<boolean> {
|
||||
if (this._environmentService.isTablet()) {
|
||||
await configure(this._config.get('licence.scandit'), {
|
||||
engineLocation: '/scandit/',
|
||||
await configure({
|
||||
licenseKey: this._config.get('licence.scandit'),
|
||||
libraryLocation: new URL('scandit', document.baseURI).toString(),
|
||||
moduleLoaders: [barcodeCaptureLoader()],
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -30,6 +42,11 @@ export class ScanditScanAdapter implements ScanAdapter {
|
||||
|
||||
scan(): Observable<string> {
|
||||
return new Observable((observer) => {
|
||||
if (this.$networkStatus() === 'offline') {
|
||||
observer.error(new Error('No network connection'));
|
||||
return;
|
||||
}
|
||||
|
||||
const overlay = this.createOverlay();
|
||||
|
||||
const portal = this.createPortal();
|
||||
@@ -49,7 +66,7 @@ export class ScanditScanAdapter implements ScanAdapter {
|
||||
sub.add(
|
||||
overlay.backdropClick().subscribe(() => {
|
||||
complete();
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
ref.instance.onScan((code) => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Directive, HostListener, Input } from '@angular/core';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
|
||||
@Directive({
|
||||
selector: '[productImageNavigation]',
|
||||
standalone: true,
|
||||
})
|
||||
export class NavigateOnClickDirective {
|
||||
@Input('productImageNavigation') ean: string;
|
||||
|
||||
constructor(private readonly _productCatalogNavigation: ProductCatalogNavigationService) {}
|
||||
|
||||
@HostListener('click', ['$event'])
|
||||
async onClick(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (this.ean) {
|
||||
await this._navigateToProductSearchDetails();
|
||||
}
|
||||
}
|
||||
|
||||
private async _navigateToProductSearchDetails() {
|
||||
await this._productCatalogNavigation
|
||||
.getArticleDetailsPathByEan({
|
||||
processId: Date.now(),
|
||||
ean: this.ean,
|
||||
extras: { queryParams: { main_qs: this.ean } },
|
||||
})
|
||||
.navigate();
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,5 @@
|
||||
export * from './lib/product-image.service';
|
||||
export * from './lib/product-image.module';
|
||||
export * from './lib/product-image.pipe';
|
||||
export * from './lib/product-image-navigation.directive';
|
||||
export * from './lib/tokens';
|
||||
|
||||
@@ -17,7 +17,10 @@ export class AuthService {
|
||||
|
||||
private _authConfig: AuthConfig;
|
||||
|
||||
constructor(private _config: Config, private readonly _oAuthService: OAuthService) {
|
||||
constructor(
|
||||
private _config: Config,
|
||||
private readonly _oAuthService: OAuthService,
|
||||
) {
|
||||
this._oAuthService.events?.subscribe((event) => {
|
||||
if (event.type === 'token_received') {
|
||||
console.log('SSO Token Expiration:', new Date(this._oAuthService.getAccessTokenExpiration()));
|
||||
@@ -45,6 +48,8 @@ export class AuthService {
|
||||
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
|
||||
} catch (error) {
|
||||
this.login();
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
this._initialized.next(true);
|
||||
|
||||
@@ -36,7 +36,7 @@ export class DomainAvailabilityService {
|
||||
private _logisticanService: LogisticianService,
|
||||
private _stockService: StockService,
|
||||
private _supplierService: StoreCheckoutSupplierService,
|
||||
private _branchService: StoreCheckoutBranchService
|
||||
private _branchService: StoreCheckoutBranchService,
|
||||
) {}
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
@@ -48,7 +48,7 @@ export class DomainAvailabilityService {
|
||||
getSuppliers(): Observable<SupplierDTO[]> {
|
||||
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
|
||||
map((response) => response.result),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class DomainAvailabilityService {
|
||||
getTakeAwaySupplier(): Observable<SupplierDTO> {
|
||||
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
|
||||
map(({ result }) => result?.find((supplier) => supplier?.supplierNumber === 'F')),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export class DomainAvailabilityService {
|
||||
getBranches(): Observable<BranchDTO[]> {
|
||||
return this._branchService.StoreCheckoutBranchGetBranches({}).pipe(
|
||||
map((response) => response.result),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export class DomainAvailabilityService {
|
||||
return this._stockService.StockGetStocksByBranch({ branchId }).pipe(
|
||||
map((response) => response.result),
|
||||
map((result) => result?.find((_) => true)),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export class DomainAvailabilityService {
|
||||
getDefaultStock(): Observable<StockDTO> {
|
||||
return this._stockService.StockCurrentStock().pipe(
|
||||
map((response) => response.result),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export class DomainAvailabilityService {
|
||||
status: response.result.status,
|
||||
version: response.result.version,
|
||||
})),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export class DomainAvailabilityService {
|
||||
getLogisticians(): Observable<LogisticianDTO> {
|
||||
return this._logisticanService.LogisticianGetLogisticians({}).pipe(
|
||||
map((response) => response.result?.find((l) => l.logisticianNumber === '2470')),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ export class DomainAvailabilityService {
|
||||
});
|
||||
return availabilities;
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,13 +167,13 @@ export class DomainAvailabilityService {
|
||||
this._stockService.StockInStock({ articleIds: [item.itemId], stockId: s.id }),
|
||||
this.getTakeAwaySupplier(),
|
||||
this.getDefaultBranch(),
|
||||
])
|
||||
]),
|
||||
),
|
||||
map(([response, supplier, defaultBranch]) => {
|
||||
const price = item?.price;
|
||||
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch?.id ?? defaultBranch?.id, quantity, price });
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ export class DomainAvailabilityService {
|
||||
map(([response, supplier]) => {
|
||||
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch.id, quantity, price });
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ export class DomainAvailabilityService {
|
||||
map(([response, supplier, defaultBranch]) => {
|
||||
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branchId ?? defaultBranch.id, quantity, price });
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ export class DomainAvailabilityService {
|
||||
switchMap((s) => this._stockService.StockInStockByEAN({ eans: eansFiltered, stockId: s.id })),
|
||||
withLatestFrom(this.getTakeAwaySupplier(), this.getDefaultBranch()),
|
||||
map((response) => response[0].result),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ export class DomainAvailabilityService {
|
||||
])
|
||||
.pipe(
|
||||
map((r) => this._mapToPickUpAvailability(r.result)?.find((_) => true)),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ export class DomainAvailabilityService {
|
||||
]).pipe(
|
||||
timeout(5000),
|
||||
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ export class DomainAvailabilityService {
|
||||
priceMaintained: preferred?.priceMaintained,
|
||||
};
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -329,12 +329,12 @@ export class DomainAvailabilityService {
|
||||
this.getPickUpAvailability({ item, quantity, branch: branch ?? defaultBranch }).pipe(
|
||||
mergeMap((availability) =>
|
||||
logistician$.pipe(
|
||||
map((logistician) => ({ ...(availability?.length > 0 ? availability[0] : []), logistician: { id: logistician.id } }))
|
||||
)
|
||||
map((logistician) => ({ ...(availability?.length > 0 ? availability[0] : []), logistician: { id: logistician.id } })),
|
||||
),
|
||||
),
|
||||
shareReplay(1)
|
||||
)
|
||||
)
|
||||
shareReplay(1),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ export class DomainAvailabilityService {
|
||||
priceMaintained: preferred?.priceMaintained,
|
||||
};
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ export class DomainAvailabilityService {
|
||||
switchMap((stockId) =>
|
||||
stockId
|
||||
? this._stockService.StockInStock({ articleIds: items.map((i) => i.id), stockId })
|
||||
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO)
|
||||
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO),
|
||||
),
|
||||
timeout(20000),
|
||||
withLatestFrom(this.getTakeAwaySupplier()),
|
||||
@@ -389,10 +389,10 @@ export class DomainAvailabilityService {
|
||||
supplier,
|
||||
quantity: 1,
|
||||
price: items?.find((i) => i.id === stockInfo.itemId)?.price,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ export class DomainAvailabilityService {
|
||||
getPickUpAvailabilities(payload: AvailabilityRequestDTO[], preferred?: boolean) {
|
||||
return this._availabilityService.AvailabilityStoreAvailability(payload).pipe(
|
||||
timeout(20000),
|
||||
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result))
|
||||
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ export class DomainAvailabilityService {
|
||||
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
||||
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
|
||||
timeout(20000),
|
||||
map((response) => this._mapToShippingAvailability(response.result))
|
||||
map((response) => this._mapToShippingAvailability(response.result)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,7 +416,7 @@ export class DomainAvailabilityService {
|
||||
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
||||
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
|
||||
timeout(20000),
|
||||
map((response) => this._mapToShippingAvailability(response.result))
|
||||
map((response) => this._mapToShippingAvailability(response.result)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -427,16 +427,16 @@ export class DomainAvailabilityService {
|
||||
return this.getPickUpAvailabilities(payload, true).pipe(
|
||||
timeout(20000),
|
||||
switchMap((availability) =>
|
||||
logistician$.pipe(map((logistician) => ({ availability: [...availability], logistician: { id: logistician.id } })))
|
||||
logistician$.pipe(map((logistician) => ({ availability: [...availability], logistician: { id: logistician.id } }))),
|
||||
),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
getPriceForAvailability(
|
||||
purchasingOption: string,
|
||||
catalogAvailability: CatAvailabilityDTO | AvailabilityDTO,
|
||||
availability: AvailabilityDTO
|
||||
availability: AvailabilityDTO,
|
||||
): PriceDTO {
|
||||
switch (purchasingOption) {
|
||||
case 'take-away':
|
||||
@@ -458,10 +458,6 @@ export class DomainAvailabilityService {
|
||||
return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
|
||||
}
|
||||
|
||||
private _priceIsEmpty(price: PriceDTO) {
|
||||
return isEmpty(price?.value) || isEmpty(price?.vat);
|
||||
}
|
||||
|
||||
private _mapToTakeAwayAvailability({
|
||||
response,
|
||||
supplier,
|
||||
@@ -482,7 +478,7 @@ export class DomainAvailabilityService {
|
||||
inStock: inStock,
|
||||
supplierSSC: quantity <= inStock ? '999' : '',
|
||||
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
|
||||
price: this._priceIsEmpty(price) ? stockInfo?.retailPrice : price,
|
||||
price: stockInfo?.retailPrice ?? price, // #4553 Es soll nun immer der retailPrice aus der InStock Abfrage verwendet werden, egal ob "price" empty ist oder nicht
|
||||
supplier: { id: supplier?.id },
|
||||
// TODO: Change after API Update
|
||||
// LH: 2021-03-09 preis Property hat nun ein Fallback auf retailPrice
|
||||
@@ -571,12 +567,12 @@ export class DomainAvailabilityService {
|
||||
if (!params.branchId) {
|
||||
branchId$ = this.getDefaultBranch().pipe(
|
||||
first(),
|
||||
map((b) => b.id)
|
||||
map((b) => b.id),
|
||||
);
|
||||
}
|
||||
|
||||
const stock$ = branchId$.pipe(
|
||||
mergeMap((branchId) => this._stockService.StockGetStocksByBranch({ branchId }).pipe(map((response) => response.result?.[0])))
|
||||
mergeMap((branchId) => this._stockService.StockGetStocksByBranch({ branchId }).pipe(map((response) => response.result?.[0]))),
|
||||
);
|
||||
|
||||
return stock$.pipe(
|
||||
@@ -593,17 +589,17 @@ export class DomainAvailabilityService {
|
||||
acc[stockInfo.ean] = stockInfo;
|
||||
return acc;
|
||||
}, {});
|
||||
})
|
||||
)
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getInStock({ itemIds, branchId }: { itemIds: number[]; branchId: number }): Observable<StockInfoDTO[]> {
|
||||
return this.getStockByBranch(branchId).pipe(
|
||||
mergeMap((stock) =>
|
||||
this._stockService.StockInStock({ articleIds: itemIds, stockId: stock.id }).pipe(map((response) => response.result))
|
||||
)
|
||||
this._stockService.StockInStock({ articleIds: itemIds, stockId: stock.id }).pipe(map((response) => response.result)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ export class ThumbnailUrlPipe implements PipeTransform, OnDestroy {
|
||||
private input$ = new BehaviorSubject<{ width?: number; height?: number; ean?: string }>(undefined);
|
||||
private result: string;
|
||||
|
||||
private onDestroy$ = new Subject();
|
||||
private onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(private domainCatalogThumbnailService: DomainCatalogThumbnailService, private cdr: ChangeDetectorRef) {}
|
||||
constructor(
|
||||
private domainCatalogThumbnailService: DomainCatalogThumbnailService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next();
|
||||
@@ -27,7 +30,7 @@ export class ThumbnailUrlPipe implements PipeTransform, OnDestroy {
|
||||
this.input$
|
||||
.pipe(
|
||||
takeUntil(this.onDestroy$),
|
||||
switchMap((input) => this.domainCatalogThumbnailService.getThumnaulUrl(input))
|
||||
switchMap((input) => this.domainCatalogThumbnailService.getThumnaulUrl(input)),
|
||||
)
|
||||
.subscribe((result) => {
|
||||
this.result = result;
|
||||
|
||||
@@ -1021,7 +1021,11 @@ export class DomainCheckoutService {
|
||||
|
||||
//#region Common
|
||||
|
||||
@memorize()
|
||||
// Fix für Ticket #4619 Versand Artikel im Warenkob -> keine Änderung bei Kundendaten erfassen
|
||||
// Auskommentiert, da dieser Aufruf oftmals mit gleichen Parametern aufgerufen wird (ohne ausgewählten Kunden nur ein leeres Objekt bei customerFeatures)
|
||||
// memorize macht keinen deepCompare von Objekten und denkt hier, dass immer der gleiche Return Wert zurückkommt, allerdings ist das hier oft nicht der Fall
|
||||
// und der Decorator memorized dann fälschlicherweise
|
||||
// @memorize()
|
||||
canSetCustomer({
|
||||
processId,
|
||||
customerFeatures,
|
||||
|
||||
@@ -1,37 +1,60 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActionHandler } from '@core/command';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
import { DomainPrinterService, Printer } from '@domain/printer';
|
||||
import { PrintModalComponent, PrintModalData } from '@modal/printer';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { OrderItemsContext } from './order-items.context';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
|
||||
@Injectable()
|
||||
export class PrintCompartmentLabelActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
constructor(
|
||||
private uiModal: UiModalService,
|
||||
private domainPrinterService: DomainPrinterService,
|
||||
private nativeContainerService: NativeContainerService
|
||||
private nativeContainerService: NativeContainerService,
|
||||
private _environmentSerivce: EnvironmentService
|
||||
) {
|
||||
super('PRINT_COMPARTMENTLABEL');
|
||||
}
|
||||
printCompartmentLabelHelper(printer: string, orderItemSubsetIds: number[]) {
|
||||
return this.domainPrinterService
|
||||
.printCompartmentLabel({
|
||||
printer,
|
||||
orderItemSubsetIds,
|
||||
})
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
|
||||
await this.uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
config: { showScrollbarY: false },
|
||||
data: {
|
||||
printImmediately: !this.nativeContainerService.isNative,
|
||||
printerType: 'Label',
|
||||
print: (printer) =>
|
||||
this.domainPrinterService
|
||||
.printCompartmentLabel({ printer, orderItemSubsetIds: data.items.map((item) => item.orderItemSubsetId) })
|
||||
.toPromise(),
|
||||
} as PrintModalData,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
const printerList = await this.domainPrinterService.getAvailableLabelPrinters().toPromise();
|
||||
let printer: Printer;
|
||||
|
||||
if (Array.isArray(printerList)) {
|
||||
printer = printerList.find((printer) => printer.selected === true);
|
||||
}
|
||||
if (!printer || this._environmentSerivce.matchTablet()) {
|
||||
await this.uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
config: { showScrollbarY: false },
|
||||
data: {
|
||||
printImmediately: !this._environmentSerivce.matchTablet(),
|
||||
printerType: 'Label',
|
||||
print: (printer) =>
|
||||
this.printCompartmentLabelHelper(
|
||||
printer,
|
||||
data.items.map((item) => item.orderItemSubsetId)
|
||||
),
|
||||
} as PrintModalData,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
} else {
|
||||
await this.printCompartmentLabelHelper(
|
||||
printer.key,
|
||||
data.items.map((item) => item.orderItemSubsetId)
|
||||
);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,45 +6,60 @@ import { UiModalService } from '@ui/modal';
|
||||
import { PrintModalComponent, PrintModalData } from '@modal/printer';
|
||||
import { groupBy } from '@ui/common';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { ReceiptDTO } from '@swagger/oms';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
|
||||
@Injectable()
|
||||
export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
constructor(
|
||||
private uiModal: UiModalService,
|
||||
private domainPrinterService: DomainPrinterService,
|
||||
private nativeContainerService: NativeContainerService
|
||||
private nativeContainerService: NativeContainerService,
|
||||
private _environmentSerivce: EnvironmentService
|
||||
) {
|
||||
super('PRINT_SHIPPINGNOTE');
|
||||
}
|
||||
|
||||
async printShippingNoteHelper(printer: string, receipts: ReceiptDTO[]) {
|
||||
try {
|
||||
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
|
||||
await this.domainPrinterService.printShippingNote({ printer, receipts: group?.items?.map((r) => r?.id) }).toPromise();
|
||||
}
|
||||
return {
|
||||
error: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {
|
||||
error: true,
|
||||
message: error?.message || error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
|
||||
await this.uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
config: { showScrollbarY: false },
|
||||
data: {
|
||||
printImmediately: !this.nativeContainerService.isNative,
|
||||
printerType: 'Label',
|
||||
print: async (printer) => {
|
||||
try {
|
||||
const receipts = data?.receipts?.filter((r) => r?.receiptType & 1);
|
||||
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
|
||||
await this.domainPrinterService.printShippingNote({ printer, receipts: group?.items?.map((r) => r?.id) }).toPromise();
|
||||
}
|
||||
return {
|
||||
error: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {
|
||||
error: true,
|
||||
message: error?.message || error,
|
||||
};
|
||||
}
|
||||
},
|
||||
} as PrintModalData,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
const printerList = await this.domainPrinterService.getAvailableLabelPrinters().toPromise();
|
||||
const receipts = data?.receipts?.filter((r) => r?.receiptType & 1);
|
||||
let printer: Printer;
|
||||
|
||||
if (Array.isArray(printerList)) {
|
||||
printer = printerList.find((printer) => printer.selected === true);
|
||||
}
|
||||
if (!printer || this._environmentSerivce.matchTablet()) {
|
||||
await this.uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
config: { showScrollbarY: false },
|
||||
data: {
|
||||
printImmediately: !this.nativeContainerService.isNative,
|
||||
printerType: 'Label',
|
||||
print: async (printer) => await this.printShippingNoteHelper(printer, receipts),
|
||||
} as PrintModalData,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
} else {
|
||||
await this.printShippingNoteHelper(printer.key, receipts);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
BranchService,
|
||||
BuyerDTO,
|
||||
ChangeStockStatusCodeValues,
|
||||
HistoryDTO,
|
||||
NotificationChannel,
|
||||
@@ -29,7 +30,7 @@ export class DomainOmsService {
|
||||
private branchService: BranchService,
|
||||
private vatService: VATService,
|
||||
private stockStatusCodeService: StockStatusCodeService,
|
||||
private _orderCheckoutService: OrderCheckoutService
|
||||
private _orderCheckoutService: OrderCheckoutService,
|
||||
) {}
|
||||
|
||||
getOrderItemsByCustomerNumber(customerNumber: string, skip: number): Observable<OrderListItemDTO[]> {
|
||||
@@ -54,7 +55,7 @@ export class DomainOmsService {
|
||||
return this.receiptService
|
||||
.ReceiptGetReceiptsByOrderItemSubset({
|
||||
payload: {
|
||||
receiptType: (65 as unknown) as any,
|
||||
receiptType: 65 as unknown as any,
|
||||
ids: orderItemSubsetIds,
|
||||
eagerLoading: 1,
|
||||
},
|
||||
@@ -76,7 +77,7 @@ export class DomainOmsService {
|
||||
getStockStatusCodes({ supplierId, eagerLoading = 0 }: { supplierId: number; eagerLoading?: number }) {
|
||||
return this.stockStatusCodeService.StockStatusCodeGetStockStatusCodes({ supplierId, eagerLoading }).pipe(
|
||||
map((response) => response.result),
|
||||
shareReplay()
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,7 +121,7 @@ export class DomainOmsService {
|
||||
orderId: number,
|
||||
orderItemId: number,
|
||||
orderItemSubsetId: number,
|
||||
data: StatusValues
|
||||
data: StatusValues,
|
||||
): Observable<ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO> {
|
||||
return this.orderService
|
||||
.OrderChangeStatus({
|
||||
@@ -184,7 +185,7 @@ export class DomainOmsService {
|
||||
selected: order.notificationChannels,
|
||||
email: order.buyer?.communicationDetails?.email,
|
||||
mobile: order.buyer?.communicationDetails?.mobile,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,12 +206,47 @@ export class DomainOmsService {
|
||||
delete communicationDetails.mobile;
|
||||
}
|
||||
|
||||
return this.updateOrder({ orderId, notificationChannels: changes.selected, communicationDetails });
|
||||
}
|
||||
|
||||
updateOrder({
|
||||
orderId,
|
||||
notificationChannels,
|
||||
communicationDetails,
|
||||
firstName,
|
||||
lastName,
|
||||
organisation,
|
||||
}: {
|
||||
orderId: number;
|
||||
notificationChannels?: NotificationChannel;
|
||||
communicationDetails?: { email?: string; mobile?: string };
|
||||
lastName?: string;
|
||||
firstName?: string;
|
||||
organisation?: string;
|
||||
}) {
|
||||
const buyer: BuyerDTO = {};
|
||||
|
||||
if (!!communicationDetails) {
|
||||
buyer.communicationDetails = { ...communicationDetails };
|
||||
}
|
||||
|
||||
if (!!lastName || !!firstName) {
|
||||
buyer.lastName = lastName;
|
||||
buyer.firstName = firstName;
|
||||
}
|
||||
|
||||
if (!!organisation) {
|
||||
buyer.organisation = {
|
||||
name: organisation,
|
||||
};
|
||||
}
|
||||
|
||||
return this.orderService
|
||||
.OrderPatchOrder({
|
||||
orderId: orderId,
|
||||
order: {
|
||||
notificationChannels: changes.selected,
|
||||
buyer: { communicationDetails },
|
||||
notificationChannels,
|
||||
buyer,
|
||||
},
|
||||
})
|
||||
.pipe(map((res) => res.result));
|
||||
@@ -242,11 +278,14 @@ export class DomainOmsService {
|
||||
map((res) =>
|
||||
res.result
|
||||
.sort((a, b) => new Date(b.completed).getTime() - new Date(a.completed).getTime())
|
||||
.reduce((data, result) => {
|
||||
(data[result.name] = data[result.name] || []).push(new Date(result.completed));
|
||||
return data;
|
||||
}, {} as Record<string, Date[]>)
|
||||
)
|
||||
.reduce(
|
||||
(data, result) => {
|
||||
(data[result.name] = data[result.name] || []).push(new Date(result.completed));
|
||||
return data;
|
||||
},
|
||||
{} as Record<string, Date[]>,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export class PickupShelfOutService extends PickupShelfIOService {
|
||||
);
|
||||
}
|
||||
|
||||
const { orderdate } = args.filter?.getQueryToken()?.filter ?? {};
|
||||
const { orderdate, supplier_id } = args.filter?.getQueryToken()?.filter ?? {};
|
||||
|
||||
return this._abholfachService.AbholfachWarenausgabe({
|
||||
input: {
|
||||
@@ -45,6 +45,7 @@ export class PickupShelfOutService extends PickupShelfIOService {
|
||||
archive: String(true),
|
||||
all_branches: String(true),
|
||||
orderdate,
|
||||
supplier_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { inject, isDevMode, NgModule } from '@angular/core';
|
||||
import { isDevMode, NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import {
|
||||
CanActivateCartGuard,
|
||||
@@ -8,8 +8,6 @@ import {
|
||||
CanActivateCustomerOrdersWithProcessIdGuard,
|
||||
CanActivateCustomerWithProcessIdGuard,
|
||||
CanActivateGoodsInGuard,
|
||||
CanActivateGoodsOutGuard,
|
||||
CanActivateGoodsOutWithProcessIdGuard,
|
||||
CanActivateProductGuard,
|
||||
CanActivateProductWithProcessIdGuard,
|
||||
CanActivateRemissionGuard,
|
||||
@@ -22,7 +20,6 @@ import { MainComponent } from './main.component';
|
||||
import { PreviewComponent } from './preview';
|
||||
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ProcessIdGuard } from './guards/process-id.guard';
|
||||
import { ActivateProcessIdGuard, ActivateProcessIdWithConfigKeyGuard } from './guards/activate-process-id.guard';
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export const metaReducers: MetaReducer<RootState>[] = !environment.production ?
|
||||
imports: [
|
||||
StoreModule.forRoot(rootReducer, { metaReducers }),
|
||||
EffectsModule.forRoot([]),
|
||||
StoreDevtoolsModule.instrument({ name: 'ISA Ngrx Application Store' }),
|
||||
StoreDevtoolsModule.instrument({ name: 'ISA Ngrx Application Store', connectInZone: true }),
|
||||
],
|
||||
})
|
||||
export class AppStoreModule {}
|
||||
|
||||
@@ -1 +1,28 @@
|
||||
@if ($offlineBannerVisible()) {
|
||||
<div [@fadeInOut] class="bg-brand text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
|
||||
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
|
||||
<div>
|
||||
<ng-icon name="matWifiOff"></ng-icon>
|
||||
</div>
|
||||
|
||||
<div>Sie sind offline, keine Verbindung zum Netzwerk.</div>
|
||||
</h3>
|
||||
<p>Bereits geladene Ihnalte werden angezeigt, Interaktionen sind aktuell nicht möglich.</p>
|
||||
</div>
|
||||
}
|
||||
@if ($onlineBannerVisible()) {
|
||||
<div [@fadeInOut] class="bg-green-500 text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
|
||||
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
|
||||
<div>
|
||||
<ng-icon name="matWifi"></ng-icon>
|
||||
</div>
|
||||
|
||||
<div>Sie sind wieder online.</div>
|
||||
</h3>
|
||||
<button class="fixed top-2 right-4 text-3xl w-12 h-12" type="button" (click)="$onlineBannerVisible.set(false)">
|
||||
<ng-icon name="matClose"></ng-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,30 +1,71 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Component, HostListener, Inject, OnInit, Renderer2 } from '@angular/core';
|
||||
import { Component, effect, HostListener, Inject, OnInit, Renderer2, signal, untracked } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { SwUpdate, UpdateAvailableEvent } from '@angular/service-worker';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { Config } from '@core/config';
|
||||
import { NotificationsHub } from '@hub/notifications';
|
||||
import packageInfo from 'package';
|
||||
import { asapScheduler, interval, Observable, Subscription } from 'rxjs';
|
||||
import { asapScheduler, interval, Subscription } from 'rxjs';
|
||||
import { UserStateService } from '@swagger/isa';
|
||||
import { IsaLogProvider } from './providers';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { injectOnline$ } from './services/network-status.service';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
animations: [
|
||||
trigger('fadeInOut', [
|
||||
transition(':enter', [
|
||||
// :enter wird ausgelöst, wenn das Element zum DOM hinzugefügt wird
|
||||
style({ opacity: 0, transform: 'translateY(-100%)' }),
|
||||
animate('300ms', style({ opacity: 1, transform: 'translateY(0)' })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
// :leave wird ausgelöst, wenn das Element aus dem DOM entfernt wird
|
||||
animate('300ms', style({ opacity: 0, transform: 'translateY(-100%)' })),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
$online = toSignal(injectOnline$());
|
||||
|
||||
$offlineBannerVisible = signal(false);
|
||||
|
||||
$onlineBannerVisible = signal(false);
|
||||
|
||||
private onlineBannerDismissTimeout: any;
|
||||
|
||||
onlineEffects = effect(() => {
|
||||
const online = this.$online();
|
||||
const offlineBannerVisible = this.$offlineBannerVisible();
|
||||
|
||||
untracked(() => {
|
||||
this.$offlineBannerVisible.set(!online);
|
||||
|
||||
if (!online) {
|
||||
this.$onlineBannerVisible.set(false);
|
||||
clearTimeout(this.onlineBannerDismissTimeout);
|
||||
}
|
||||
|
||||
if (offlineBannerVisible && online) {
|
||||
this.$onlineBannerVisible.set(true);
|
||||
this.onlineBannerDismissTimeout = setTimeout(() => this.$onlineBannerVisible.set(false), 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
private _checkForUpdates: number = this._config.get('checkForUpdates');
|
||||
updateAvailableObs: Observable<UpdateAvailableEvent>;
|
||||
|
||||
get checkForUpdates(): number {
|
||||
return this._checkForUpdates;
|
||||
return this._checkForUpdates ?? 60 * 60 * 1000; // default 1 hour
|
||||
}
|
||||
|
||||
// For Unit Testing
|
||||
@@ -45,7 +86,7 @@ export class AppComponent implements OnInit {
|
||||
private infoService: UserStateService,
|
||||
private readonly _environment: EnvironmentService,
|
||||
private readonly _authService: AuthService,
|
||||
private readonly _modal: UiModalService
|
||||
private readonly _modal: UiModalService,
|
||||
) {
|
||||
this.updateClient();
|
||||
IsaLogProvider.InfoService = this.infoService;
|
||||
|
||||
@@ -37,6 +37,10 @@ import { NativeContainerService } from 'native-container';
|
||||
import { ShellModule } from '@shared/shell';
|
||||
import { MainComponent } from './main.component';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { NgIconsModule } from '@ng-icons/core';
|
||||
import { matClose, matWifi, matWifiOff } from '@ng-icons/material-icons/baseline';
|
||||
import { NetworkStatusService } from './services/network-status.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
@@ -46,26 +50,68 @@ export function _appInitializerFactory(
|
||||
auth: AuthService,
|
||||
injector: Injector,
|
||||
scanAdapter: ScanAdapterService,
|
||||
nativeContainer: NativeContainerService
|
||||
nativeContainer: NativeContainerService,
|
||||
networkStatus: NetworkStatusService,
|
||||
) {
|
||||
return async () => {
|
||||
const statusElement = document.querySelector('#init-status');
|
||||
statusElement.innerHTML = 'Konfigurationen werden geladen...';
|
||||
await config.init();
|
||||
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
|
||||
await auth.init();
|
||||
const laoderElement = document.querySelector('#init-loader');
|
||||
|
||||
if (auth.isAuthenticated()) {
|
||||
statusElement.innerHTML = 'App wird initialisiert...';
|
||||
const state = injector.get(RootStateService);
|
||||
await state.init();
|
||||
try {
|
||||
let online = false;
|
||||
|
||||
while (!online) {
|
||||
online = await firstValueFrom(networkStatus.online$);
|
||||
|
||||
if (!online) {
|
||||
statusElement.innerHTML =
|
||||
'<b>Warte auf Netzwerkverbindung (WLAN)</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br>Sobald eine Netzwerkverbindung besteht, wird die App automatisch neu geladen.';
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
|
||||
statusElement.innerHTML = 'Konfigurationen werden geladen...';
|
||||
await config.init();
|
||||
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
|
||||
await auth.init();
|
||||
|
||||
if (auth.isAuthenticated()) {
|
||||
statusElement.innerHTML = 'App wird initialisiert...';
|
||||
const state = injector.get(RootStateService);
|
||||
await state.init();
|
||||
}
|
||||
|
||||
statusElement.innerHTML = 'Native Container wird initialisiert...';
|
||||
await nativeContainer.init();
|
||||
|
||||
statusElement.innerHTML = 'Scanner wird initialisiert...';
|
||||
await scanAdapter.init();
|
||||
} catch (error) {
|
||||
laoderElement.remove();
|
||||
statusElement.classList.add('text-xl');
|
||||
statusElement.innerHTML = '<b>Fehler bei der Initialisierung</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br><br>';
|
||||
|
||||
const reload = document.createElement('button');
|
||||
reload.classList.add('bg-brand', 'text-white', 'p-2', 'rounded', 'cursor-pointer');
|
||||
reload.innerHTML = 'App neu laden';
|
||||
reload.onclick = () => window.location.reload();
|
||||
statusElement.appendChild(reload);
|
||||
|
||||
const preLabel = document.createElement('div');
|
||||
preLabel.classList.add('mt-12');
|
||||
preLabel.innerHTML = 'Fehlermeldung:';
|
||||
|
||||
statusElement.appendChild(preLabel);
|
||||
|
||||
const pre = document.createElement('pre');
|
||||
pre.classList.add('mt-4', 'text-wrap');
|
||||
pre.innerHTML = error.message;
|
||||
|
||||
statusElement.appendChild(pre);
|
||||
|
||||
console.error('Error during app initialization', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
statusElement.innerHTML = 'Native Container wird initialisiert...';
|
||||
await nativeContainer.init();
|
||||
|
||||
statusElement.innerHTML = 'Scanner wird initialisiert...';
|
||||
await scanAdapter.init();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,13 +153,14 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
ScanditScanAdapterModule.forRoot(),
|
||||
PlatformModule,
|
||||
IconModule.forRoot(),
|
||||
NgIconsModule.withIcons({ matWifiOff, matClose, matWifi }),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: _appInitializerFactory,
|
||||
multi: true,
|
||||
deps: [Config, AuthService, Injector, ScanAdapterService, NativeContainerService],
|
||||
deps: [Config, AuthService, Injector, ScanAdapterService, NativeContainerService, NetworkStatusService],
|
||||
},
|
||||
{
|
||||
provide: NOTIFICATIONS_HUB_OPTIONS,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { ScanAdapterService } from '@adapter/scan';
|
||||
import { AuthService as IsaAuthService } from '@swagger/isa';
|
||||
@@ -9,11 +9,12 @@ import { EnvironmentService } from '@core/environment';
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class IsAuthenticatedGuard {
|
||||
constructor(
|
||||
private _router: Router,
|
||||
private _authService: AuthService,
|
||||
private _scanService: ScanAdapterService,
|
||||
private _isaAuthService: IsaAuthService,
|
||||
private _modal: UiModalService,
|
||||
private _environmentService: EnvironmentService
|
||||
private _environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
|
||||
@@ -2,30 +2,31 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
|
||||
import { NEVER, Observable, throwError } from 'rxjs';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { catchError, mergeMap, tap } from 'rxjs/operators';
|
||||
import { catchError, filter, mergeMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { IsaLogProvider } from '../providers';
|
||||
import { LogLevel } from '@core/logger';
|
||||
import { injectOnline$ } from '../services/network-status.service';
|
||||
|
||||
@Injectable()
|
||||
export class HttpErrorInterceptor implements HttpInterceptor {
|
||||
constructor(private _modal: UiModalService, private _auth: AuthService, private _isaLogProvider: IsaLogProvider) {}
|
||||
readonly offline$ = injectOnline$().pipe(filter((online) => !online));
|
||||
|
||||
constructor(
|
||||
private _modal: UiModalService,
|
||||
private _auth: AuthService,
|
||||
private _isaLogProvider: IsaLogProvider,
|
||||
) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(req).pipe(catchError((error: HttpErrorResponse, caught: any) => this.handleError(error)));
|
||||
return next.handle(req).pipe(
|
||||
takeUntil(this.offline$),
|
||||
catchError((error: HttpErrorResponse, caught: any) => this.handleError(error)),
|
||||
);
|
||||
}
|
||||
|
||||
handleError(error: HttpErrorResponse): Observable<any> {
|
||||
if (error.status === 0) {
|
||||
return this._modal
|
||||
.open({
|
||||
content: UiMessageModalComponent,
|
||||
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
|
||||
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
|
||||
})
|
||||
.afterClosed$.pipe(mergeMap(() => throwError(error)));
|
||||
} else if (error.status === 401) {
|
||||
console.log('401', error);
|
||||
if (error.status === 401) {
|
||||
return this._modal
|
||||
.open({
|
||||
content: UiMessageModalComponent,
|
||||
@@ -36,11 +37,13 @@ export class HttpErrorInterceptor implements HttpInterceptor {
|
||||
tap(() => {
|
||||
this._auth.login();
|
||||
}),
|
||||
mergeMap(() => NEVER)
|
||||
mergeMap(() => NEVER),
|
||||
);
|
||||
}
|
||||
|
||||
this._isaLogProvider.log(LogLevel.ERROR, 'Http Error', error);
|
||||
if (!error.url.endsWith('/isa/logging')) {
|
||||
this._isaLogProvider.log(LogLevel.ERROR, 'Http Error', error);
|
||||
}
|
||||
|
||||
return throwError(error);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ import { LogLevel } from '@core/logger';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class IsaErrorHandler implements ErrorHandler {
|
||||
constructor(private _modal: UiModalService, private _authService: AuthService, private _isaLogProvider: IsaLogProvider) {}
|
||||
constructor(
|
||||
private _modal: UiModalService,
|
||||
private _authService: AuthService,
|
||||
private _isaLogProvider: IsaLogProvider,
|
||||
) {}
|
||||
|
||||
async handleError(error: any): Promise<void> {
|
||||
console.error(error);
|
||||
@@ -37,13 +41,13 @@ export class IsaErrorHandler implements ErrorHandler {
|
||||
|
||||
this._isaLogProvider.log(LogLevel.ERROR, 'Client Error', error);
|
||||
|
||||
this._modal.open({
|
||||
content: UiErrorModalComponent,
|
||||
title:
|
||||
!navigator.onLine || (error instanceof HttpErrorResponse && error?.status === 0)
|
||||
? 'Sie sind offline, keine Verbindung zum Netzwerk'
|
||||
: 'Unbekannter Fehler',
|
||||
data: error,
|
||||
});
|
||||
// this._modal.open({
|
||||
// content: UiErrorModalComponent,
|
||||
// title:
|
||||
// !navigator.onLine || (error instanceof HttpErrorResponse && error?.status === 0)
|
||||
// ? 'Sie sind offline, keine Verbindung zum Netzwerk'
|
||||
// : 'Unbekannter Fehler',
|
||||
// data: error,
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
25
apps/isa-app/src/app/services/network-status.service.ts
Normal file
25
apps/isa-app/src/app/services/network-status.service.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class NetworkStatusService {
|
||||
online$ = new Observable<boolean>((subscriber) => {
|
||||
const handler = () => subscriber.next(navigator.onLine);
|
||||
|
||||
window.addEventListener('online', handler);
|
||||
window.addEventListener('offline', handler);
|
||||
|
||||
handler();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', handler);
|
||||
window.removeEventListener('offline', handler);
|
||||
};
|
||||
});
|
||||
|
||||
status$ = this.online$.pipe(map((online) => (online ? 'online' : 'offline')));
|
||||
}
|
||||
|
||||
export const injectNetworkStatus$ = () => inject(NetworkStatusService).status$;
|
||||
|
||||
export const injectOnline$ = () => inject(NetworkStatusService).online$;
|
||||
@@ -2,23 +2,26 @@ import { Injectable } from '@angular/core';
|
||||
import { Logger, LogLevel } from '@core/logger';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { UserStateService } from '@swagger/isa';
|
||||
import { debounceTime, switchMap } from 'rxjs/operators';
|
||||
import { debounceTime, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { RootState } from './root.state';
|
||||
import packageInfo from 'package';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RootStateService {
|
||||
static LOCAL_STORAGE_KEY = 'ISA_APP_INITIALSTATE';
|
||||
|
||||
private _cancelSave = new Subject<void>();
|
||||
|
||||
constructor(private readonly _userStateService: UserStateService, private _logger: Logger, private _store: Store) {
|
||||
if (!environment.production) {
|
||||
console.log('Die UserState kann in der Konsole mit der Funktion "clearUserState()" geleert werden.');
|
||||
|
||||
window['clearUserState'] = () => {
|
||||
this.clear();
|
||||
};
|
||||
}
|
||||
|
||||
window['clearUserState'] = () => {
|
||||
this.clear();
|
||||
};
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -31,7 +34,8 @@ export class RootStateService {
|
||||
this._store
|
||||
.select((state) => state)
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
takeUntil(this._cancelSave),
|
||||
debounceTime(1000),
|
||||
switchMap((state) => {
|
||||
const raw = JSON.stringify({ ...state, version: packageInfo.version });
|
||||
RootStateService.SaveToLocalStorageRaw(raw);
|
||||
@@ -64,13 +68,17 @@ export class RootStateService {
|
||||
return false;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._userStateService
|
||||
.UserStateResetUserState()
|
||||
.toPromise()
|
||||
.catch((error) => this._logger.log(LogLevel.ERROR, error));
|
||||
RootStateService.RemoveFromLocalStorage();
|
||||
window.location.reload();
|
||||
async clear() {
|
||||
try {
|
||||
this._cancelSave.next();
|
||||
await this._userStateService.UserStateResetUserState().toPromise();
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
RootStateService.RemoveFromLocalStorage();
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
this._logger.log(LogLevel.ERROR, error);
|
||||
}
|
||||
}
|
||||
|
||||
static SaveToLocalStorage(state: RootState) {
|
||||
|
||||
@@ -280,6 +280,11 @@
|
||||
"name": "apps",
|
||||
"data": "M226-160q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19ZM226-414q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19ZM226-668q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Z",
|
||||
"viewBox": "0 -960 960 960"
|
||||
},
|
||||
{
|
||||
"name": "gift",
|
||||
"data": "M2 21V10H0V4H5.2C5.11667 3.85 5.0625 3.69167 5.0375 3.525C5.0125 3.35833 5 3.18333 5 3C5 2.16667 5.29167 1.45833 5.875 0.875C6.45833 0.291667 7.16667 0 8 0C8.38333 0 8.74167 0.0708333 9.075 0.2125C9.40833 0.354167 9.71667 0.55 10 0.8C10.2833 0.533333 10.5917 0.333333 10.925 0.2C11.2583 0.0666667 11.6167 0 12 0C12.8333 0 13.5417 0.291667 14.125 0.875C14.7083 1.45833 15 2.16667 15 3C15 3.18333 14.9833 3.35417 14.95 3.5125C14.9167 3.67083 14.8667 3.83333 14.8 4H20V10H18V21H2ZM12 2C11.7167 2 11.4792 2.09583 11.2875 2.2875C11.0958 2.47917 11 2.71667 11 3C11 3.28333 11.0958 3.52083 11.2875 3.7125C11.4792 3.90417 11.7167 4 12 4C12.2833 4 12.5208 3.90417 12.7125 3.7125C12.9042 3.52083 13 3.28333 13 3C13 2.71667 12.9042 2.47917 12.7125 2.2875C12.5208 2.09583 12.2833 2 12 2ZM7 3C7 3.28333 7.09583 3.52083 7.2875 3.7125C7.47917 3.90417 7.71667 4 8 4C8.28333 4 8.52083 3.90417 8.7125 3.7125C8.90417 3.52083 9 3.28333 9 3C9 2.71667 8.90417 2.47917 8.7125 2.2875C8.52083 2.09583 8.28333 2 8 2C7.71667 2 7.47917 2.09583 7.2875 2.2875C7.09583 2.47917 7 2.71667 7 3ZM2 6V8H9V6H2ZM9 19V10H4V19H9ZM11 19H16V10H11V19ZM18 8V6H11V8H18Z",
|
||||
"viewBox": "0 0 20 21"
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
5
apps/isa-app/src/assets/images/bookmark_responsive.svg
Normal file
5
apps/isa-app/src/assets/images/bookmark_responsive.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="48" height="51" viewBox="0 0 48 51" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 4.47368C8 2.01878 9.99009 0 12.445 0L43.555 0C46.0099 0 48 2.01878 48 4.47368H8Z" fill="#172062"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 4.445C0 1.99009 1.99009 0 4.445 0L42.7807 0V43.4808C42.7807 46.7878 39.2981 48.9368 36.3423 47.4537L23.376 40.948C22.1212 40.3183 20.6426 40.3186 19.3879 40.9486L6.4397 47.4505C3.48377 48.9348 0 46.7859 0 43.4782L0 4.445Z" fill="#0556B4"/>
|
||||
<rect x="19" y="19" width="18" height="17" fill="#0556B4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 606 B |
@@ -73,7 +73,13 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
|
||||
"scandit": "ApI1Sg5/KzWICPhn0CbflI4cjgXiFkKnhgEmUvAt0D3CWsck0SCIm557hJvGTqXD0A1mt3IvEwA8eQ1CBXL3EoA9lASWUcLt+FSWP9gHao+LT6Oz9QMrsJoJRC/OboodKS4/F+ZVa02eUT53X0GX9jJeEnh8EerqZimrl6oDjNLvHCzBnxtmsMZidAUaErqK5yaQQCkUKcfTQhRzVhqvsjdENR76EmrrvCSr93oMCnnDHjcyOz8O3stcNrCOGbxwDBIMwf05CzoEBhxF9SoT/fwvfWa9BRvlMTxl9lUUE2f3Aw9+0CBG7PYgZ9XGCiCM2Wy9T5cf0C5MOLr2MAIgQrlDua0oEtmC5FjFojQHJEITRmLyQnAyUU8Y+jkUUCtQNmweZKtTQT9le+G9QCzRoSsmVsVxCKxII3+8iLA+Lh9meI/vJSaQ2VpDgZVrV1Vc4V/z0Exu7Ap7R0MpzCy4hhRzdpWgf4OG815ZctwMvM7qfBqykG98B9oP3YC/LG+cpVK4Ipx5VnWBUIwVHDiocHhMjyroTBAeH3BfeCdOEpKwNK9vOxrbvUpqAc8ZOaaal08hWgFVFR8BQN+rEkiqKx5fONRXefkkyUWMdqwvoBn7ZzCr+F9GmD52Q7ZuDcyAigm2ArwDipT9QUH7gxphogcKRNAWAyaENWWKRBFWmeNYXJOuGXLr05Zye7joGFbJHxPYyUJ6KMombmYlI2GHc4JQGJNneDuL93v8AkYcfM2WfOjf0hm+SVw0TP95bD1xJwSCoKO5L6FxqMu5GPcOqik3sjGHg6XsEr10m7jt7G2PfNTgS3ft+TP32OjeSu9M80Fu3nmeqLM4a4lx9fFbGMpWDEU9wdftmPByeW7KoXyS2bkd1Mg9UP8ci0nKo2d/ThPn+iMEjXSibNeIGibaIT4ys3pjDLW2mx0IxQpwnuQfZzAHBszpMkwKuG4+FrqXff9+bZGuRQlVwhm2v5xbKRjgi99i14QkuNVUkePxllLZGFgQH0DjsLINZb+R9stBYjQmMGc2kkJfyhHx2/zvT2D2mpgcaq0ZRKco2CqDiLUJoHwLPse/fu4PWvqSKVpwiqhCehuloWTE3jkFUncwDI50hPvrfCx92cOJUE8RTsQgDoAh7JKsPHctSZfUicZyLHOuHMhtIoxs6l/lzFowbhZob/Uo7aRsipOcP/iZgEPnMEu/obXcLR3QrlQ+WuEppU4g2MZ9+xENLCqVDLEHiz+jZIvl42ePc4Ip4lJ5RQFcSSR1yYb+z/4KB/3bc308PHnEMEB5TYvVpK7M3kg2CTjos5lh733qQKwqYamgFjpr5oPUxRYwRvj41GYpcLN3Tr4WOi4lfevR5eVFBu1Jc9baY8JIOvcCL5WhXsEemnx5dUhcnP2ozloFCvU1IS60KHkXtF4NJl89tu5EECquTN5Lf9cvtIhlbmPsVg=="
|
||||
},
|
||||
"gender": {
|
||||
"0": "Keine Anrede",
|
||||
"1": "Enby",
|
||||
"2": "Herr",
|
||||
"4": "Frau"
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
|
||||
|
||||
@@ -72,7 +72,13 @@
|
||||
},
|
||||
"checkForUpdates": 900000,
|
||||
"licence": {
|
||||
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
|
||||
"scandit": "ApI1Sg5/KzWICPhn0CbflI4cjgXiFkKnhgEmUvAt0D3CWsck0SCIm557hJvGTqXD0A1mt3IvEwA8eQ1CBXL3EoA9lASWUcLt+FSWP9gHao+LT6Oz9QMrsJoJRC/OboodKS4/F+ZVa02eUT53X0GX9jJeEnh8EerqZimrl6oDjNLvHCzBnxtmsMZidAUaErqK5yaQQCkUKcfTQhRzVhqvsjdENR76EmrrvCSr93oMCnnDHjcyOz8O3stcNrCOGbxwDBIMwf05CzoEBhxF9SoT/fwvfWa9BRvlMTxl9lUUE2f3Aw9+0CBG7PYgZ9XGCiCM2Wy9T5cf0C5MOLr2MAIgQrlDua0oEtmC5FjFojQHJEITRmLyQnAyUU8Y+jkUUCtQNmweZKtTQT9le+G9QCzRoSsmVsVxCKxII3+8iLA+Lh9meI/vJSaQ2VpDgZVrV1Vc4V/z0Exu7Ap7R0MpzCy4hhRzdpWgf4OG815ZctwMvM7qfBqykG98B9oP3YC/LG+cpVK4Ipx5VnWBUIwVHDiocHhMjyroTBAeH3BfeCdOEpKwNK9vOxrbvUpqAc8ZOaaal08hWgFVFR8BQN+rEkiqKx5fONRXefkkyUWMdqwvoBn7ZzCr+F9GmD52Q7ZuDcyAigm2ArwDipT9QUH7gxphogcKRNAWAyaENWWKRBFWmeNYXJOuGXLr05Zye7joGFbJHxPYyUJ6KMombmYlI2GHc4JQGJNneDuL93v8AkYcfM2WfOjf0hm+SVw0TP95bD1xJwSCoKO5L6FxqMu5GPcOqik3sjGHg6XsEr10m7jt7G2PfNTgS3ft+TP32OjeSu9M80Fu3nmeqLM4a4lx9fFbGMpWDEU9wdftmPByeW7KoXyS2bkd1Mg9UP8ci0nKo2d/ThPn+iMEjXSibNeIGibaIT4ys3pjDLW2mx0IxQpwnuQfZzAHBszpMkwKuG4+FrqXff9+bZGuRQlVwhm2v5xbKRjgi99i14QkuNVUkePxllLZGFgQH0DjsLINZb+R9stBYjQmMGc2kkJfyhHx2/zvT2D2mpgcaq0ZRKco2CqDiLUJoHwLPse/fu4PWvqSKVpwiqhCehuloWTE3jkFUncwDI50hPvrfCx92cOJUE8RTsQgDoAh7JKsPHctSZfUicZyLHOuHMhtIoxs6l/lzFowbhZob/Uo7aRsipOcP/iZgEPnMEu/obXcLR3QrlQ+WuEppU4g2MZ9+xENLCqVDLEHiz+jZIvl42ePc4Ip4lJ5RQFcSSR1yYb+z/4KB/3bc308PHnEMEB5TYvVpK7M3kg2CTjos5lh733qQKwqYamgFjpr5oPUxRYwRvj41GYpcLN3Tr4WOi4lfevR5eVFBu1Jc9baY8JIOvcCL5WhXsEemnx5dUhcnP2ozloFCvU1IS60KHkXtF4NJl89tu5EECquTN5Lf9cvtIhlbmPsVg=="
|
||||
},
|
||||
"gender": {
|
||||
"0": "Keine Anrede",
|
||||
"1": "Enby",
|
||||
"2": "Herr",
|
||||
"4": "Frau"
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -74,7 +74,13 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
|
||||
"scandit": "ApI1Sg5/KzWICPhn0CbflI4cjgXiFkKnhgEmUvAt0D3CWsck0SCIm557hJvGTqXD0A1mt3IvEwA8eQ1CBXL3EoA9lASWUcLt+FSWP9gHao+LT6Oz9QMrsJoJRC/OboodKS4/F+ZVa02eUT53X0GX9jJeEnh8EerqZimrl6oDjNLvHCzBnxtmsMZidAUaErqK5yaQQCkUKcfTQhRzVhqvsjdENR76EmrrvCSr93oMCnnDHjcyOz8O3stcNrCOGbxwDBIMwf05CzoEBhxF9SoT/fwvfWa9BRvlMTxl9lUUE2f3Aw9+0CBG7PYgZ9XGCiCM2Wy9T5cf0C5MOLr2MAIgQrlDua0oEtmC5FjFojQHJEITRmLyQnAyUU8Y+jkUUCtQNmweZKtTQT9le+G9QCzRoSsmVsVxCKxII3+8iLA+Lh9meI/vJSaQ2VpDgZVrV1Vc4V/z0Exu7Ap7R0MpzCy4hhRzdpWgf4OG815ZctwMvM7qfBqykG98B9oP3YC/LG+cpVK4Ipx5VnWBUIwVHDiocHhMjyroTBAeH3BfeCdOEpKwNK9vOxrbvUpqAc8ZOaaal08hWgFVFR8BQN+rEkiqKx5fONRXefkkyUWMdqwvoBn7ZzCr+F9GmD52Q7ZuDcyAigm2ArwDipT9QUH7gxphogcKRNAWAyaENWWKRBFWmeNYXJOuGXLr05Zye7joGFbJHxPYyUJ6KMombmYlI2GHc4JQGJNneDuL93v8AkYcfM2WfOjf0hm+SVw0TP95bD1xJwSCoKO5L6FxqMu5GPcOqik3sjGHg6XsEr10m7jt7G2PfNTgS3ft+TP32OjeSu9M80Fu3nmeqLM4a4lx9fFbGMpWDEU9wdftmPByeW7KoXyS2bkd1Mg9UP8ci0nKo2d/ThPn+iMEjXSibNeIGibaIT4ys3pjDLW2mx0IxQpwnuQfZzAHBszpMkwKuG4+FrqXff9+bZGuRQlVwhm2v5xbKRjgi99i14QkuNVUkePxllLZGFgQH0DjsLINZb+R9stBYjQmMGc2kkJfyhHx2/zvT2D2mpgcaq0ZRKco2CqDiLUJoHwLPse/fu4PWvqSKVpwiqhCehuloWTE3jkFUncwDI50hPvrfCx92cOJUE8RTsQgDoAh7JKsPHctSZfUicZyLHOuHMhtIoxs6l/lzFowbhZob/Uo7aRsipOcP/iZgEPnMEu/obXcLR3QrlQ+WuEppU4g2MZ9+xENLCqVDLEHiz+jZIvl42ePc4Ip4lJ5RQFcSSR1yYb+z/4KB/3bc308PHnEMEB5TYvVpK7M3kg2CTjos5lh733qQKwqYamgFjpr5oPUxRYwRvj41GYpcLN3Tr4WOi4lfevR5eVFBu1Jc9baY8JIOvcCL5WhXsEemnx5dUhcnP2ozloFCvU1IS60KHkXtF4NJl89tu5EECquTN5Lf9cvtIhlbmPsVg=="
|
||||
},
|
||||
"gender": {
|
||||
"0": "Keine Anrede",
|
||||
"1": "Enby",
|
||||
"2": "Herr",
|
||||
"4": "Frau"
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -73,7 +73,13 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
|
||||
"scandit": "AqeFthV/MMKEPoQNIB3vSLYDthFhJ0F03QQtH+4f/ivCRKdYPjxnMU94RVMEaldhNTQj5sB8mJVNWq0vbHCzqul36KcLdG0kwFfBiXAoEkfOe2bRGTCX4bASLuwfHVv8+AG+Pwdhelf3XMcIvnrGkiR91EmRGeebiiVX448H6U+WdyXJtns/W/ZgdMZBGUHA4WsG9Ct5SyLGaeGYI1iK8LZtpKzLBcsvYwOkDeI4IjIuOZpMpy6CzmBAHQlAHWKmwjDizWwB2rU2BZ6i+3H99mpvSz9PCWm9PS+Jr30Me13bb+Oz5zHxeMdoJtZNIhsTgWyVErUlcuceNIibrDj84L4qfmHKPQtalW10+w9ODwRIS+nLCWOpo9we+UCsFgMJLX3lH0ZIXEWEZjAxhzXmdxxGEcroQQXAIH3PsN8RUSuaC6PPOirvKmZUbW3XUun+vmxMWxhDyGwnat9CpDpQ+CpsyPECaUEkbkVEg/Mq42QxVHF25UgZ37c5gN0wHNAjal/HSpZfLBzDeC5Y/iKqgKhjf68Bf7lnOBYokWFUxBRdcKLhtz3nGNZL3UpabCULf2J5lOZ0Mj8hdC+P4UNZMCRVEs4UR9pqHEoJVdQ5KMO8cXcvKGxGG4hT9UNNbk9o+jNWOlhCribVTmkj6kmInB8zDFkceKy3HUSC8p9TlzjxfBpzN0oVMG9DIlkTWh3nryr4lDJimIz4AriI6Xft3NJgiIkUVAjsTHl33IRz7jwyQOzyUR197o0ojRwvLpm3zkHwap4lyvMz7SoYhKoyN66Aul3K5iic8hVVUuriChEZyIwYml71vAfIpmwZC5GVF5XJBT1r0OG+xmJbH6sae0gOnMpFyed3R+BH/AU2OY9nrnTNLN83pk8huK7BPQVnpAQqU76IrZP5GAyd1GNRi9WWJo4mHefe/9/x22lfHg7BELhOXoJlDZWoj4uOYJVVycQD/ninQMtwkry/9VEOQmrg76bRVbz2OZ3RFDw/AXBd+1jA0K7gIP9G5dN4x2NjNbzPE4gMhHvNi+vUy8x+1uHRQdBzI/F7EkuFb59eKZYe1EHTofnzGofZUgiuXMDDVTi8dE80PmFRCTnNvEtK8rRcBFSqWxeX1u5h+PR8btTagOH0hNCNWUaVYpIcHXvKP5gFFluPy5+RR3TRXYhBKOeb+Q2uxrowwVJs2FOxwfORtKXti8pGIw9bVZk9yNCqafPuURqGKMw/e+H8ElIWaiWH2mcY9dDKN6lBhOdKYjhKQZ/4t9H+ZbyeKzebH8habpAZEDdN0fbqDMwWTD+eKo02s5wOzJ+0z4BpDszMd2acvQ1GVrF0KajpReco2FE+AQpbxHM2KQfKqiDHOUd02nG9AHjKRf/xUTySpD8pG3DvPZC+ImqQZN/t7/IegQptgrezq2w2y0L89sgNnnhRopaMRt/sPSirTXh/NA=="
|
||||
},
|
||||
"gender": {
|
||||
"0": "Keine Anrede",
|
||||
"1": "Enby",
|
||||
"2": "Herr",
|
||||
"4": "Frau"
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -73,7 +73,13 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
|
||||
"scandit": "AqeFthV/MMKEPoQNIB3vSLYDthFhJ0F03QQtH+4f/ivCRKdYPjxnMU94RVMEaldhNTQj5sB8mJVNWq0vbHCzqul36KcLdG0kwFfBiXAoEkfOe2bRGTCX4bASLuwfHVv8+AG+Pwdhelf3XMcIvnrGkiR91EmRGeebiiVX448H6U+WdyXJtns/W/ZgdMZBGUHA4WsG9Ct5SyLGaeGYI1iK8LZtpKzLBcsvYwOkDeI4IjIuOZpMpy6CzmBAHQlAHWKmwjDizWwB2rU2BZ6i+3H99mpvSz9PCWm9PS+Jr30Me13bb+Oz5zHxeMdoJtZNIhsTgWyVErUlcuceNIibrDj84L4qfmHKPQtalW10+w9ODwRIS+nLCWOpo9we+UCsFgMJLX3lH0ZIXEWEZjAxhzXmdxxGEcroQQXAIH3PsN8RUSuaC6PPOirvKmZUbW3XUun+vmxMWxhDyGwnat9CpDpQ+CpsyPECaUEkbkVEg/Mq42QxVHF25UgZ37c5gN0wHNAjal/HSpZfLBzDeC5Y/iKqgKhjf68Bf7lnOBYokWFUxBRdcKLhtz3nGNZL3UpabCULf2J5lOZ0Mj8hdC+P4UNZMCRVEs4UR9pqHEoJVdQ5KMO8cXcvKGxGG4hT9UNNbk9o+jNWOlhCribVTmkj6kmInB8zDFkceKy3HUSC8p9TlzjxfBpzN0oVMG9DIlkTWh3nryr4lDJimIz4AriI6Xft3NJgiIkUVAjsTHl33IRz7jwyQOzyUR197o0ojRwvLpm3zkHwap4lyvMz7SoYhKoyN66Aul3K5iic8hVVUuriChEZyIwYml71vAfIpmwZC5GVF5XJBT1r0OG+xmJbH6sae0gOnMpFyed3R+BH/AU2OY9nrnTNLN83pk8huK7BPQVnpAQqU76IrZP5GAyd1GNRi9WWJo4mHefe/9/x22lfHg7BELhOXoJlDZWoj4uOYJVVycQD/ninQMtwkry/9VEOQmrg76bRVbz2OZ3RFDw/AXBd+1jA0K7gIP9G5dN4x2NjNbzPE4gMhHvNi+vUy8x+1uHRQdBzI/F7EkuFb59eKZYe1EHTofnzGofZUgiuXMDDVTi8dE80PmFRCTnNvEtK8rRcBFSqWxeX1u5h+PR8btTagOH0hNCNWUaVYpIcHXvKP5gFFluPy5+RR3TRXYhBKOeb+Q2uxrowwVJs2FOxwfORtKXti8pGIw9bVZk9yNCqafPuURqGKMw/e+H8ElIWaiWH2mcY9dDKN6lBhOdKYjhKQZ/4t9H+ZbyeKzebH8habpAZEDdN0fbqDMwWTD+eKo02s5wOzJ+0z4BpDszMd2acvQ1GVrF0KajpReco2FE+AQpbxHM2KQfKqiDHOUd02nG9AHjKRf/xUTySpD8pG3DvPZC+ImqQZN/t7/IegQptgrezq2w2y0L89sgNnnhRopaMRt/sPSirTXh/NA=="
|
||||
},
|
||||
"gender": {
|
||||
"0": "Keine Anrede",
|
||||
"1": "Enby",
|
||||
"2": "Herr",
|
||||
"4": "Frau"
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -74,7 +74,12 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
|
||||
},
|
||||
"scandit": "ApI1Sg5/KzWICPhn0CbflI4cjgXiFkKnhgEmUvAt0D3CWsck0SCIm557hJvGTqXD0A1mt3IvEwA8eQ1CBXL3EoA9lASWUcLt+FSWP9gHao+LT6Oz9QMrsJoJRC/OboodKS4/F+ZVa02eUT53X0GX9jJeEnh8EerqZimrl6oDjNLvHCzBnxtmsMZidAUaErqK5yaQQCkUKcfTQhRzVhqvsjdENR76EmrrvCSr93oMCnnDHjcyOz8O3stcNrCOGbxwDBIMwf05CzoEBhxF9SoT/fwvfWa9BRvlMTxl9lUUE2f3Aw9+0CBG7PYgZ9XGCiCM2Wy9T5cf0C5MOLr2MAIgQrlDua0oEtmC5FjFojQHJEITRmLyQnAyUU8Y+jkUUCtQNmweZKtTQT9le+G9QCzRoSsmVsVxCKxII3+8iLA+Lh9meI/vJSaQ2VpDgZVrV1Vc4V/z0Exu7Ap7R0MpzCy4hhRzdpWgf4OG815ZctwMvM7qfBqykG98B9oP3YC/LG+cpVK4Ipx5VnWBUIwVHDiocHhMjyroTBAeH3BfeCdOEpKwNK9vOxrbvUpqAc8ZOaaal08hWgFVFR8BQN+rEkiqKx5fONRXefkkyUWMdqwvoBn7ZzCr+F9GmD52Q7ZuDcyAigm2ArwDipT9QUH7gxphogcKRNAWAyaENWWKRBFWmeNYXJOuGXLr05Zye7joGFbJHxPYyUJ6KMombmYlI2GHc4JQGJNneDuL93v8AkYcfM2WfOjf0hm+SVw0TP95bD1xJwSCoKO5L6FxqMu5GPcOqik3sjGHg6XsEr10m7jt7G2PfNTgS3ft+TP32OjeSu9M80Fu3nmeqLM4a4lx9fFbGMpWDEU9wdftmPByeW7KoXyS2bkd1Mg9UP8ci0nKo2d/ThPn+iMEjXSibNeIGibaIT4ys3pjDLW2mx0IxQpwnuQfZzAHBszpMkwKuG4+FrqXff9+bZGuRQlVwhm2v5xbKRjgi99i14QkuNVUkePxllLZGFgQH0DjsLINZb+R9stBYjQmMGc2kkJfyhHx2/zvT2D2mpgcaq0ZRKco2CqDiLUJoHwLPse/fu4PWvqSKVpwiqhCehuloWTE3jkFUncwDI50hPvrfCx92cOJUE8RTsQgDoAh7JKsPHctSZfUicZyLHOuHMhtIoxs6l/lzFowbhZob/Uo7aRsipOcP/iZgEPnMEu/obXcLR3QrlQ+WuEppU4g2MZ9+xENLCqVDLEHiz+jZIvl42ePc4Ip4lJ5RQFcSSR1yYb+z/4KB/3bc308PHnEMEB5TYvVpK7M3kg2CTjos5lh733qQKwqYamgFjpr5oPUxRYwRvj41GYpcLN3Tr4WOi4lfevR5eVFBu1Jc9baY8JIOvcCL5WhXsEemnx5dUhcnP2ozloFCvU1IS60KHkXtF4NJl89tu5EECquTN5Lf9cvtIhlbmPsVg==" },
|
||||
"gender": {
|
||||
"0": "Keine Anrede",
|
||||
"1": "Enby",
|
||||
"2": "Herr",
|
||||
"4": "Frau"
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -15,7 +15,7 @@
|
||||
<app-root>
|
||||
<div class="grid place-items-center h-screen">
|
||||
<div class="grid grid-flow-row gap-4 items-center justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<div id="init-loader" class="flex flex-col items-center">
|
||||
<img class="animate-spin" src="/assets/images/spinner.svg" alt="spinner animation" />
|
||||
</div>
|
||||
<div id="init-status" class="text-center">App wird geladen</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
@@ -57,4 +57,3 @@ import 'zone.js'; // Included with Angular CLI.
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
import 'hammerjs';
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
Observable,
|
||||
of,
|
||||
BehaviorSubject,
|
||||
Subject,
|
||||
merge,
|
||||
combineLatest,
|
||||
} from 'rxjs';
|
||||
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 { 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 { 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';
|
||||
@@ -41,33 +28,21 @@ import { ActionResult, CapacityType } from '../models';
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MockRemissionService extends RemissionService {
|
||||
private delayInMs = 100;
|
||||
private remissionSubjectStore = new Map<
|
||||
number,
|
||||
BehaviorSubject<RemissionProcess>
|
||||
>();
|
||||
private remissionSubjectStore = new Map<number, BehaviorSubject<RemissionProcess>>();
|
||||
private remissionSubjectIdRef = new Map<number, number>();
|
||||
|
||||
private reloadProductsSubject = new Subject();
|
||||
private productSubject = new BehaviorSubject<RemissionProduct[]>(
|
||||
remissionProducts
|
||||
);
|
||||
private reloadProductsSubject = new Subject<void>();
|
||||
private productSubject = new BehaviorSubject<RemissionProduct[]>(remissionProducts);
|
||||
|
||||
private getRemissionFromStore = (
|
||||
remissionProcessId: number
|
||||
): BehaviorSubject<RemissionProcess> => {
|
||||
private getRemissionFromStore = (remissionProcessId: number): BehaviorSubject<RemissionProcess> => {
|
||||
const remission =
|
||||
this.remissionSubjectStore.get(remissionProcessId) ||
|
||||
this.remissionSubjectStore.get(
|
||||
this.remissionSubjectIdRef.get(remissionProcessId)
|
||||
);
|
||||
this.remissionSubjectStore.get(this.remissionSubjectIdRef.get(remissionProcessId));
|
||||
|
||||
return remission;
|
||||
};
|
||||
|
||||
private saveRemissionInStore = (
|
||||
remissionProcessId: number,
|
||||
remissionSubject: BehaviorSubject<RemissionProcess>
|
||||
) => {
|
||||
private saveRemissionInStore = (remissionProcessId: number, remissionSubject: BehaviorSubject<RemissionProcess>) => {
|
||||
this.remissionSubjectStore.set(remissionProcessId, remissionSubject);
|
||||
};
|
||||
|
||||
@@ -80,27 +55,20 @@ export class MockRemissionService extends RemissionService {
|
||||
return processSubject.pipe(distinctUntilChanged(), delay(this.delayInMs));
|
||||
}
|
||||
|
||||
getRemissionProducts(params: {
|
||||
remissionProcessId: number;
|
||||
}): Observable<{
|
||||
getRemissionProducts(params: { remissionProcessId: number }): Observable<{
|
||||
skip?: number;
|
||||
take?: number;
|
||||
hits?: number;
|
||||
items: RemissionProduct[];
|
||||
completed: boolean;
|
||||
}> {
|
||||
const processSubject = this.getRemissionFromStore(
|
||||
params.remissionProcessId
|
||||
);
|
||||
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 process$ = merge(processSubject, this.reloadProductsSubject.pipe(map((_) => processSubject.value)));
|
||||
|
||||
const products$ = this.productSubject.asObservable();
|
||||
|
||||
@@ -126,24 +94,19 @@ export class MockRemissionService extends RemissionService {
|
||||
const productGroupFilter = filter.filter['421228ce'] || [];
|
||||
|
||||
if (productGroupFilter.length > 0) {
|
||||
result.items = result.items.filter((product) =>
|
||||
productGroupFilter.some((s) => product.productGroup === s)
|
||||
);
|
||||
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.items = result.items.slice(result.skip, result.skip + result.take);
|
||||
|
||||
result.completed = result.skip + result.take >= result.hits;
|
||||
|
||||
return result;
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,9 +116,7 @@ export class MockRemissionService extends RemissionService {
|
||||
createShippingDocument?: boolean;
|
||||
};
|
||||
}): Observable<RemissionProcess> {
|
||||
const processSubject = this.remissionSubjectStore.get(
|
||||
params.remissionProcessId
|
||||
);
|
||||
const processSubject = this.remissionSubjectStore.get(params.remissionProcessId);
|
||||
|
||||
if (!processSubject) {
|
||||
throw new Error('Prozess nicht gefunden');
|
||||
@@ -214,12 +175,8 @@ export class MockRemissionService extends RemissionService {
|
||||
}
|
||||
}
|
||||
|
||||
completeRemission(params: {
|
||||
remissionProcessId: number;
|
||||
}): Observable<boolean> {
|
||||
let processSubject = this.remissionSubjectStore.get(
|
||||
params.remissionProcessId
|
||||
);
|
||||
completeRemission(params: { remissionProcessId: number }): Observable<boolean> {
|
||||
let processSubject = this.remissionSubjectStore.get(params.remissionProcessId);
|
||||
|
||||
if (!processSubject) {
|
||||
const refId = this.remissionSubjectIdRef.get(params.remissionProcessId);
|
||||
@@ -236,9 +193,7 @@ export class MockRemissionService extends RemissionService {
|
||||
}
|
||||
}
|
||||
|
||||
getRemission(params: {
|
||||
remissionProcessId: number;
|
||||
}): Observable<RemissionProcess> {
|
||||
getRemission(params: { remissionProcessId: number }): Observable<RemissionProcess> {
|
||||
const remission = this.getRemissionFromStore(params.remissionProcessId);
|
||||
|
||||
if (!remission) {
|
||||
@@ -248,11 +203,7 @@ export class MockRemissionService extends RemissionService {
|
||||
return remission.pipe(distinctUntilChanged(), delay(this.delayInMs));
|
||||
}
|
||||
|
||||
isPrintingRequired({
|
||||
remissionProcessId,
|
||||
}: {
|
||||
remissionProcessId: number;
|
||||
}): Observable<boolean> {
|
||||
isPrintingRequired({ remissionProcessId }: { remissionProcessId: number }): Observable<boolean> {
|
||||
const remission = this.getRemissionFromStore(remissionProcessId);
|
||||
|
||||
if (!remission) {
|
||||
@@ -272,10 +223,7 @@ export class MockRemissionService extends RemissionService {
|
||||
return of(printers).pipe(delay(this.delayInMs));
|
||||
}
|
||||
|
||||
printRemissionList(params: {
|
||||
remissionProcessId: number;
|
||||
printerKey: string;
|
||||
}): Observable<boolean> {
|
||||
printRemissionList(params: { remissionProcessId: number; printerKey: string }): Observable<boolean> {
|
||||
const remission = this.getRemissionFromStore(params.remissionProcessId);
|
||||
|
||||
if (!remission) {
|
||||
@@ -285,11 +233,7 @@ export class MockRemissionService extends RemissionService {
|
||||
return of(true).pipe(delay(this.delayInMs));
|
||||
}
|
||||
|
||||
printShippingDocument(params: {
|
||||
remissionProcessId: number;
|
||||
shippingDocumentId?: number;
|
||||
printerKey: string;
|
||||
}): Observable<boolean> {
|
||||
printShippingDocument(params: { remissionProcessId: number; shippingDocumentId?: number; printerKey: string }): Observable<boolean> {
|
||||
const remission = this.getRemissionFromStore(params.remissionProcessId);
|
||||
|
||||
if (!remission || !params.printerKey) {
|
||||
@@ -307,8 +251,7 @@ export class MockRemissionService extends RemissionService {
|
||||
getFilters(params: { remissionProcessId: number }): Observable<Filter[]> {
|
||||
const remission = this.getRemissionFromStore(params.remissionProcessId);
|
||||
|
||||
const filterForGivenSourceType =
|
||||
remission.value.filter.source === 'zentral' ? filter : filterUeberlauf;
|
||||
const filterForGivenSourceType = remission.value.filter.source === 'zentral' ? filter : filterUeberlauf;
|
||||
return of(filterForGivenSourceType).pipe(delay(this.delayInMs));
|
||||
}
|
||||
|
||||
@@ -327,38 +270,24 @@ export class MockRemissionService extends RemissionService {
|
||||
return of(remissionSources).pipe(delay(this.delayInMs));
|
||||
}
|
||||
|
||||
getShippingDocuments(params: {
|
||||
remissionProcessId: number;
|
||||
shippingDocumentId: number;
|
||||
}): Observable<ShippingDocument> {
|
||||
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
|
||||
)
|
||||
),
|
||||
map((remission) => remission.shippingDocuments.find((shippingDoc) => shippingDoc.id === params.shippingDocumentId)),
|
||||
distinctUntilChanged(),
|
||||
delay(this.delayInMs)
|
||||
delay(this.delayInMs),
|
||||
);
|
||||
}
|
||||
|
||||
deleteProductFromRemissionList(input: {
|
||||
remissionProcessId: number;
|
||||
remissionProductId: number;
|
||||
}): Observable<ActionResult<boolean>> {
|
||||
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> {
|
||||
addProductToRemit(input: { product: Product; remissionReason: string; remissionQuantity: number }): Observable<RemissionProduct> {
|
||||
const productToAdd = removeSourcePriceAndProductFromProduct({ ...product });
|
||||
|
||||
const newProduct = createRemissionProduct({
|
||||
@@ -382,9 +311,7 @@ export class MockRemissionService extends RemissionService {
|
||||
quantity?: number;
|
||||
inStock?: number;
|
||||
}): Observable<ActionResult<boolean>> {
|
||||
const processSubject = this.getRemissionFromStore(
|
||||
params.remissionProcessId
|
||||
);
|
||||
const processSubject = this.getRemissionFromStore(params.remissionProcessId);
|
||||
const process = processSubject.value;
|
||||
const products = this.productSubject.value;
|
||||
|
||||
@@ -392,22 +319,15 @@ export class MockRemissionService extends RemissionService {
|
||||
throw new Error('Prozess nicht gefunden');
|
||||
}
|
||||
|
||||
let productToAdd = products.find(
|
||||
(product) => product.id === params.remissionProductId
|
||||
);
|
||||
let newProducts = products.filter(
|
||||
(product) => product.id !== params.remissionProductId
|
||||
);
|
||||
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];
|
||||
newProducts = productToAdd.remissionQuantity - params.quantity < 1 ? newProducts : [...newProducts, productToUpdateInProductList];
|
||||
|
||||
productToAdd = {
|
||||
...productToAdd,
|
||||
@@ -415,9 +335,7 @@ export class MockRemissionService extends RemissionService {
|
||||
};
|
||||
}
|
||||
|
||||
const shippingDocumentToUpdate = process.shippingDocuments.find(
|
||||
(document) => document.id === params.shippingDocumentId
|
||||
);
|
||||
const shippingDocumentToUpdate = process.shippingDocuments.find((document) => document.id === params.shippingDocumentId);
|
||||
const updatedShippingDocument = {
|
||||
...shippingDocumentToUpdate,
|
||||
products: [...shippingDocumentToUpdate.products, productToAdd],
|
||||
@@ -426,9 +344,7 @@ export class MockRemissionService extends RemissionService {
|
||||
const updatedProcess = {
|
||||
...process,
|
||||
shippingDocuments: [
|
||||
...process.shippingDocuments.filter(
|
||||
(document) => document.id !== params.shippingDocumentId
|
||||
),
|
||||
...process.shippingDocuments.filter((document) => document.id !== params.shippingDocumentId),
|
||||
updatedShippingDocument,
|
||||
],
|
||||
};
|
||||
@@ -448,9 +364,7 @@ export class MockRemissionService extends RemissionService {
|
||||
remissionProductId: number;
|
||||
placementType?: string;
|
||||
}): Observable<boolean> {
|
||||
const processSubject = this.getRemissionFromStore(
|
||||
params.remissionProcessId
|
||||
);
|
||||
const processSubject = this.getRemissionFromStore(params.remissionProcessId);
|
||||
const process = processSubject.value;
|
||||
const products = this.productSubject.value;
|
||||
|
||||
@@ -458,44 +372,30 @@ export class MockRemissionService extends RemissionService {
|
||||
throw new Error('Prozess nicht gefunden');
|
||||
}
|
||||
|
||||
const shippingDocumentToUpdate = process.shippingDocuments.find(
|
||||
(document) => document.id === params.shippingDocumentId
|
||||
);
|
||||
const shippingDocumentToUpdate = process.shippingDocuments.find((document) => document.id === params.shippingDocumentId);
|
||||
const updatedShippingDocument = {
|
||||
...shippingDocumentToUpdate,
|
||||
products: shippingDocumentToUpdate.products.filter(
|
||||
(product) => product.id !== params.remissionProductId
|
||||
),
|
||||
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 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),
|
||||
remissionQuantity: Number(productInProductList.remissionQuantity) + Number(productToRemove.remissionQuantity),
|
||||
}
|
||||
: productToRemove;
|
||||
|
||||
const updatedProcess = {
|
||||
...process,
|
||||
shippingDocuments: [
|
||||
...process.shippingDocuments.filter(
|
||||
(document) => document.id !== params.shippingDocumentId
|
||||
),
|
||||
...process.shippingDocuments.filter((document) => document.id !== params.shippingDocumentId),
|
||||
updatedShippingDocument,
|
||||
],
|
||||
};
|
||||
|
||||
const updatedProducts = [
|
||||
...this.productSubject.value.filter(
|
||||
(product) => product.id !== updatedProductInProductList.id
|
||||
),
|
||||
...this.productSubject.value.filter((product) => product.id !== updatedProductInProductList.id),
|
||||
updatedProductInProductList,
|
||||
];
|
||||
|
||||
@@ -509,12 +409,8 @@ export class MockRemissionService extends RemissionService {
|
||||
this.reloadProductsSubject.next();
|
||||
}
|
||||
|
||||
createShippingDocument(params: {
|
||||
remissionProcessId: number;
|
||||
}): Observable<ShippingDocument> {
|
||||
const processSubject = this.getRemissionFromStore(
|
||||
params.remissionProcessId
|
||||
);
|
||||
createShippingDocument(params: { remissionProcessId: number }): Observable<ShippingDocument> {
|
||||
const processSubject = this.getRemissionFromStore(params.remissionProcessId);
|
||||
|
||||
if (!processSubject) {
|
||||
throw new Error('Prozess nicht gefunden');
|
||||
@@ -526,10 +422,7 @@ export class MockRemissionService extends RemissionService {
|
||||
id: createRandomId(),
|
||||
shippingDocumentNumber: createRandomStringId(),
|
||||
};
|
||||
const updatedShippingDocuments = [
|
||||
...process.shippingDocuments,
|
||||
newShippingDocument,
|
||||
];
|
||||
const updatedShippingDocuments = [...process.shippingDocuments, newShippingDocument];
|
||||
const updatedProcess = {
|
||||
...process,
|
||||
shippingDocuments: updatedShippingDocuments,
|
||||
@@ -545,18 +438,14 @@ export class MockRemissionService extends RemissionService {
|
||||
shippingDocumentId: number;
|
||||
containerId: string;
|
||||
}): Observable<ActionResult<boolean>> {
|
||||
const processSubject = this.getRemissionFromStore(
|
||||
params.remissionProcessId
|
||||
);
|
||||
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 shippingContainerToUpdate = process.shippingDocuments.find((document) => document.id === params.shippingDocumentId);
|
||||
const updatedShippingContainer = {
|
||||
...shippingContainerToUpdate,
|
||||
isCompleted: true,
|
||||
@@ -564,9 +453,7 @@ export class MockRemissionService extends RemissionService {
|
||||
const updatedProcess = {
|
||||
...process,
|
||||
shippingDocuments: [
|
||||
...process.shippingDocuments.filter(
|
||||
(document) => document.id !== params.shippingDocumentId
|
||||
),
|
||||
...process.shippingDocuments.filter((document) => document.id !== params.shippingDocumentId),
|
||||
updatedShippingContainer,
|
||||
],
|
||||
};
|
||||
@@ -579,9 +466,7 @@ export class MockRemissionService extends RemissionService {
|
||||
}).pipe(delay(this.delayInMs));
|
||||
}
|
||||
|
||||
completeRemissions(params: {
|
||||
remissionProcessId: number;
|
||||
}): Observable<boolean> {
|
||||
completeRemissions(params: { remissionProcessId: number }): Observable<boolean> {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -605,9 +490,7 @@ export class MockRemissionService extends RemissionService {
|
||||
return of(true);
|
||||
}
|
||||
|
||||
deleteShippingDocument(): Observable<
|
||||
ActionResult<{ deleted: boolean; completedRemissionsExist: boolean }>
|
||||
> {
|
||||
deleteShippingDocument(): Observable<ActionResult<{ deleted: boolean; completedRemissionsExist: boolean }>> {
|
||||
return of({
|
||||
result: {
|
||||
deleted: true,
|
||||
@@ -620,10 +503,7 @@ export class MockRemissionService extends RemissionService {
|
||||
return;
|
||||
}
|
||||
|
||||
getCapacities(params: {
|
||||
selectedFilters?: { [filterId: string]: string[] };
|
||||
supplierId: number;
|
||||
}): Observable<CapacityType[]> {
|
||||
getCapacities(params: { selectedFilters?: { [filterId: string]: string[] }; supplierId: number }): Observable<CapacityType[]> {
|
||||
return of([]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,11 @@
|
||||
</div>
|
||||
|
||||
<div class="branch-actions">
|
||||
<button *ngIf="(branch.id | stockInfo: (inStock$ | async))?.availableQuantity > 0" class="cta-reserve" (click)="reserve(branch)">
|
||||
<button
|
||||
*ngIf="(branch.id | stockInfo: (inStock$ | async))?.availableQuantity > 0 && branch?.isShippingEnabled"
|
||||
class="cta-reserve"
|
||||
(click)="reserve(branch)"
|
||||
>
|
||||
Reservieren
|
||||
</button>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export class ModalAvailabilitiesComponent {
|
||||
(branch) =>
|
||||
branch &&
|
||||
branch?.isOnline &&
|
||||
branch?.isShippingEnabled &&
|
||||
// branch?.isShippingEnabled && ------ Rausgenommen aufgrund des Tickets #4712
|
||||
branch?.isOrderingEnabled &&
|
||||
branch?.id !== userbranch?.id &&
|
||||
branch?.branchType === 1
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
}
|
||||
|
||||
.page-price-update-item__item-card {
|
||||
@apply grid grid-flow-col;
|
||||
grid-template-columns: 63px auto minmax(230px, auto);
|
||||
@apply grid grid-flow-col gap-2;
|
||||
grid-template-columns: 63px 1fr 14rem;
|
||||
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
|
||||
}
|
||||
|
||||
@@ -20,5 +20,11 @@
|
||||
|
||||
.page-price-update-item__item-addition {
|
||||
@apply grid grid-flow-row justify-items-end;
|
||||
grid-template-rows: 27px 27px 41px 52px auto;
|
||||
grid-template-rows: auto auto 2.56rem 3.25rem auto;
|
||||
}
|
||||
|
||||
.page-price-update-item__item-product-group-details,
|
||||
.page-price-update-item__item-title {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="page-price-update-item__item-card p-5 h-[212px] bg-white">
|
||||
<div class="page-price-update-item__item-thumbnail text-center mr-4 w-[47px] h-[73px]">
|
||||
<img
|
||||
class="page-price-update-item__item-image w-[47px] h-[73px]"
|
||||
class="page-price-update-item__item-image w-[47px] max-h-[73px]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.product?.ean | productImage; let productImage"
|
||||
[src]="productImage"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="page-article-details__wrapper">
|
||||
<div #detailsContainer class="page-article-details__container px-5" *ngIf="store.item$ | async; let item">
|
||||
<div class="page-article-details__product-details mb-3">
|
||||
<div class="page-article-details__product-bookmark justify-self-end">
|
||||
<div class="page-article-details__product-bookmark flex fixed justify-self-end">
|
||||
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
||||
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
|
||||
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
||||
@@ -30,9 +30,9 @@
|
||||
</div>
|
||||
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
|
||||
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
|
||||
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
|
||||
<shared-icon-badge icon="gift" alt="Prämienkatalog Badge"></shared-icon-badge>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
|
||||
Dieser Artikel befindet sich im Prämienkatalog.
|
||||
Der Artikel ist als Prämie für {{ promotionPoints$ | async }} Punkte erhältlich.
|
||||
</ui-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -58,11 +58,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.store.isDownloadAvailabilityAvailable$,
|
||||
]).pipe(
|
||||
map((values) => values.some((v) => v)),
|
||||
shareReplay()
|
||||
shareReplay(),
|
||||
);
|
||||
|
||||
showDeliveryTruck$ = combineLatest([this.store.isDeliveryAvailabilityAvailable$, this.store.isDeliveryDigAvailabilityAvailable$]).pipe(
|
||||
map(([delivery, digDelivery]) => delivery || digDelivery)
|
||||
map(([delivery, digDelivery]) => delivery || digDelivery),
|
||||
);
|
||||
|
||||
showDeliveryB2BTruck$ = combineLatest([
|
||||
@@ -71,17 +71,22 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
]).pipe(map(([digDelivery, b2bDelivery]) => b2bDelivery && !digDelivery));
|
||||
|
||||
customerFeatures$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this._domainCheckoutService.getCustomerFeatures({ processId }))
|
||||
switchMap((processId) => this._domainCheckoutService.getCustomerFeatures({ processId })),
|
||||
);
|
||||
|
||||
showSubscriptionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'PFO')));
|
||||
|
||||
showPromotionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'Promotion')));
|
||||
hasPromotionFeature$ = this.store.item$.pipe(map((item) => !!item?.features?.find((i) => i.key === 'Promotion')));
|
||||
promotionPoints$ = this.store.item$.pipe(map((item) => item?.redemptionPoints));
|
||||
|
||||
showPromotionBadge$ = combineLatest([this.hasPromotionFeature$, this.promotionPoints$]).pipe(
|
||||
map(([hasPromotionFeature, promotionPoints]) => hasPromotionFeature && promotionPoints > 0),
|
||||
);
|
||||
|
||||
showArchivBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'ARC')));
|
||||
|
||||
isBadgeVisible$ = combineLatest([this.showSubscriptionBadge$, this.showPromotionBadge$, this.showArchivBadge$]).pipe(
|
||||
map(([showSubscriptionBadge, showPromotionBadge, showArchivBadge]) => showSubscriptionBadge || showPromotionBadge || showArchivBadge)
|
||||
map(([showSubscriptionBadge, showPromotionBadge, showArchivBadge]) => showSubscriptionBadge || showPromotionBadge || showArchivBadge),
|
||||
);
|
||||
|
||||
contributors$ = this.store.item$.pipe(map((item) => item?.product?.contributors?.split(';').map((m) => m.trim())));
|
||||
@@ -96,11 +101,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
return this._datePipe.transform(date, 'dd. MMMM y');
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.applicationService.getSelectedBranch$(processId))
|
||||
switchMap((processId) => this.applicationService.getSelectedBranch$(processId)),
|
||||
);
|
||||
|
||||
get isTablet$() {
|
||||
@@ -126,7 +131,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
return 'Sie sehen den Bestand einer anderen Filiale.';
|
||||
}
|
||||
return '';
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
priceMaintained$ = combineLatest([
|
||||
@@ -136,8 +141,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.store.deliveryB2BAvailability$,
|
||||
]).pipe(
|
||||
map((availabilities) => {
|
||||
return availabilities?.some((availability) => availability?.priceMaintained) ?? false;
|
||||
})
|
||||
return availabilities?.some((availability) => (availability as any)?.priceMaintained) ?? false;
|
||||
}),
|
||||
);
|
||||
|
||||
price$ = combineLatest([
|
||||
@@ -169,7 +174,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
constructor(
|
||||
@@ -188,7 +193,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
private _environment: EnvironmentService,
|
||||
private _router: Router,
|
||||
private _domainCheckoutService: DomainCheckoutService,
|
||||
private _store: Store
|
||||
private _store: Store,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -196,8 +201,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
.pipe(
|
||||
debounceTime(0),
|
||||
switchMap((params) =>
|
||||
this.applicationService.getSelectedBranch$(Number(params.processId)).pipe(map((selectedBranch) => ({ params, selectedBranch })))
|
||||
)
|
||||
this.applicationService.getSelectedBranch$(Number(params.processId)).pipe(map((selectedBranch) => ({ params, selectedBranch }))),
|
||||
),
|
||||
)
|
||||
.subscribe(({ params, selectedBranch }) => {
|
||||
const processId = Number(params.processId);
|
||||
@@ -216,13 +221,13 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
const id$ = this.activatedRoute.params.pipe(
|
||||
tap((_) => (this.showRecommendations = false)),
|
||||
map((params) => Number(params?.id) || undefined),
|
||||
filter((f) => !!f)
|
||||
filter((f) => !!f),
|
||||
);
|
||||
|
||||
const ean$ = this.activatedRoute.params.pipe(
|
||||
tap((_) => (this.showRecommendations = false)),
|
||||
map((params) => params?.ean || undefined),
|
||||
filter((f) => !!f)
|
||||
filter((f) => !!f),
|
||||
);
|
||||
|
||||
const more$ = this.activatedRoute.params.subscribe(() => (this.showMore = false));
|
||||
@@ -236,9 +241,9 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.store.item$
|
||||
.pipe(
|
||||
withLatestFrom(this.isTablet$),
|
||||
filter(([item, isTablet]) => !!item)
|
||||
filter(([item, isTablet]) => !!item),
|
||||
)
|
||||
.subscribe(([item, isTablet]) => (isTablet ? this.updateBreadcrumb(item) : this.updateBreadcrumbDesktop(item)))
|
||||
.subscribe(([item, isTablet]) => (isTablet ? this.updateBreadcrumb(item) : this.updateBreadcrumbDesktop(item))),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -338,7 +343,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
const item = await this.store.item$.pipe(first()).toPromise();
|
||||
const modal = this.uiModal.open<BranchDTO>({
|
||||
content: ModalAvailabilitiesComponent,
|
||||
title: 'Weitere Verfügbarkeiten',
|
||||
title: 'Bestände in anderen Filialen',
|
||||
data: {
|
||||
item,
|
||||
},
|
||||
@@ -349,7 +354,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
if (result?.data) {
|
||||
this.showPurchasingModal(result.data);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -420,7 +425,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
first(),
|
||||
switchMap((customerFeatures) => {
|
||||
return this._domainCheckoutService.canSetCustomer({ processId: this.applicationService.activatedProcessId, customerFeatures });
|
||||
})
|
||||
}),
|
||||
)
|
||||
.toPromise();
|
||||
this._router.navigate(nav.path, {
|
||||
@@ -434,11 +439,10 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
async navigateToResultList() {
|
||||
const processId = this.applicationService.activatedProcessId;
|
||||
let crumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog'])
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
crumbs = crumbs.filter((crumb) => !crumb.tags?.includes('details'));
|
||||
const crumb = crumbs[crumbs.length - 1];
|
||||
if (!!crumb) {
|
||||
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: crumb.params }).navigate();
|
||||
|
||||
@@ -13,6 +13,7 @@ import { UiCommonModule } from '@ui/common';
|
||||
import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { ArticleDetailsTextComponent } from './article-details-text/article-details-text.component';
|
||||
import { IconBadgeComponent } from 'apps/shared/components/icon/src/lib/badge/icon-badge.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -28,6 +29,7 @@ import { ArticleDetailsTextComponent } from './article-details-text/article-deta
|
||||
PipesModule,
|
||||
OrderDeadlinePipeModule,
|
||||
ArticleDetailsTextComponent,
|
||||
IconBadgeComponent,
|
||||
],
|
||||
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<p>Neben dem Titel "{{ item.product?.name }}" gibt es noch andere Artikel, die Sie interessieren könnten.</p>
|
||||
|
||||
<div class="articles">
|
||||
<span class="label">
|
||||
<span class="label mb-2">
|
||||
<ui-icon icon="recommendation" size="20px"></ui-icon>
|
||||
Artikel
|
||||
</span>
|
||||
@@ -28,9 +28,10 @@
|
||||
(click)="close.emit()"
|
||||
>
|
||||
<img [src]="recommendation.product?.ean | productImage: 195:315:true" alt="product-image" />
|
||||
|
||||
<span class="format">{{ recommendation.product?.formatDetail }}</span>
|
||||
<span class="price">{{ recommendation.catalogAvailability?.price?.value?.value | currency: ' ' }} EUR</span>
|
||||
<div class="flex flex-col">
|
||||
<span class="format">{{ recommendation.product?.formatDetail }}</span>
|
||||
<span class="price">{{ recommendation.catalogAvailability?.price?.value?.value | currency: ' ' }} EUR</span>
|
||||
</div>
|
||||
</a>
|
||||
</ui-slider>
|
||||
</ng-container>
|
||||
|
||||
@@ -29,12 +29,12 @@ p {
|
||||
}
|
||||
|
||||
.article {
|
||||
@apply flex flex-col mr-7 mt-4 no-underline text-black;
|
||||
@apply flex flex-col mr-7 mt-4 no-underline text-black h-full min-w-[11rem] justify-between;
|
||||
|
||||
img {
|
||||
@apply rounded-xl;
|
||||
height: 315px;
|
||||
max-width: 195px;
|
||||
max-height: 19.6875rem;
|
||||
max-width: 11rem;
|
||||
box-shadow: 0 0 15px #949393;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@ import { FilterAutocompleteProvider } from '@shared/components/filter';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
private _processId$: Observable<number>;
|
||||
|
||||
constructor(
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _articleSearch: ArticleSearchService,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _navigationService: ProductCatalogNavigationService
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -58,14 +58,14 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private articleSearch: ArticleSearchService,
|
||||
private _environment: EnvironmentService,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
public application: ApplicationService,
|
||||
private _navigationService: ProductCatalogNavigationService
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
>
|
||||
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
|
||||
<img
|
||||
class="page-search-result-item__item-image w-[3.125rem] h-[4.9375rem]"
|
||||
class="page-search-result-item__item-image w-[3.125rem] max-h-[4.9375rem]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
|
||||
[src]="thumbnailUrl"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search({filter, clear: true})"
|
||||
(search)="search({ filter, clear: true })"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
@@ -20,7 +20,7 @@
|
||||
class="page-search-results__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
[routerLink]="filterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
[queryParams]="filterQueryParams"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
@@ -32,8 +32,7 @@
|
||||
class="page-search-results__items-count inline-flex flex-row items-center pr-5 text-p3"
|
||||
[class.mb-4]="primaryOutletActive$ | async"
|
||||
>
|
||||
{{ hits ??
|
||||
0 }}
|
||||
{{ hits ?? 0 }}
|
||||
Titel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
selectedItems$ = combineLatest([this.results$, this.selectedItemIds$]).pipe(
|
||||
map(([items, selectedItemIds]) => {
|
||||
return items?.filter((item) => selectedItemIds?.find((selectedItemId) => item.id === selectedItemId));
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
getProcessId(): number {
|
||||
@@ -84,7 +84,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
map(([filter, defaultFilter]) => {
|
||||
const filterQueryParams = filter?.getQueryParams();
|
||||
return !isEqual(this.resetQueryParamsQueryAndOrderBy(filterQueryParams), Filter.create(defaultFilter).getQueryParams());
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
get filterRoute() {
|
||||
@@ -95,6 +95,10 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}).path;
|
||||
}
|
||||
|
||||
get filterQueryParams() {
|
||||
return this.cleanupQueryParams(this.searchService?.filter?.getQueryParams());
|
||||
}
|
||||
|
||||
get primaryOutletActive$() {
|
||||
return this._environment.matchDesktop$.pipe(map((matches) => matches && this.route.outlet === 'primary'));
|
||||
}
|
||||
@@ -116,7 +120,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _router: Router
|
||||
private _router: Router,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -125,8 +129,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
.pipe(
|
||||
debounceTime(0),
|
||||
switchMap(([processId, queryParams]) =>
|
||||
this.application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, queryParams, selectedBranch })))
|
||||
)
|
||||
this.application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, queryParams, selectedBranch }))),
|
||||
),
|
||||
)
|
||||
.subscribe(async ({ processId, queryParams, selectedBranch }) => {
|
||||
const processChanged = processId !== this.searchService.processId;
|
||||
@@ -138,7 +142,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
this.cacheCurrentData(
|
||||
this.searchService.processId,
|
||||
this.searchService.filter.getQueryParams(),
|
||||
this.searchService?.selectedBranch?.id
|
||||
this.searchService?.selectedBranch?.id,
|
||||
);
|
||||
this.updateBreadcrumbs(this.searchService.processId, this.searchService.filter.getQueryParams());
|
||||
}
|
||||
@@ -155,7 +159,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||
|
||||
if (this.route.outlet === 'primary' && processChanged) {
|
||||
if (processChanged) {
|
||||
this.scrollToItem(this._getScrollIndexFromCache());
|
||||
}
|
||||
|
||||
@@ -190,7 +194,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
if (this.route?.outlet === 'primary') {
|
||||
await this.removeDetailsBreadcrumb(processId);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
@@ -209,7 +213,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
if (searchCompleted.state.hits === 1) {
|
||||
const item = searchCompleted.state.items.find((f) => f);
|
||||
const ean = this.route?.snapshot?.params?.ean;
|
||||
const itemId = this.route?.snapshot?.params?.id ? Number(this.route?.snapshot?.params?.id) : item.id; // Nicht zum ersten Item der Liste springen wenn bereits eines selektiert ist
|
||||
|
||||
// Navigation from Cart uses ean
|
||||
if (!!ean) {
|
||||
@@ -224,7 +227,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
await this._navigationService
|
||||
.getArticleDetailsPath({
|
||||
processId,
|
||||
itemId,
|
||||
itemId: item.id,
|
||||
extras: { queryParams: params },
|
||||
})
|
||||
.navigate();
|
||||
@@ -245,7 +248,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
@@ -259,7 +262,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -272,7 +275,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
|
||||
private _getScrollIndexFromCache(): number {
|
||||
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN });
|
||||
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }) ?? 0;
|
||||
}
|
||||
|
||||
scrollToItem(i?: number) {
|
||||
@@ -338,7 +341,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
async updateBreadcrumbs(
|
||||
processId: number = this.searchService.processId,
|
||||
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams()
|
||||
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams(),
|
||||
) {
|
||||
const selected_item_ids = this.searchService?.selectedItemIds?.toString();
|
||||
|
||||
@@ -560,7 +563,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
this.unselectAll();
|
||||
}
|
||||
this.loading$.next(false);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
|
||||
}
|
||||
|
||||
_onDestroy$ = new Subject<boolean>();
|
||||
_onDestroy$ = new Subject<void>();
|
||||
|
||||
get isTablet$() {
|
||||
return this._environmentService.matchTablet$;
|
||||
@@ -60,7 +60,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
public auth: AuthService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _renderer: Renderer2,
|
||||
private _actions: ActionsSubject
|
||||
private _actions: ActionsSubject,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -76,7 +76,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return 'Bitte wählen Sie eine Filiale aus, um den Bestand einer anderen Filiale zu sehen';
|
||||
}
|
||||
return '';
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
|
||||
export interface CheckoutDummyData extends ShoppingCartItemDTO {}
|
||||
export interface CheckoutDummyData extends ShoppingCartItemDTO {
|
||||
changeDataFromCart?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
@@ -9,7 +8,8 @@ import { Subject } from 'rxjs';
|
||||
import { first, shareReplay, takeUntil } from 'rxjs/operators';
|
||||
import { CheckoutDummyData } from './checkout-dummy-data';
|
||||
import { CheckoutDummyStore } from './checkout-dummy.store';
|
||||
import { CheckoutNavigationService } from '@shared/services';
|
||||
import { CheckoutNavigationService, CustomerSearchNavigation } from '@shared/services';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-dummy',
|
||||
@@ -38,14 +38,15 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
_onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private _router: Router,
|
||||
private _fb: UntypedFormBuilder,
|
||||
private _dateAdapter: DateAdapter,
|
||||
private _modal: UiModalService,
|
||||
private _store: CheckoutDummyStore,
|
||||
private _ref: UiModalRef<any, CheckoutDummyData>,
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutNavigationService: CheckoutNavigationService
|
||||
private readonly _checkoutNavigationService: CheckoutNavigationService,
|
||||
private readonly _customerNavigationService: CustomerSearchNavigation,
|
||||
private _router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -58,7 +59,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
if (!!this._ref?.data && Object.keys(this._ref?.data).length !== 0) {
|
||||
if (this.hasShoppingCartItemToUpdate()) {
|
||||
const data = this._ref?.data;
|
||||
this._store.patchState({ shoppingCartItem: data });
|
||||
this.populateFormFromModalData(data);
|
||||
@@ -149,6 +150,14 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
this.control.markAsUntouched();
|
||||
}
|
||||
|
||||
hasShoppingCartItemToUpdate(): boolean {
|
||||
const hasShoppingCartItem = !!this._ref.data?.id;
|
||||
if (!!this._ref?.data && hasShoppingCartItem) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async nextItem() {
|
||||
if (this.control.invalid || this.control.disabled) {
|
||||
this.control.enable();
|
||||
@@ -158,7 +167,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
|
||||
try {
|
||||
const branch = await this._store.currentBranch$.pipe(first()).toPromise();
|
||||
if (!!this._ref?.data && Object.keys(this._ref?.data).length !== 0) {
|
||||
if (this.hasShoppingCartItemToUpdate()) {
|
||||
await this._store.createAddToCartItem(this.control, branch, true);
|
||||
this._store.updateCart(() => {});
|
||||
} else {
|
||||
@@ -187,16 +196,17 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
|
||||
try {
|
||||
const branch = await this._store.currentBranch$.pipe(first()).toPromise();
|
||||
if (!!this._ref?.data && Object.keys(this._ref?.data).length !== 0) {
|
||||
if (this.hasShoppingCartItemToUpdate()) {
|
||||
await this._store.createAddToCartItem(this.control, branch, true);
|
||||
this._store.updateCart(async () => {
|
||||
// Set filter for navigation to customer search if customer is not set
|
||||
const customer = await this._store.customer$.pipe(first()).toPromise();
|
||||
const customerFilter = await this._store.customerFilter$.pipe(first()).toPromise();
|
||||
let filter: { [key: string]: string };
|
||||
if (!customer) {
|
||||
if (!customer && !this._ref?.data?.changeDataFromCart) {
|
||||
filter = customerFilter;
|
||||
this._router.navigate(['/kunde', this._applicationService.activatedProcessId, 'customer', 'search'], {
|
||||
const path = this._customerNavigationService.defaultRoute({ processId: this._applicationService.activatedProcessId }).path;
|
||||
await this._router.navigate(path, {
|
||||
queryParams: { customertype: filter.customertype },
|
||||
});
|
||||
} else {
|
||||
@@ -211,9 +221,10 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
const customer = await this._store.customer$.pipe(first()).toPromise();
|
||||
const customerFilter = await this._store.customerFilter$.pipe(first()).toPromise();
|
||||
let filter: { [key: string]: string };
|
||||
if (!customer) {
|
||||
if (!customer && !this._ref?.data?.changeDataFromCart) {
|
||||
filter = customerFilter;
|
||||
this._router.navigate(['/kunde', this._applicationService.activatedProcessId, 'customer', 'search'], {
|
||||
const path = this._customerNavigationService.defaultRoute({ processId: this._applicationService.activatedProcessId }).path;
|
||||
await this._router.navigate(path, {
|
||||
queryParams: { customertype: filter.customertype },
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<div class="btn-wrapper">
|
||||
<a class="cta-primary" [routerLink]="productSearchBasePath">Artikel suchen</a>
|
||||
<button class="cta-secondary" (click)="openDummyModal()">Neuanlage</button>
|
||||
<button class="cta-secondary" (click)="openDummyModal({})">Neuanlage</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,7 +54,7 @@
|
||||
<button
|
||||
*ngIf="group.orderType === 'Dummy'"
|
||||
class="text-brand border-none font-bold text-p1 outline-none pl-4"
|
||||
(click)="openDummyModal()"
|
||||
(click)="openDummyModal({ changeDataFromCart: true })"
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
|
||||
@@ -292,15 +292,15 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
this._store.notificationsControl = undefined;
|
||||
}
|
||||
|
||||
openDummyModal(data?: CheckoutDummyData) {
|
||||
openDummyModal({ data, changeDataFromCart = false }: { data?: CheckoutDummyData; changeDataFromCart?: boolean }) {
|
||||
this.uiModal.open({
|
||||
content: CheckoutDummyComponent,
|
||||
data,
|
||||
data: { ...data, changeDataFromCart },
|
||||
});
|
||||
}
|
||||
|
||||
changeDummyItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
||||
this.openDummyModal(shoppingCartItem);
|
||||
this.openDummyModal({ data: shoppingCartItem, changeDataFromCart: true });
|
||||
}
|
||||
|
||||
async changeItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
||||
|
||||
@@ -5,14 +5,10 @@
|
||||
<ng-container *ngIf="buyer$ | async; let buyer">
|
||||
<div *ngIf="!(showAddresses$ | async)" class="flex flex-row items-start justify-between p-5">
|
||||
<div class="flex flex-row flex-wrap pr-4">
|
||||
<ng-container *ngIf="!!buyer?.lastName && !!buyer?.firstName; else organisation">
|
||||
<div class="mr-3">Nachname, Vorname</div>
|
||||
<div class="font-bold">{{ buyer?.lastName }}, {{ buyer?.firstName }}</div>
|
||||
<ng-container *ngIf="getNameFromBuyer(buyer); let name">
|
||||
<div class="mr-3">{{ name.label }}</div>
|
||||
<div class="font-bold">{{ name.value }}</div>
|
||||
</ng-container>
|
||||
<ng-template #organisation>
|
||||
<div class="mr-3">Firmenname</div>
|
||||
<div class="font-bold">{{ buyer?.organisation?.name }}</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<button (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">
|
||||
@@ -36,9 +32,9 @@
|
||||
|
||||
<ng-container *ngIf="payer$ | async; let payer">
|
||||
<div *ngIf="showAddresses$ | async" class="flex flex-row items-start justify-between p-5 pt-0">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Rechnungsadresse">
|
||||
<div class="mr-3">Rechnungsadresse</div>
|
||||
<div class="font-bold">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Rechnungsadresse" data-which="Rechnungsadresse">
|
||||
<div class="mr-3" data-what="title">Rechnungsadresse</div>
|
||||
<div class="font-bold" data-what="address">
|
||||
{{ payer | payerAddress }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,9 +47,9 @@
|
||||
|
||||
<ng-container *ngIf="payer$ | async; let payer">
|
||||
<div *ngIf="showAddresses$ | async" class="flex flex-row items-start justify-between px-5">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Lieferadresse">
|
||||
<div class="mr-3">Lieferadresse</div>
|
||||
<div class="font-bold">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Lieferadresse" data-which="Lieferadresse">
|
||||
<div class="mr-3" data-what="title">Lieferadresse</div>
|
||||
<div class="font-bold" data-what="address">
|
||||
{{ shippingAddress$ | async | shippingAddress }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, OnInit, inject } from '@angular/core';
|
||||
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/components/notification-channel-control';
|
||||
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { combineLatest } from 'rxjs';
|
||||
@@ -7,7 +7,8 @@ import { first, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { Router } from '@angular/router';
|
||||
import { NotificationChannel } from '@swagger/checkout';
|
||||
import { BuyerDTO, NotificationChannel } from '@swagger/checkout';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-review-details',
|
||||
@@ -16,6 +17,8 @@ import { NotificationChannel } from '@swagger/checkout';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewDetailsComponent implements OnInit {
|
||||
customerNavigation = inject(CustomerSearchNavigation);
|
||||
|
||||
control: UntypedFormGroup;
|
||||
|
||||
customerFeatures$ = this._store.customerFeatures$;
|
||||
@@ -23,20 +26,6 @@ export class CheckoutReviewDetailsComponent implements OnInit {
|
||||
payer$ = this._store.payer$;
|
||||
buyer$ = this._store.buyer$;
|
||||
|
||||
showAddresses$ = this._store.shoppingCartItems$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
withLatestFrom(this.customerFeatures$),
|
||||
map(
|
||||
([items, customerFeatures]) =>
|
||||
items.some(
|
||||
(item) =>
|
||||
item.features?.orderType === 'Versand' ||
|
||||
item.features?.orderType === 'B2B-Versand' ||
|
||||
item.features?.orderType === 'DIG-Versand'
|
||||
) || !!customerFeatures?.b2b
|
||||
)
|
||||
);
|
||||
|
||||
showNotificationChannels$ = combineLatest([this._store.shoppingCartItems$, this.payer$, this.buyer$]).pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
map(
|
||||
@@ -65,6 +54,22 @@ export class CheckoutReviewDetailsComponent implements OnInit {
|
||||
switchMap((processId) => this._domainCheckoutService.getShippingAddress({ processId }))
|
||||
);
|
||||
|
||||
showAddresses$ = this._store.shoppingCartItems$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
withLatestFrom(this.customerFeatures$, this.payer$, this.shippingAddress$),
|
||||
map(([items, customerFeatures, payer, shippingAddress]) => {
|
||||
const hasShippingOrBillingAddresses = !!payer?.address || !!shippingAddress;
|
||||
const hasShippingFeature = items.some(
|
||||
(item) =>
|
||||
item.features?.orderType === 'Versand' || item.features?.orderType === 'B2B-Versand' || item.features?.orderType === 'DIG-Versand'
|
||||
);
|
||||
|
||||
const isB2bCustomer = !!customerFeatures?.b2b;
|
||||
|
||||
return hasShippingOrBillingAddresses && (hasShippingFeature || isB2bCustomer);
|
||||
})
|
||||
);
|
||||
|
||||
notificationChannelLoading$ = this._store.notificationChannelLoading$;
|
||||
|
||||
constructor(
|
||||
@@ -118,6 +123,20 @@ export class CheckoutReviewDetailsComponent implements OnInit {
|
||||
this._store.onNotificationChange(notificationChannels);
|
||||
}
|
||||
|
||||
getNameFromBuyer(buyer: BuyerDTO): { value: string; label: string } {
|
||||
if (buyer?.lastName && buyer?.firstName) {
|
||||
return { value: `${buyer?.lastName}, ${buyer?.firstName}`, label: 'Nachname, Vorname' };
|
||||
} else if (buyer?.lastName) {
|
||||
return { value: buyer?.lastName, label: 'Nachname, Vorname' };
|
||||
} else if (buyer?.firstName) {
|
||||
return { value: buyer?.firstName, label: 'Nachname, Vorname' };
|
||||
} else if (buyer?.organisation?.name) {
|
||||
return { value: buyer?.organisation?.name, label: 'Firmenname' };
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async changeAddress() {
|
||||
const processId = this._application.activatedProcessId;
|
||||
const customer = await this._domainCheckoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
@@ -126,7 +145,8 @@ export class CheckoutReviewDetailsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
const customerId = customer.source;
|
||||
this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'search', `${customerId}`]);
|
||||
await this.customerNavigation.navigateToDetails({ processId, customerId, customer: { customerNumber: customer.buyerNumber } });
|
||||
// this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'search', `${customerId}`]);
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch(processId: number) {
|
||||
|
||||
@@ -17,7 +17,7 @@ button {
|
||||
grid-area: item-thumbnail;
|
||||
@apply mr-8 w-[3.75rem] h-[5.9375rem];
|
||||
img {
|
||||
@apply w-[3.75rem] h-[5.9375rem] rounded shadow-cta;
|
||||
@apply w-[3.75rem] max-h-[5.9375rem] rounded shadow-cta;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,17 +19,14 @@
|
||||
placeholder="Eine Anmerkung hinzufügen"
|
||||
[(ngModel)]="value"
|
||||
[rows]="rows"
|
||||
(ngModelChange)="check()"
|
||||
(blur)="save()"
|
||||
(ngModelChange)="updateValue()"
|
||||
(blur)="updateValue()"
|
||||
></textarea>
|
||||
|
||||
<div class="comment-actions py-4">
|
||||
<button type="reset" class="clear pl-4" *ngIf="!disabled && !!value" (click)="clear(); triggerResize()">
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
<button class="cta-save ml-4" type="submit" *ngIf="!disabled && isDirty" (click)="save()">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export class SpecialCommentComponent implements ControlValueAccessor {
|
||||
|
||||
clear() {
|
||||
this.value = '';
|
||||
this.save();
|
||||
this.updateValue();
|
||||
}
|
||||
|
||||
check() {
|
||||
@@ -80,11 +80,12 @@ export class SpecialCommentComponent implements ControlValueAccessor {
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
save() {
|
||||
updateValue() {
|
||||
this.initialValue = this.value;
|
||||
this.onChange(this.value);
|
||||
this.check();
|
||||
}
|
||||
|
||||
setIsDirty(isDirty: boolean) {
|
||||
this.isDirty = isDirty;
|
||||
this.isDirtyChange.emit(isDirty);
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
<span class="w-32">Vorgangs-ID</span>
|
||||
<ng-container *ngIf="customer$ | async; let customer">
|
||||
<a
|
||||
data-which="Vorgangs-ID"
|
||||
data-what="link"
|
||||
*ngIf="customer$ | async; let customer"
|
||||
class="font-bold text-[#0556B4] no-underline"
|
||||
[routerLink]="['/kunde', processId, 'customer', 'search', customer?.id, 'orders', displayOrder.id]"
|
||||
@@ -102,7 +104,7 @@
|
||||
>
|
||||
<div class="page-checkout-summary__items-thumbnail flex flex-row">
|
||||
<a [routerLink]="getProductSearchDetailsPath(order?.product?.ean)" [queryParams]="getProductSearchDetailsQueryParams(order)">
|
||||
<img class="w-[3.125rem] h-20 mr-2" [src]="order.product?.ean | productImage: 195:315:true" />
|
||||
<img class="w-[3.125rem] max-h-20 mr-2" [src]="order.product?.ean | productImage: 195:315:true" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -226,11 +228,12 @@
|
||||
<div class="absolute left-1/2 bottom-10 inline-grid grid-flow-col gap-4 justify-center transform -translate-x-1/2">
|
||||
<button
|
||||
*ifRole="'Store'"
|
||||
[disabled]="isPrinting$ | async"
|
||||
type="button"
|
||||
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14"
|
||||
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14 flex flex-row items-center justify-center print-button"
|
||||
(click)="printOrderConfirmation()"
|
||||
>
|
||||
Bestellbestätigung drucken
|
||||
<ui-spinner class="min-h-4 min-w-4" [show]="isPrinting$ | async"> Bestellbestätigung drucken </ui-spinner>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
@@ -96,6 +96,12 @@ hr {
|
||||
}
|
||||
}
|
||||
|
||||
.print-button {
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch border-solid border-inactive-branch text-white cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.last {
|
||||
@apply pb-5;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import { DateAdapter } from '@ui/common';
|
||||
import { CheckoutNavigationService, PickUpShelfOutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { SendOrderConfirmationModalService } from '@shared/modals/send-order-confirmation-modal';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { ToasterService } from '@shared/shell';
|
||||
|
||||
@Component({
|
||||
@@ -34,7 +33,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
|
||||
private _toaster = inject(ToasterService);
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
processId = Date.now();
|
||||
selectedDate = this.dateAdapter.today();
|
||||
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);
|
||||
@@ -53,17 +52,17 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Ticket #4228 Für die korrekte Gruppierung der Items bei gleichem Bestellziel (Aufsplitten von Abholung und Rücklage)
|
||||
const ordersWithMultipleFeatures = filteredOrders.filter((order) =>
|
||||
order.items.find((item) => item.features.orderType !== order.features.orderType)
|
||||
order.items.find((item) => item.features.orderType !== order.features.orderType),
|
||||
);
|
||||
|
||||
if (!!ordersWithMultipleFeatures) {
|
||||
for (let orderWithMultipleFeatures of ordersWithMultipleFeatures) {
|
||||
if (orderWithMultipleFeatures?.items?.length > 1) {
|
||||
const itemsWithOrderFeature = orderWithMultipleFeatures.items.filter(
|
||||
(item) => item.features.orderType === orderWithMultipleFeatures.features.orderType
|
||||
(item) => item.features.orderType === orderWithMultipleFeatures.features.orderType,
|
||||
);
|
||||
const itemsWithDifferentOrderFeature = orderWithMultipleFeatures.items.filter(
|
||||
(item) => item.features.orderType !== orderWithMultipleFeatures.features.orderType
|
||||
(item) => item.features.orderType !== orderWithMultipleFeatures.features.orderType,
|
||||
);
|
||||
|
||||
filteredOrders = [...filteredOrders.filter((order) => order.id !== orderWithMultipleFeatures.id)];
|
||||
@@ -86,20 +85,20 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
});
|
||||
}),
|
||||
shareReplay()
|
||||
shareReplay(),
|
||||
);
|
||||
|
||||
hasAbholung$ = this.displayOrders$.pipe(
|
||||
map((displayOrders) => displayOrders.filter((order) => order.features?.orderType === 'Abholung')?.length > 0)
|
||||
map((displayOrders) => displayOrders.filter((order) => order.features?.orderType === 'Abholung')?.length > 0),
|
||||
);
|
||||
|
||||
totalItemCount$ = this.displayOrders$.pipe(
|
||||
map((displayOrders) =>
|
||||
displayOrders.reduce(
|
||||
(total, displayOrder) => total + displayOrder?.items?.reduce((subTotal, order) => subTotal + order?.quantity, 0),
|
||||
0
|
||||
)
|
||||
)
|
||||
0,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
totalReadingPoints$ = this.displayOrders$.pipe(
|
||||
@@ -123,7 +122,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
return NEVER;
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
totalPrice$ = this.displayOrders$.pipe(
|
||||
@@ -131,36 +130,42 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
displayOrders.reduce(
|
||||
(total, displayOrder) =>
|
||||
total + displayOrder?.items?.reduce((subTotal, order) => subTotal + order?.price?.value?.value * order.quantity, 0),
|
||||
0
|
||||
)
|
||||
)
|
||||
0,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
isPrinting$ = new BehaviorSubject(false);
|
||||
|
||||
totalPriceCurrency$ = this.displayOrders$.pipe(map((displayOrders) => displayOrders[0]?.items[0]?.price?.value?.currency));
|
||||
|
||||
containsDeliveryOrder$ = this.displayOrders$.pipe(
|
||||
map(
|
||||
(displayOrders) =>
|
||||
displayOrders.filter((o) => ['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(o.features?.orderType) > -1)?.length > 0
|
||||
)
|
||||
displayOrders.filter((o) => ['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(o.features?.orderType) > -1)?.length > 0,
|
||||
),
|
||||
);
|
||||
|
||||
customer$ = this.displayOrders$.pipe(
|
||||
switchMap((o) => this.customerService.getCustomers(o[0].buyerNumber, { take: 5 })),
|
||||
map((customers) => customers.result[0]),
|
||||
shareReplay()
|
||||
shareReplay(),
|
||||
);
|
||||
|
||||
isB2BCustomer$ = this.customer$.pipe(map((customer) => customer?.features?.find((f) => f.key === 'b2b') != null));
|
||||
|
||||
takeNowOrders$ = this.displayOrders$.pipe(
|
||||
map((displayOrders) => displayOrders.filter((o) => o.items.find((oi) => oi.features?.orderType === 'Rücklage') != null))
|
||||
map((displayOrders) => displayOrders.filter((o) => o.items.find((oi) => oi.features?.orderType === 'Rücklage') != null)),
|
||||
);
|
||||
|
||||
get isDesktop$() {
|
||||
return this._environmentService.matchDesktopLarge$;
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this._environmentService.matchTablet();
|
||||
}
|
||||
|
||||
expanded: boolean[] = [];
|
||||
|
||||
constructor(
|
||||
@@ -179,7 +184,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
private _productNavigationService: ProductCatalogNavigationService,
|
||||
private _shelfOutNavigationService: PickUpShelfOutNavigationService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _cdr: ChangeDetectorRef
|
||||
private _cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['checkout'])
|
||||
@@ -291,22 +296,9 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
if (takeNowOrders.length != 1) return;
|
||||
|
||||
try {
|
||||
for (const takeNowOrder of takeNowOrders) {
|
||||
for (const orderItem of takeNowOrder.items.filter((item) => item.features?.orderType === 'Rücklage')) {
|
||||
await this.omsService
|
||||
.changeOrderStatus(takeNowOrder.id, orderItem.id, orderItem.subsetItems[0]?.id, {
|
||||
processingStatus: 128,
|
||||
})
|
||||
.toPromise();
|
||||
}
|
||||
}
|
||||
|
||||
await this.router.navigate(
|
||||
this._shelfOutNavigationService.detailRoute({
|
||||
processId: Date.now(),
|
||||
item: { orderId: takeNowOrders[0].id, orderNumber: takeNowOrders[0].orderNumber, processingStatus: 128 },
|
||||
}).path
|
||||
);
|
||||
await this.router.navigate(this._shelfOutNavigationService.listRoute({ processId: Date.now() }).path, {
|
||||
queryParams: { main_qs: takeNowOrders[0].orderNumber, filter_supplier_id: '16' },
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -319,27 +311,57 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async printOrderConfirmation() {
|
||||
this.isPrinting$.next(true);
|
||||
const orders = await this.displayOrders$.pipe(first()).toPromise();
|
||||
await this.uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
data: {
|
||||
printerType: 'Label',
|
||||
print: async (printer) => {
|
||||
try {
|
||||
const result = await this.domainPrinterService.printOrder({ orderIds: orders.map((o) => o.id), printer }).toPromise();
|
||||
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
|
||||
return result;
|
||||
} catch (error) {
|
||||
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
|
||||
}
|
||||
const selectedPrinter = await this.domainPrinterService
|
||||
.getAvailableLabelPrinters()
|
||||
.pipe(
|
||||
first(),
|
||||
map((printers) => {
|
||||
if (Array.isArray(printers)) return printers.find((printer) => printer.selected === true);
|
||||
}),
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
console.log(selectedPrinter);
|
||||
if (!selectedPrinter || this.isTablet) {
|
||||
await this.uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
data: {
|
||||
printerType: 'Label',
|
||||
printImmediately: !this.isTablet,
|
||||
print: async (printer) => {
|
||||
try {
|
||||
const result = await this.domainPrinterService.printOrder({ orderIds: orders.map((o) => o.id), printer }).toPromise();
|
||||
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
|
||||
return result;
|
||||
} catch (error) {
|
||||
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
|
||||
} finally {
|
||||
this.isPrinting$.next(false);
|
||||
}
|
||||
},
|
||||
} as PrintModalData,
|
||||
config: {
|
||||
panelClass: [],
|
||||
showScrollbarY: false,
|
||||
},
|
||||
} as PrintModalData,
|
||||
config: {
|
||||
panelClass: [],
|
||||
showScrollbarY: false,
|
||||
},
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
this.isPrinting$.next(false);
|
||||
} else {
|
||||
try {
|
||||
const result = await this.domainPrinterService
|
||||
.printOrder({ orderIds: orders.map((o) => o.id), printer: selectedPrinter.key })
|
||||
.toPromise();
|
||||
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
|
||||
return result;
|
||||
} catch (error) {
|
||||
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
|
||||
} finally {
|
||||
this.isPrinting$.next(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,19 @@
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="page-customer-order-details-item__tracking-details" *ngIf="getOrderItemTrackingData(orderItem); let trackingData">
|
||||
<div class="label">{{ trackingData.length > 1 ? 'Sendungsnummern' : 'Sendungsnummer' }}</div>
|
||||
<ng-container *ngFor="let tracking of trackingData">
|
||||
<ng-container *ngIf="tracking.trackingProvider === 'DHL' && !isNative; else noTrackingLink">
|
||||
<a class="value text-[#0556B4]" [href]="getTrackingNumberLink(tracking.trackingNumber)" target="_blank"
|
||||
>{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}</a
|
||||
>
|
||||
</ng-container>
|
||||
<ng-template #noTrackingLink>
|
||||
<p class="value">{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}</p>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ button {
|
||||
|
||||
.page-customer-order-details-item__thumbnail {
|
||||
img {
|
||||
@apply rounded shadow-cta w-[3.625rem] h-[5.9375rem];
|
||||
@apply rounded shadow-cta w-[3.625rem] max-h-[5.9375rem];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,18 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__tracking-details {
|
||||
@apply flex gap-x-7;
|
||||
|
||||
.label {
|
||||
@apply w-[8.125rem];
|
||||
}
|
||||
|
||||
.value {
|
||||
@apply flex flex-row items-center font-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__comment {
|
||||
textarea {
|
||||
@apply w-full flex-grow rounded bg-[#EDEFF0] border-[#AEB7C1] border border-solid outline-none text-p2 p-4;
|
||||
|
||||
@@ -18,6 +18,7 @@ import { isEqual } from 'lodash';
|
||||
import { combineLatest, NEVER, Subject, Observable } from 'rxjs';
|
||||
import { catchError, filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { CustomerOrderDetailsStore } from '../customer-order-details.store';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
|
||||
export interface CustomerOrderDetailsItemComponentState {
|
||||
orderItem?: OrderItemListItemDTO;
|
||||
@@ -77,12 +78,12 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
|
||||
switchMap((oi) =>
|
||||
this._omsService
|
||||
.getCompletedTasks({ orderId: oi.orderId, orderItemId: oi.orderItemId, orderItemSubsetId: oi.orderItemSubsetId, take: 4, skip: 0 })
|
||||
.pipe(catchError(() => NEVER))
|
||||
)
|
||||
.pipe(catchError(() => NEVER)),
|
||||
),
|
||||
);
|
||||
|
||||
canChangeQuantity$ = combineLatest([this.orderItem$, this._store.fetchPartial$]).pipe(
|
||||
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1)
|
||||
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1),
|
||||
);
|
||||
|
||||
get quantity() {
|
||||
@@ -108,7 +109,7 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
|
||||
}
|
||||
|
||||
readonly selected$ = combineLatest([this.orderItem$, this._store.selectedeOrderItemSubsetIds$]).pipe(
|
||||
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId))
|
||||
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId)),
|
||||
);
|
||||
|
||||
@Output()
|
||||
@@ -119,7 +120,7 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
|
||||
}
|
||||
|
||||
readonly selectable$ = combineLatest([this._store.items$, this._store.itemsSelectable$, this._store.fetchPartial$]).pipe(
|
||||
map(([orderItems, selectable, fetchPartial]) => orderItems.length > 1 && selectable && fetchPartial)
|
||||
map(([orderItems, selectable, fetchPartial]) => orderItems.length > 1 && selectable && fetchPartial),
|
||||
);
|
||||
|
||||
get receipts() {
|
||||
@@ -140,13 +141,18 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
|
||||
|
||||
more$ = this.select((s) => s.more);
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
get isNative() {
|
||||
return this._environment.isNative();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _store: CustomerOrderDetailsStore,
|
||||
private _domainReceiptService: DomainReceiptService,
|
||||
private _omsService: DomainOmsService,
|
||||
private _cdr: ChangeDetectorRef
|
||||
private _cdr: ChangeDetectorRef,
|
||||
private _environment: EnvironmentService,
|
||||
) {
|
||||
super({
|
||||
more: false,
|
||||
@@ -188,11 +194,11 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
|
||||
done?.([]);
|
||||
}
|
||||
},
|
||||
() => {}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
() => {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
async saveSpecialComment() {
|
||||
@@ -231,6 +237,38 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
|
||||
return orderItems?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)?.data?.features?.orderType;
|
||||
}
|
||||
|
||||
getOrderItemTrackingData(orderItemListItem: OrderItemListItemDTO): Array<{ trackingProvider: string; trackingNumber: string }> {
|
||||
const orderItems = this.order?.items;
|
||||
const completeTrackingInformation = orderItems
|
||||
?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)
|
||||
?.data?.subsetItems?.find((subsetItem) => subsetItem.id === orderItemListItem.orderItemSubsetId)?.data?.trackingNumber;
|
||||
|
||||
if (!completeTrackingInformation) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Beispielnummer: 'DHL: 124124' - Bei mehreren Tracking-Informationen muss noch ein Splitter eingebaut werden, je nach dem welcher Trenner verwendet wird
|
||||
const trackingInformationPairs = completeTrackingInformation.split(':').map((obj) => obj.trim());
|
||||
return this._trackingTransformationHelper(trackingInformationPairs);
|
||||
}
|
||||
|
||||
// Macht aus einem String Array ein Array von Objekten mit den keys trackingProvider und trackingNumber
|
||||
private _trackingTransformationHelper(trackingInformationPairs: string[]): Array<{ trackingProvider: string; trackingNumber: string }> {
|
||||
return trackingInformationPairs.reduce(
|
||||
(acc, current, index, array) => {
|
||||
if (index % 2 === 0) {
|
||||
acc.push({ trackingProvider: current, trackingNumber: array[index + 1] });
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as { trackingProvider: string; trackingNumber: string }[],
|
||||
);
|
||||
}
|
||||
|
||||
getTrackingNumberLink(trackingNumber: string) {
|
||||
return `https://www.dhl.de/de/privatkunden/dhl-sendungsverfolgung.html?piececode=${trackingNumber}`;
|
||||
}
|
||||
|
||||
triggerResize() {
|
||||
this.autosize.reset();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[order]="order$ | async"
|
||||
[selected]="true"
|
||||
(historyClick)="navigateToHistoryPage($event)"
|
||||
(specialCommentChanged)="updateCustomerOrderResults()"
|
||||
(specialCommentChanged)="onSpecialCommentChange()"
|
||||
></page-customer-order-details-item>
|
||||
<page-customer-order-details-tags *ngIf="showTagsComponent$ | async"></page-customer-order-details-tags>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
OnInit,
|
||||
QueryList,
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRoute, NavigationError, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { OrderItemsContext } from '@domain/oms';
|
||||
@@ -84,7 +84,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
const first = items?.find((_) => true);
|
||||
const hasArrivedAction = first?.actions?.some((a) => a.command?.includes('ARRIVED'));
|
||||
return hasArrivedAction && !fetching;
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
actionsDisabled$ = combineLatest([
|
||||
@@ -94,7 +94,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
]).pipe(map(([disabled, partial, orderItems]) => disabled || (partial && orderItems.length > 1)));
|
||||
|
||||
addToPreviousCompartmentActionDisabled$ = combineLatest([this.compartmentInfo$, this.changeActionDisabled$, this.fetching$]).pipe(
|
||||
map(([compartmentInfo, changeActionDisabled, fetching]) => (!!compartmentInfo || changeActionDisabled) && fetching)
|
||||
map(([compartmentInfo, changeActionDisabled, fetching]) => (!!compartmentInfo || changeActionDisabled) && fetching),
|
||||
);
|
||||
|
||||
constructor(
|
||||
@@ -105,7 +105,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
private _router: Router,
|
||||
private _uiModal: UiModalService,
|
||||
private _environment: EnvironmentService,
|
||||
private _commandService: CommandService
|
||||
private _commandService: CommandService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -113,7 +113,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
this._activatedRoute.queryParams.subscribe((params) => {
|
||||
const buyerNumber: string = decodeURIComponent(params.buyerNumber ?? '');
|
||||
this._store.patchState({ buyerNumber });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
@@ -136,7 +136,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
}
|
||||
|
||||
await this.removeDetailsCrumbs();
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
@@ -153,7 +153,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
this._store.patchState({ orderId });
|
||||
this._store.loadOrder();
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.removeBreadcrumbs();
|
||||
@@ -172,7 +172,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
this.items$.subscribe((_) => {
|
||||
this.customerOrderDetailsTags?.writeValue(this.compartmentInfo);
|
||||
this.customerOrderDetailsTags?.registerOnChange((compartmentInfo) => (this._store.compartmentInfo = compartmentInfo));
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
|
||||
getItemQuantityMap(): Map<number, number> {
|
||||
return new Map(
|
||||
this.customerOrderDetailsItemComponents.toArray().map((component) => [component.orderItem.orderItemSubsetId, component.quantity])
|
||||
this.customerOrderDetailsItemComponents.toArray().map((component) => [component.orderItem.orderItemSubsetId, component.quantity]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
|
||||
async handleAction(
|
||||
action: KeyValueDTOOfStringAndString,
|
||||
{ compartmentCode, compartmentInfo }: { compartmentCode?: string; compartmentInfo?: string } = {}
|
||||
{ compartmentCode, compartmentInfo }: { compartmentCode?: string; compartmentInfo?: string } = {},
|
||||
) {
|
||||
if (action.command.includes('FETCHED_PARTIAL')) {
|
||||
this._store.patchState({ fetchPartial: true });
|
||||
@@ -312,7 +312,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
(itemComponent) =>
|
||||
new Promise<ReceiptDTO[]>((resolve) => {
|
||||
itemComponent.loadReceipts((r) => resolve(r));
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
receipts = await Promise.all(receiptsPromise).then((r) => r.reduce((acc, val) => acc.concat(val), []));
|
||||
@@ -345,17 +345,21 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
if (action.command.includes('ARRIVED')) {
|
||||
navigateTo = await this.arrivedActionNavigation();
|
||||
}
|
||||
if (action.command.includes('PRINT_PRICEDIFFQRCODELABEL')) {
|
||||
if (action.command.includes('PRINT_PRICEDIFFQRCODELABEL') || action.command.includes('BACKTOSTOCK')) {
|
||||
navigateTo = 'main';
|
||||
}
|
||||
|
||||
await this.actionHandled({ orderItemsContext: commandData, command: action.command, navigation: navigateTo });
|
||||
this._store.updateOrderItems(commandData.items);
|
||||
} catch (error) {
|
||||
this._uiModal.open({
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
if (error instanceof NavigationError) {
|
||||
await this.handleRouterMatchNoRoutesError(); // Refresh Result list but stay on same page
|
||||
} else {
|
||||
this._uiModal.open({
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -374,24 +378,36 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
if (handler.navigation === 'main') {
|
||||
await this._navigationService.getCustomerOrdersBasePath(this.processId).navigate();
|
||||
} else {
|
||||
const item: OrderItemListItemDTO = handler.orderItemsContext.items.find((_) => true);
|
||||
await this._router.navigate(this.getDetailsPath(item), {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, buyerNumber: item.buyerNumber },
|
||||
});
|
||||
await this.updateCustomerOrderResults();
|
||||
const item: OrderItemListItemDTO = handler?.orderItemsContext?.items?.find((_) => true);
|
||||
if (!!item) {
|
||||
await this._router.navigate(this.getDetailsPath(item), {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, buyerNumber: item.buyerNumber },
|
||||
});
|
||||
}
|
||||
if (this.isDesktop) {
|
||||
await this.refreshResults();
|
||||
}
|
||||
await this.removeDetailsCrumbs();
|
||||
this._store.loadItems();
|
||||
}
|
||||
}
|
||||
|
||||
async updateCustomerOrderResults() {
|
||||
async handleRouterMatchNoRoutesError() {
|
||||
await this.refreshResults();
|
||||
}
|
||||
|
||||
async onSpecialCommentChange() {
|
||||
if (this.isDesktop) {
|
||||
await this._router.navigate([], {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, updateResults: true },
|
||||
});
|
||||
await this.refreshResults();
|
||||
}
|
||||
}
|
||||
|
||||
async refreshResults() {
|
||||
await this._router.navigate([], {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, updateResults: true },
|
||||
});
|
||||
}
|
||||
|
||||
async arrivedActionNavigation(): Promise<'main'> {
|
||||
const detailsCrumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$('customer-order', ['customer-order', 'details'])
|
||||
|
||||
@@ -22,7 +22,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
|
||||
processId = Number(this._activatedRoute?.parent?.snapshot?.data?.processId);
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
@ViewChild(FilterComponent, { static: false })
|
||||
uiFilter: FilterComponent;
|
||||
@@ -79,7 +79,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
private _environment: EnvironmentService,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
public application: ApplicationService,
|
||||
private _navigationService: CustomerOrdersNavigationService
|
||||
private _navigationService: CustomerOrdersNavigationService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { CustomerOrdersNavigationService } from '@shared/services';
|
||||
],
|
||||
})
|
||||
export class CustomerOrderSearchComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
private _processId$: Observable<number>;
|
||||
|
||||
get isTablet() {
|
||||
@@ -35,7 +35,7 @@ export class CustomerOrderSearchComponent implements OnInit, OnDestroy {
|
||||
private _navigationService: CustomerOrdersNavigationService,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _environment: EnvironmentService
|
||||
private _environment: EnvironmentService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -127,7 +127,10 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
|
||||
cancelSearch$ = new Subject<void>();
|
||||
|
||||
constructor(private _domainGoodsInService: DomainCustomerOrderService, private _cache: CacheService) {
|
||||
constructor(
|
||||
private _domainGoodsInService: DomainCustomerOrderService,
|
||||
private _cache: CacheService,
|
||||
) {
|
||||
super({
|
||||
fetching: false,
|
||||
silentFetching: false,
|
||||
@@ -201,8 +204,8 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
|
||||
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
|
||||
options$.pipe(
|
||||
tap((_) => {
|
||||
this.searchStarted.next();
|
||||
tap((opt) => {
|
||||
this.searchStarted.next(opt);
|
||||
this.patchState({ message: undefined });
|
||||
}),
|
||||
withLatestFrom(this.results$, this.filter$, this.selectedBranch$),
|
||||
@@ -289,10 +292,10 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
}
|
||||
this.patchState({ fetching: false, silentFetching: false });
|
||||
console.error('GoodsInSearchStore.search()', err);
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
>
|
||||
<div class="page-customer-order-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
|
||||
<img
|
||||
class="page-customer-order-item__item-image w-[3.125rem] h-[4.9375rem]"
|
||||
class="page-customer-order-item__item-image w-[3.125rem] max-h-[4.9375rem]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.product?.ean | productImage; let productImage"
|
||||
[src]="productImage"
|
||||
|
||||
@@ -42,8 +42,10 @@ export interface CustomerOrderSearchResultsState {
|
||||
styleUrls: ['customer-order-search-results.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderSearchResultsComponent extends ComponentStore<CustomerOrderSearchResultsState>
|
||||
implements OnInit, AfterViewInit, OnDestroy {
|
||||
export class CustomerOrderSearchResultsComponent
|
||||
extends ComponentStore<CustomerOrderSearchResultsState>
|
||||
implements OnInit, AfterViewInit, OnDestroy
|
||||
{
|
||||
@ViewChildren(CustomerOrderItemComponent) listItems: QueryList<CustomerOrderItemComponent>;
|
||||
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
|
||||
|
||||
@@ -63,7 +65,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
|
||||
listEmpty$ = combineLatest([this.loading$, this.hits$]).pipe(
|
||||
map(([loading, hits]) => !loading && hits === 0),
|
||||
shareReplay()
|
||||
shareReplay(),
|
||||
);
|
||||
|
||||
message$ = this._customerOrderSearchStore.message$;
|
||||
@@ -76,9 +78,9 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
|
||||
actions$ = combineLatest([this.items$, this.selectedOrderItemSubsetIds$]).pipe(
|
||||
map(([items, selectedOrderItemSubsetIds]) =>
|
||||
items?.find((item) => selectedOrderItemSubsetIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId))
|
||||
items?.find((item) => selectedOrderItemSubsetIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId)),
|
||||
),
|
||||
map((item) => item?.actions?.filter((action) => this.selectionRules(action)))
|
||||
map((item) => item?.actions?.filter((action) => this.selectionRules(action))),
|
||||
);
|
||||
|
||||
get selectedItems() {
|
||||
@@ -99,7 +101,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
|
||||
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
trackByFn: TrackByFunction<OrderItemListItemDTO> = (index, item) => `${item.orderId}${item.orderItemId}${item.orderItemSubsetId}`;
|
||||
|
||||
@@ -108,7 +110,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
filter$ = this._customerOrderSearchStore.filter$.pipe(filter((f) => !!f));
|
||||
|
||||
hasFilter$ = combineLatest([this.filter$, this._customerOrderSearchStore.defaultSettings$]).pipe(
|
||||
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), Filter.create(defaultFilter).getQueryParams()))
|
||||
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), Filter.create(defaultFilter).getQueryParams())),
|
||||
);
|
||||
|
||||
get isTablet$() {
|
||||
@@ -148,7 +150,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
private _navigationService: CustomerOrdersNavigationService,
|
||||
private _application: ApplicationService,
|
||||
private _cache: CacheService,
|
||||
private _router: Router
|
||||
private _router: Router,
|
||||
) {
|
||||
super({
|
||||
selectedOrderItemSubsetIds: [],
|
||||
@@ -166,17 +168,16 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
const clean = { ...queryParams };
|
||||
delete clean['updateResults'];
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this._searchResultSubscription.add(
|
||||
this.processId$
|
||||
combineLatest([this.processId$, this._activatedRoute.queryParams])
|
||||
.pipe(
|
||||
debounceTime(150),
|
||||
withLatestFrom(this._activatedRoute.queryParams),
|
||||
switchMap(([processId, params]) =>
|
||||
this._application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, params, selectedBranch })))
|
||||
)
|
||||
this._application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, params, selectedBranch }))),
|
||||
),
|
||||
)
|
||||
.subscribe(async ({ processId, params, selectedBranch }) => {
|
||||
const branchChanged = selectedBranch?.id !== this._customerOrderSearchStore?.selectedBranch?.id;
|
||||
@@ -185,16 +186,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
|
||||
if (processChanged) {
|
||||
if (!!this._customerOrderSearchStore.processId && this._customerOrderSearchStore.filter instanceof Filter) {
|
||||
const queryToken = {
|
||||
...this._customerOrderSearchStore.filter?.getQueryParams(),
|
||||
processId,
|
||||
branchId: String(selectedBranch?.id),
|
||||
};
|
||||
this._customerOrderSearchStore.setCache({
|
||||
queryToken,
|
||||
hits: this._customerOrderSearchStore.hits,
|
||||
results: this._customerOrderSearchStore.results,
|
||||
});
|
||||
await this.updateBreadcrumb(processId, this._customerOrderSearchStore.filter?.getQueryParams());
|
||||
}
|
||||
this._customerOrderSearchStore.patchState({ processId });
|
||||
@@ -212,6 +203,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()))) {
|
||||
this._customerOrderSearchStore.setQueryParams(params);
|
||||
|
||||
const queryToken = {
|
||||
...this._customerOrderSearchStore.filter.getQueryParams(),
|
||||
processId,
|
||||
@@ -253,7 +245,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
if (this._activatedRoute?.outlet === 'primary') {
|
||||
await this.removeDetailsBreadcrumb(processId);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this._searchResultSubscription.add(
|
||||
@@ -271,12 +263,12 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
await this.navigateToDetails(
|
||||
processId,
|
||||
result?.results?.result?.find((_) => true),
|
||||
queryParams
|
||||
queryParams,
|
||||
);
|
||||
} else if ((!!result?.clear || this._activatedRoute.outlet === 'primary') && this.isDesktopLarge) {
|
||||
await this._navigationService.getCustomerOrdersResultsPath(processId, { queryParams }).navigate();
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this._customerOrderSearchStore.searchResultClearedSubject.pipe(takeUntil(this._onDestroy$)).subscribe((_) => this.clearSelectedItems());
|
||||
@@ -288,13 +280,13 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
...this.cleanupQueryParams(this._customerOrderSearchStore?.filter?.getQueryParams()),
|
||||
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
|
||||
};
|
||||
|
||||
this._customerOrderSearchStore?.setQueryParams(queryParams);
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this._router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
||||
if (event instanceof NavigationStart) {
|
||||
this.cacheResults();
|
||||
this._addScrollPositionToCache();
|
||||
}
|
||||
});
|
||||
@@ -312,7 +304,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
if (this._activatedRoute.outlet === 'primary') {
|
||||
this._cache.set<number>(
|
||||
{ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN },
|
||||
this.scrollContainer?.scrollPos
|
||||
this.scrollContainer?.scrollPos,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -335,18 +327,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
this._onDestroy$.complete();
|
||||
|
||||
this._searchResultSubscription.unsubscribe();
|
||||
|
||||
const queryToken = {
|
||||
...this._customerOrderSearchStore.filter?.getQueryParams(),
|
||||
processId: this._customerOrderSearchStore.processId,
|
||||
branchId: String(this._customerOrderSearchStore.selectedBranch?.id),
|
||||
};
|
||||
this._customerOrderSearchStore.setCache({
|
||||
queryToken,
|
||||
hits: this._customerOrderSearchStore.hits,
|
||||
results: this._customerOrderSearchStore.results,
|
||||
});
|
||||
|
||||
await this.updateBreadcrumb(this._customerOrderSearchStore.processId, this._customerOrderSearchStore.filter?.getQueryParams());
|
||||
}
|
||||
|
||||
@@ -416,6 +396,20 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
}
|
||||
}
|
||||
|
||||
cacheResults() {
|
||||
const queryToken = {
|
||||
...this._customerOrderSearchStore.filter?.getQueryParams(),
|
||||
processId: this._customerOrderSearchStore.processId,
|
||||
branchId: String(this._customerOrderSearchStore.selectedBranch?.id),
|
||||
};
|
||||
|
||||
this._customerOrderSearchStore.setCache({
|
||||
queryToken,
|
||||
hits: this._customerOrderSearchStore.hits,
|
||||
results: this._customerOrderSearchStore.results,
|
||||
});
|
||||
}
|
||||
|
||||
getBreadcrumbName(params: Record<string, string>) {
|
||||
const input = params?.main_qs;
|
||||
|
||||
@@ -435,8 +429,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
search({ filter, clear = false }: { filter?: Filter; clear?: boolean }) {
|
||||
if (!!filter) {
|
||||
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||
this._customerOrderSearchStore.setQueryParams(filter?.getQueryParams());
|
||||
}
|
||||
|
||||
this._customerOrderSearchStore.search({ clear });
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export class CustomerOrderComponent implements OnInit {
|
||||
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
|
||||
}
|
||||
|
||||
_onDestroy$ = new Subject<boolean>();
|
||||
_onDestroy$ = new Subject<void>();
|
||||
|
||||
get isTablet$() {
|
||||
return this._environmentService.matchTablet$;
|
||||
@@ -43,12 +43,12 @@ export class CustomerOrderComponent implements OnInit {
|
||||
private _renderer: Renderer2,
|
||||
private _environmentService: EnvironmentService,
|
||||
public auth: AuthService,
|
||||
private _store: CustomerOrderSearchStore
|
||||
private _store: CustomerOrderSearchStore,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedBranch$ = this.application.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.application.getSelectedBranch$(Number(processId)))
|
||||
switchMap((processId) => this.application.getSelectedBranch$(Number(processId))),
|
||||
);
|
||||
|
||||
/* Ticket #4544 - Suchrequest abbrechen bei Prozesswechsel
|
||||
|
||||
@@ -41,8 +41,10 @@ export interface CustomerTypeSelectorState {
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSelectorState>
|
||||
implements OnInit, OnDestroy, ControlValueAccessor {
|
||||
export class CustomerTypeSelectorComponent
|
||||
extends ComponentStore<CustomerTypeSelectorState>
|
||||
implements OnInit, OnDestroy, ControlValueAccessor
|
||||
{
|
||||
@ViewChildren(UiCheckboxComponent)
|
||||
checkboxes: QueryList<UiCheckboxComponent>;
|
||||
|
||||
@@ -133,7 +135,7 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
|
||||
|
||||
return result;
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -144,7 +146,11 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
|
||||
onChange = (value: string) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _checkoutService: DomainCheckoutService, private _cache: CacheService, private _cdr: ChangeDetectorRef) {
|
||||
constructor(
|
||||
private _checkoutService: DomainCheckoutService,
|
||||
private _cache: CacheService,
|
||||
private _cdr: ChangeDetectorRef,
|
||||
) {
|
||||
super({
|
||||
processId: undefined,
|
||||
customerType: undefined,
|
||||
@@ -165,10 +171,10 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
|
||||
this.patchState({ options });
|
||||
this._cache.set('customerTypeOptions', options);
|
||||
},
|
||||
(err) => {}
|
||||
)
|
||||
)
|
||||
)
|
||||
(err) => {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -197,7 +203,7 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
|
||||
setValue(value: { p4mUser?: boolean; customerType?: string } | string) {
|
||||
const initial = { p4mUser: this.p4mUser, customerType: this.customerType };
|
||||
|
||||
if (isString(value)) {
|
||||
if (typeof value === 'string') {
|
||||
this.value = value;
|
||||
} else {
|
||||
if (isBoolean(value.p4mUser)) {
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
(onInit)="addAddressGroup($event)"
|
||||
(onDestroy)="removeAddressGroup()"
|
||||
[data]="data?.address"
|
||||
[tabIndexStart]="nameFormBlock?.tabIndexEnd + 1"
|
||||
[requiredMarks]="addressRequiredMarks"
|
||||
[validatorFns]="addressValidatorFns"
|
||||
[readonly]="readonly"
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
>
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { FormBlockGroup } from '../form-block';
|
||||
import { NameFormBlockData } from './name-form-block-data';
|
||||
import { Gender } from '@swagger/crm';
|
||||
import { GenderSettingsService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-name-form-block',
|
||||
@@ -15,18 +15,7 @@ export class NameFormBlockComponent extends FormBlockGroup<NameFormBlockData> {
|
||||
return this.tabIndexStart + 3;
|
||||
}
|
||||
|
||||
displayGenderNameFn = (gender: Gender) => {
|
||||
if (gender == 2) {
|
||||
return 'Herr';
|
||||
}
|
||||
if (gender == 4) {
|
||||
return 'Frau';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
constructor() {
|
||||
constructor(public genderSettings: GenderSettingsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ChangeDetectorRef, Directive, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
|
||||
import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AddressDTO, CustomerDTO, CustomerInfoDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
|
||||
import { AddressDTO, CustomerDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { UiValidators } from '@ui/validators';
|
||||
import { isNull, merge } from 'lodash';
|
||||
import { isNull } from 'lodash';
|
||||
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
|
||||
import {
|
||||
first,
|
||||
@@ -76,7 +76,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
protected modal: UiModalService,
|
||||
protected breadcrumb: BreadcrumbService,
|
||||
protected cdr: ChangeDetectorRef,
|
||||
protected customerSearchNavigation: CustomerSearchNavigation
|
||||
protected customerSearchNavigation: CustomerSearchNavigation,
|
||||
) {
|
||||
this._initProcessId$();
|
||||
}
|
||||
@@ -88,7 +88,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
map((data) => +data.processId),
|
||||
tap((processId) => (this.latestProcessId = processId)),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1)
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
this.processId$
|
||||
@@ -106,7 +106,8 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.updateBreadcrumb(this.latestProcessId, this.formData);
|
||||
// Fix für #4676 - Breadcrumb wurde beim Schließen des Prozesses neu erstellt und nicht korrekt gelöscht
|
||||
// this.updateBreadcrumb(this.latestProcessId, this.formData);
|
||||
this.onDestroy$.next();
|
||||
this.onDestroy$.complete();
|
||||
this.busy$.complete();
|
||||
@@ -117,7 +118,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
const formData = await this.activatedRoute.queryParams
|
||||
.pipe(
|
||||
map((params) => params['formData']),
|
||||
first()
|
||||
first(),
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
@@ -189,6 +190,47 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
minBirthDateValidator = (): ValidatorFn => {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const minAge = 16; // 16 years
|
||||
|
||||
if (!control.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const controlBirthDate = new Date(control.value);
|
||||
const minBirthDate = new Date();
|
||||
minBirthDate.setFullYear(minBirthDate.getFullYear() - minAge);
|
||||
|
||||
// Check if customer is over 16 years old
|
||||
if (this._checkIfAgeOver16(controlBirthDate, minBirthDate)) {
|
||||
return null;
|
||||
} else {
|
||||
return { minBirthDate: `Teilnahme ab ${minAge} Jahren` };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
private _checkIfAgeOver16(inputDate: Date, minBirthDate: Date): boolean {
|
||||
// Check year
|
||||
if (inputDate.getFullYear() < minBirthDate.getFullYear()) {
|
||||
return true;
|
||||
}
|
||||
// Check Year + Month
|
||||
else if (inputDate.getFullYear() === minBirthDate.getFullYear() && inputDate.getMonth() < minBirthDate.getMonth()) {
|
||||
return true;
|
||||
}
|
||||
// Check Year + Month + Day
|
||||
else if (
|
||||
inputDate.getFullYear() === minBirthDate.getFullYear() &&
|
||||
inputDate.getMonth() === minBirthDate.getMonth() &&
|
||||
inputDate.getDate() <= minBirthDate.getDate()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
emailExistsValidator: AsyncValidatorFn = (control) => {
|
||||
return of(control.value).pipe(
|
||||
tap((_) => this.customerExists$.next(false)),
|
||||
@@ -209,7 +251,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
return of({ invalid: 'E-Mail ist ungültig' });
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
tap((error) => {
|
||||
@@ -218,7 +260,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
}
|
||||
control.markAsTouched();
|
||||
this.cdr.markForCheck();
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -265,13 +307,13 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
return of({ invalid: 'Kundenkartencode ist ungültig' });
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
tap(() => {
|
||||
control.markAsTouched();
|
||||
this.cdr.markForCheck();
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -377,7 +419,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
} catch (error) {
|
||||
this.form.enable();
|
||||
setTimeout(() => {
|
||||
this.addressFormBlock.setAddressValidationError(error.error.invalidProperties);
|
||||
this.deviatingDeliveryAddressFormBlock.setAddressValidationError(error.error.invalidProperties);
|
||||
}, 10);
|
||||
|
||||
return;
|
||||
|
||||
@@ -41,7 +41,7 @@ export class CreateB2BCustomerComponent extends AbstractCreateCustomer {
|
||||
deviatingNameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
|
||||
|
||||
deviatingNameValidationFns: Record<string, ValidatorFn[]> = {
|
||||
gender: [Validators.required, Validators.min(1)],
|
||||
gender: [Validators.required],
|
||||
firstName: [Validators.required],
|
||||
lastName: [Validators.required],
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ export class CreateGuestCustomerComponent extends AbstractCreateCustomer {
|
||||
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
|
||||
firstName: [Validators.required],
|
||||
lastName: [Validators.required],
|
||||
gender: [Validators.required, Validators.min(1)],
|
||||
gender: [Validators.required],
|
||||
title: [],
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ export class CreateGuestCustomerComponent extends AbstractCreateCustomer {
|
||||
deviatingNameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
|
||||
|
||||
deviatingNameValidationFns: Record<string, ValidatorFn[]> = {
|
||||
gender: [Validators.required, Validators.min(1)],
|
||||
gender: [Validators.required],
|
||||
firstName: [Validators.required],
|
||||
lastName: [Validators.required],
|
||||
};
|
||||
|
||||
@@ -64,6 +64,12 @@
|
||||
>
|
||||
</app-name-form-block>
|
||||
|
||||
<p class="info" *ngIf="customerType === 'webshop-p4m'">
|
||||
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
|
||||
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere als
|
||||
die Übermittlungskosten nach den Basistarifen entstehen.
|
||||
</p>
|
||||
|
||||
<app-email-form-block
|
||||
class="flex-grow"
|
||||
#email
|
||||
|
||||
@@ -36,7 +36,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
|
||||
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
|
||||
firstName: [Validators.required],
|
||||
lastName: [Validators.required],
|
||||
gender: [Validators.required, Validators.min(1)],
|
||||
gender: [Validators.required],
|
||||
title: [],
|
||||
};
|
||||
|
||||
@@ -70,9 +70,9 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
|
||||
|
||||
agbValidatorFns = [Validators.requiredTrue];
|
||||
|
||||
birthDateValidatorFns = [Validators.required];
|
||||
birthDateValidatorFns = [];
|
||||
|
||||
existingCustomer$: Observable<CustomerInfoDTO | null>;
|
||||
existingCustomer$: Observable<CustomerInfoDTO | CustomerDTO | null>;
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
@@ -84,7 +84,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
|
||||
return this.fetchCustomerInfo();
|
||||
}
|
||||
return of(null);
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.existingCustomer$
|
||||
@@ -94,12 +94,12 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
|
||||
if (info) {
|
||||
return this.customerService.getCustomer(info.id, 2).pipe(
|
||||
map((res) => res.result),
|
||||
catchError((err) => NEVER)
|
||||
catchError((err) => NEVER),
|
||||
);
|
||||
}
|
||||
return NEVER;
|
||||
}),
|
||||
withLatestFrom(this.processId$)
|
||||
withLatestFrom(this.processId$),
|
||||
)
|
||||
.subscribe(([customer, processId]) => {
|
||||
if (customer) {
|
||||
@@ -138,6 +138,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
|
||||
|
||||
initMarksAndValidators() {
|
||||
this.asyncLoyaltyCardValidatorFn = [this.checkLoyalityCardValidator];
|
||||
this.birthDateValidatorFns = [Validators.required, this.minBirthDateValidator()];
|
||||
if (this._customerType === 'webshop') {
|
||||
this.emailRequiredMark = true;
|
||||
this.emailValidatorFn = [Validators.required, Validators.email, validateEmail];
|
||||
@@ -165,7 +166,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
|
||||
data: err,
|
||||
});
|
||||
return [null];
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export class CreateStoreCustomerComponent extends AbstractCreateCustomer {
|
||||
|
||||
nameValidationFns: Record<string, ValidatorFn[]> = {
|
||||
title: [],
|
||||
gender: [Validators.required, Validators.min(1)],
|
||||
gender: [Validators.required],
|
||||
firstName: [Validators.required],
|
||||
lastName: [Validators.required],
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ export class CreateWebshopCustomerComponent extends AbstractCreateCustomer {
|
||||
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
|
||||
firstName: [Validators.required],
|
||||
lastName: [Validators.required],
|
||||
gender: [Validators.required, Validators.min(1)],
|
||||
gender: [Validators.required],
|
||||
title: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ export class UpdateP4MWebshopCustomerComponent extends AbstractCreateCustomer im
|
||||
|
||||
agbValidatorFns = [Validators.requiredTrue];
|
||||
|
||||
birthDateValidatorFns = [Validators.required];
|
||||
birthDateValidatorFns = [Validators.required, this.minBirthDateValidator()];
|
||||
|
||||
nameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
|
||||
|
||||
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
|
||||
firstName: [Validators.required],
|
||||
lastName: [Validators.required],
|
||||
gender: [Validators.required, Validators.min(1)],
|
||||
gender: [Validators.required],
|
||||
title: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
<form [formGroup]="formGroup" (ngSubmit)="save()">
|
||||
<shared-form-control label="Anrede">
|
||||
<shared-select formControlName="gender" placeholder="Anrede" tabindex="1" [autofocus]="true">
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { map } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { RouterLink } from '@angular/router';
|
||||
@@ -35,7 +35,7 @@ import { RouterLink } from '@angular/router';
|
||||
})
|
||||
export class AddBillingAddressMainViewComponent {
|
||||
formGroup = new FormGroup({
|
||||
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
|
||||
gender: new FormControl<Gender>(undefined, [Validators.required]),
|
||||
title: new FormControl<string>(undefined),
|
||||
firstName: new FormControl<string>(undefined, [Validators.required]),
|
||||
lastName: new FormControl<string>(undefined, [Validators.required]),
|
||||
@@ -59,7 +59,8 @@ export class AddBillingAddressMainViewComponent {
|
||||
private _customerService: CrmCustomerService,
|
||||
private _addressSelection: AddressSelectionModalService,
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
public genderSettings: GenderSettingsService
|
||||
) {}
|
||||
|
||||
async save() {
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
|
||||
<shared-form-control label="Anrede">
|
||||
<shared-select formControlName="gender" placeholder="Anrede" tabindex="4" [autofocus]="true">
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { map, takeUntil } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
@@ -41,7 +41,7 @@ export class AddShippingAddressMainViewComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
formGroup = new FormGroup({
|
||||
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
|
||||
gender: new FormControl<Gender>(undefined, [Validators.required]),
|
||||
title: new FormControl<string>(undefined),
|
||||
firstName: new FormControl<string>(undefined, [Validators.required]),
|
||||
lastName: new FormControl<string>(undefined, [Validators.required]),
|
||||
@@ -65,7 +65,8 @@ export class AddShippingAddressMainViewComponent implements OnInit, OnDestroy {
|
||||
private _customerService: CrmCustomerService,
|
||||
private _addressSelection: AddressSelectionModalService,
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
public genderSettings: GenderSettingsService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject, effect, untracked } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, Subject, Subscription, fromEvent } from 'rxjs';
|
||||
import { CustomerSearchStore } from './store/customer-search.store';
|
||||
import { provideComponentStore } from '@ngrx/component-store';
|
||||
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
|
||||
@@ -8,6 +8,7 @@ import { delay, filter, first, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { CustomerCreateNavigation, CustomerSearchNavigation } from '@shared/services';
|
||||
import { CustomerSearchMainAutocompleteProvider } from './providers/customer-search-main-autocomplete.provider';
|
||||
import { FilterAutocompleteProvider } from '@shared/components/filter';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-search',
|
||||
@@ -24,6 +25,10 @@ import { FilterAutocompleteProvider } from '@shared/components/filter';
|
||||
],
|
||||
})
|
||||
export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
private searchStore = inject(CustomerSearchStore);
|
||||
|
||||
keyEscPressed = toSignal(fromEvent(document, 'keydown').pipe(filter((e: KeyboardEvent) => e.key === 'Escape')));
|
||||
|
||||
get breadcrumb() {
|
||||
let breadcrumb: string;
|
||||
|
||||
@@ -38,7 +43,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
|
||||
private _breadcrumbs$ = this._store.processId$.pipe(
|
||||
filter((id) => !!id),
|
||||
switchMap((id) => this._breadcrumbService.getBreadcrumbsByKeyAndTag$(id, 'customer'))
|
||||
switchMap((id) => this._breadcrumbService.getBreadcrumbsByKeyAndTag$(id, 'customer')),
|
||||
);
|
||||
|
||||
side$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
@@ -73,7 +78,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
private _router: Router,
|
||||
private _breadcrumbService: BreadcrumbService,
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
private _createNavigation: CustomerCreateNavigation
|
||||
private _createNavigation: CustomerCreateNavigation,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -542,4 +547,11 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
this._breadcrumbService.removeBreadcrumb(orderDetailsHistoryBreadcrumb.id);
|
||||
}
|
||||
}
|
||||
|
||||
cancelSearchEffect = effect(() => {
|
||||
const event = this.keyEscPressed();
|
||||
untracked(() => {
|
||||
this.searchStore.cancelSearch();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, Host } from '@angular/core';
|
||||
import { CustomerSearchStore } from '../../store';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { debounceTime, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { Observable, Subject, combineLatest } from 'rxjs';
|
||||
import { AssignedPayerDTO, CustomerDTO, ListResponseArgsOfAssignedPayerDTO } from '@swagger/crm';
|
||||
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
|
||||
@@ -27,8 +27,10 @@ interface DetailsMainViewBillingAddressesComponentState {
|
||||
standalone: true,
|
||||
imports: [NgIf, NgFor, AsyncPipe, CustomerPipesModule, RouterLink],
|
||||
})
|
||||
export class DetailsMainViewBillingAddressesComponent extends ComponentStore<DetailsMainViewBillingAddressesComponentState>
|
||||
implements OnInit, OnDestroy {
|
||||
export class DetailsMainViewBillingAddressesComponent
|
||||
extends ComponentStore<DetailsMainViewBillingAddressesComponentState>
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
assignedPayers$ = this.select((state) => state.assignedPayers);
|
||||
|
||||
selectedPayer$ = this.select((state) => state.selectedPayer);
|
||||
@@ -36,7 +38,7 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
isNotBusinessKonto$ = this._store.isBusinessKonto$.pipe(map((isBusinessKonto) => !isBusinessKonto));
|
||||
|
||||
showCustomerAddress$ = combineLatest([this._store.isBusinessKonto$, this._store.isMitarbeiter$, this._store.isKundenkarte$]).pipe(
|
||||
map(([isBusinessKonto, isMitarbeiter, isKundenkarte]) => isBusinessKonto || isMitarbeiter || isKundenkarte)
|
||||
map(([isBusinessKonto, isMitarbeiter, isKundenkarte]) => isBusinessKonto || isMitarbeiter || isKundenkarte),
|
||||
);
|
||||
|
||||
get showCustomerAddress() {
|
||||
@@ -47,12 +49,8 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
this._store.isOnlinekonto$,
|
||||
this._store.isOnlineKontoMitKundenkarte$,
|
||||
this._store.isKundenkarte$,
|
||||
this._store.isMitarbeiter$,
|
||||
]).pipe(
|
||||
map(
|
||||
([isOnlinekonto, isOnlineKontoMitKundenkarte, isKundenkarte, isMitarbeiter]) =>
|
||||
isOnlinekonto || isOnlineKontoMitKundenkarte || isKundenkarte || isMitarbeiter
|
||||
)
|
||||
map(([isOnlinekonto, isOnlineKontoMitKundenkarte, isKundenkarte]) => isOnlinekonto || isOnlineKontoMitKundenkarte || isKundenkarte),
|
||||
);
|
||||
|
||||
canEditAddress$ = combineLatest([this._store.isKundenkarte$]).pipe(map(([isKundenkarte]) => isKundenkarte));
|
||||
@@ -62,13 +60,13 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
editRoute$ = combineLatest([this._store.processId$, this._store.customerId$, this._store.isBusinessKonto$]).pipe(
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b }))
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b })),
|
||||
);
|
||||
|
||||
addBillingAddressRoute$ = combineLatest([this.canAddNewAddress$, this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([canAddNewAddress, processId, customerId]) =>
|
||||
canAddNewAddress ? this._navigation.addBillingAddressRoute({ processId, customerId }) : undefined
|
||||
)
|
||||
canAddNewAddress ? this._navigation.addBillingAddressRoute({ processId, customerId }) : undefined,
|
||||
),
|
||||
);
|
||||
|
||||
constructor(
|
||||
@@ -76,7 +74,7 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
private _store: CustomerSearchStore,
|
||||
private _customerService: CrmCustomerService,
|
||||
private _modal: UiModalService,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
) {
|
||||
super({
|
||||
assignedPayers: [],
|
||||
@@ -89,12 +87,15 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._store.customerId$.pipe(takeUntil(this._onDestroy$)).subscribe((customerId) => {
|
||||
this.resetStore();
|
||||
if (customerId) {
|
||||
this.loadAssignedPayers(customerId);
|
||||
}
|
||||
});
|
||||
combineLatest([this._store.customerId$, this._store.isMitarbeiter$])
|
||||
.pipe(takeUntil(this._onDestroy$), debounceTime(250))
|
||||
.subscribe(([customerId, isMitarbeiter]) => {
|
||||
this.resetStore();
|
||||
// #4715 Hier erfolgt ein Check auf Mitarbeiter, da Mitarbeiter keine zusätzlichen Rechnungsadressen haben sollen
|
||||
if (customerId && !isMitarbeiter) {
|
||||
this.loadAssignedPayers(customerId);
|
||||
}
|
||||
});
|
||||
|
||||
combineLatest([this.selectedPayer$, this._store.customer$])
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
@@ -151,9 +152,9 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
switchMap((customerId) =>
|
||||
this._customerService
|
||||
.getAssignedPayers({ customerId })
|
||||
.pipe(tapResponse(this.handleLoadAssignedPayersResponse, this.handleLoadAssignedPayersError))
|
||||
)
|
||||
)
|
||||
.pipe(tapResponse(this.handleLoadAssignedPayersResponse, this.handleLoadAssignedPayersError)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
handleLoadAssignedPayersResponse = (response: ListResponseArgsOfAssignedPayerDTO) => {
|
||||
|
||||
@@ -39,7 +39,9 @@
|
||||
<div class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3">
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Erstellungsdatum</div>
|
||||
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'HH:mm' }} Uhr</div>
|
||||
<div *ngIf="created$ | async; let created" class="data-value">
|
||||
{{ created | date: 'dd.MM.yyyy' }} | {{ created | date: 'HH:mm' }} Uhr
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer</div>
|
||||
@@ -111,7 +113,7 @@
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Land</div>
|
||||
<div class="data-value">{{ country$ | async | country }}</div>
|
||||
<div *ngIf="country$ | async; let country" class="data-value">{{ country | country }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Festnetznr.</div>
|
||||
@@ -123,6 +125,16 @@
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!(isBusinessKonto$ | async)">
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Geburtstag</div>
|
||||
<div class="data-value">{{ dateOfBirth$ | async | date: 'dd.MM.yyyy' }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!(isBusinessKonto$ | async) && (organisationName$ | async)">
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Firmenname</div>
|
||||
<div class="data-value">{{ organisationName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { ShippingAddressDTO, NotificationChannel, ShoppingCartDTO, PayerDTO, BuyerDTO } from '@swagger/checkout';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
@@ -10,16 +10,11 @@ import { UiModalService } from '@ui/modal';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { log, logAsync } from '@utils/common';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { MessageModalComponent, MessageModalData } from '@shared/modals/message-modal';
|
||||
|
||||
const GENDER_MAP = {
|
||||
2: 'Herr',
|
||||
4: 'Frau',
|
||||
};
|
||||
|
||||
export interface CustomerDetailsViewMainState {
|
||||
isBusy: boolean;
|
||||
shoppingCart: ShoppingCartDTO;
|
||||
@@ -34,11 +29,13 @@ export interface CustomerDetailsViewMainState {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDetailsViewMainState> implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
customerService = inject(CrmCustomerService);
|
||||
|
||||
fetching$ = this._store.fetchingCustomer$;
|
||||
fetching$ = combineLatest([this._store.fetchingCustomer$, this._store.fetchingCustomerList$]).pipe(
|
||||
map(([fetchingCustomer, fetchingList]) => fetchingCustomer || fetchingList),
|
||||
);
|
||||
|
||||
isBusy$ = this.select((s) => s.isBusy);
|
||||
|
||||
@@ -49,31 +46,31 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
customerId$ = this._store.customerId$;
|
||||
|
||||
historyRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.historyRoute({ processId, customerId }))
|
||||
map(([processId, customerId]) => this._navigation.historyRoute({ processId, customerId })),
|
||||
);
|
||||
|
||||
ordersRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.ordersRoute({ processId, customerId }))
|
||||
map(([processId, customerId]) => this._navigation.ordersRoute({ processId, customerId })),
|
||||
);
|
||||
|
||||
isB2b$ = this._store.isBusinessKonto$;
|
||||
|
||||
editRoute$ = combineLatest([this.processId$, this.customerId$, this.isB2b$]).pipe(
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b }))
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b })),
|
||||
);
|
||||
|
||||
showEditButton$ = combineLatest([this._store.isOnlinekonto$, this._store.isBestellungOhneKonto$]).pipe(
|
||||
map(([isOnlinekonto, isBestellungOhneKonto]) => !isOnlinekonto && !isBestellungOhneKonto)
|
||||
map(([isOnlinekonto, isBestellungOhneKonto]) => !isOnlinekonto && !isBestellungOhneKonto),
|
||||
);
|
||||
|
||||
hasKundenkarte$ = combineLatest([this._store.isKundenkarte$, this._store.isOnlineKontoMitKundenkarte$]).pipe(
|
||||
map(([isKundenkarte, isOnlineKontoMitKundenkarte]) => isKundenkarte || isOnlineKontoMitKundenkarte)
|
||||
map(([isKundenkarte, isOnlineKontoMitKundenkarte]) => isKundenkarte || isOnlineKontoMitKundenkarte),
|
||||
);
|
||||
|
||||
kundenkarteRoute$ = combineLatest([this.hasKundenkarte$, this.processId$, this.customerId$]).pipe(
|
||||
map(([hasKundenkarte, processId, customerId]) =>
|
||||
hasKundenkarte ? this._navigation.kundenkarteRoute({ processId, customerId }) : undefined
|
||||
)
|
||||
hasKundenkarte ? this._navigation.kundenkarteRoute({ processId, customerId }) : undefined,
|
||||
),
|
||||
);
|
||||
|
||||
customerType$ = this._store.select((s) => s.customer?.features?.find((f) => f.enabled)?.description);
|
||||
@@ -86,7 +83,7 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
|
||||
customerNumberBeeline$ = this._store.select((s) => s.customer?.linkedRecords?.find((r) => r.repository === 'beeline')?.number);
|
||||
|
||||
gender$ = this._store.select((s) => GENDER_MAP[s.customer?.gender]);
|
||||
gender$ = this._store.select((s) => this._genderSettings.getGenderByValue(s.customer?.gender));
|
||||
|
||||
title$ = this._store.select((s) => s.customer?.title);
|
||||
|
||||
@@ -124,6 +121,8 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
|
||||
shoppingCartHasNoItems$ = this.shoppingCartHasItems$.pipe(map((hasItems) => !hasItems));
|
||||
|
||||
dateOfBirth$ = this._store.select((s) => s.customer?.dateOfBirth);
|
||||
|
||||
get isBusy() {
|
||||
return this.get((s) => s.isBusy);
|
||||
}
|
||||
@@ -181,6 +180,10 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
return this.get((s) => s.payer);
|
||||
}
|
||||
|
||||
get snapshot() {
|
||||
return this._activatedRoute.snapshot;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
@@ -189,7 +192,9 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
private _application: ApplicationService,
|
||||
private _catalogNavigation: ProductCatalogNavigationService,
|
||||
private _checkoutNavigation: CheckoutNavigationService,
|
||||
private _router: Router
|
||||
private _router: Router,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _genderSettings: GenderSettingsService,
|
||||
) {
|
||||
super({ isBusy: false, shoppingCart: undefined, shippingAddress: undefined, payer: undefined });
|
||||
}
|
||||
@@ -216,12 +221,27 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
this._checkoutService.getShoppingCart({
|
||||
processId: pid,
|
||||
latest: true,
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.subscribe((shoppingCart) => {
|
||||
this.patchState({ shoppingCart });
|
||||
});
|
||||
|
||||
// #4564 Fix - Da der Kunde bei einer erneuten Suche auf undefined gesetzt wird, muss man diesen erneut nach erfolgreicher Suche setzen.
|
||||
// Dies geschieht bereits in der customer-search.component.ts immer wenn eine Navigation ausgeführt wird (sprich für Fälle in denen ein neuer Kunde gesetzt wird).
|
||||
// Falls aber nach exakt dem gleichen Kunden gesucht wird, muss man diesen hier manuell erneut setzen, ansonsten bleibt dieser im Store auf undefined.
|
||||
// Da dies nur für die Detailansicht relevant ist, wird hier auch auf hits 1 überprüft, ansonsten befindet man sich sowieso in der Trefferliste.
|
||||
this._store.customerListResponse$.pipe(takeUntil(this._onDestroy$)).subscribe(([result]) => {
|
||||
if (result.hits === 1) {
|
||||
const customerId = result?.result[0].id;
|
||||
const selectedCustomerId = +this.snapshot.params.customerId;
|
||||
|
||||
if (customerId === selectedCustomerId) {
|
||||
this._store.selectCustomer({ customerId });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -409,12 +429,12 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
|
||||
@log
|
||||
_patchProcessName() {
|
||||
let name = `${this.customer.firstName} ${this.customer.lastName}`;
|
||||
let name = `${this.customer.firstName ?? ''} ${this.customer.lastName ?? ''}`;
|
||||
|
||||
// Ticket #4458 Es kann vorkommen, dass B2B Konten keinen Firmennamen hinterlegt haben
|
||||
// zusätzlich kanne es bei Mitarbeiter Konten vorkommen, dass die Namen in der Organisation statt im Kundennamen hinterlegt sind
|
||||
if ((this._store.isBusinessKonto && this.customer.organisation?.name) || (!this.customer.firstName && !this.customer.lastName)) {
|
||||
name = `${this.customer.organisation?.name}`;
|
||||
name = `${this.customer.organisation?.name ?? ''}`;
|
||||
}
|
||||
|
||||
this._application.patchProcess(this.processId, {
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
<form [formGroup]="formGroup" (ngSubmit)="save()">
|
||||
<shared-form-control label="Anrede">
|
||||
<shared-select formControlName="gender" placeholder="Anrede" tabindex="1" [autofocus]="true">
|
||||
<shared-select-option [value]="2">Herr</shared-select-option>
|
||||
<shared-select-option [value]="4">Frau</shared-select-option>
|
||||
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf } from '@angular/common';
|
||||
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
@@ -39,7 +39,7 @@ export class EditBillingAddressMainViewComponent extends ComponentStore<EditBill
|
||||
private _cdr = inject(ChangeDetectorRef);
|
||||
|
||||
formGroup = new FormGroup({
|
||||
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
|
||||
gender: new FormControl<Gender>(undefined, [Validators.required]),
|
||||
title: new FormControl<string>(undefined),
|
||||
firstName: new FormControl<string>(undefined, [Validators.required]),
|
||||
lastName: new FormControl<string>(undefined, [Validators.required]),
|
||||
@@ -74,7 +74,8 @@ export class EditBillingAddressMainViewComponent extends ComponentStore<EditBill
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _modal: UiModalService
|
||||
private _modal: UiModalService,
|
||||
public genderSettings: GenderSettingsService
|
||||
) {
|
||||
super({ payer: undefined });
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
|
||||
<ui-form-control [clearable]="true" label="Anrede" variant="inline">
|
||||
<ui-select formControlName="gender" tabindex="4">
|
||||
<ui-select-option [value]="2" label="Herr"></ui-select-option>
|
||||
<ui-select-option [value]="4" label="Frau"></ui-select-option>
|
||||
<ui-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value" [label]="gender.label"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<ui-form-control [clearable]="true" label="Titel" variant="inline">
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
|
||||
<ui-form-control [clearable]="true" label="Anrede" variant="inline">
|
||||
<ui-select formControlName="gender" tabindex="1">
|
||||
<ui-select-option [value]="2" label="Herr"></ui-select-option>
|
||||
<ui-select-option [value]="4" label="Frau"></ui-select-option>
|
||||
<ui-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value" [label]="gender.label"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<ui-form-control [clearable]="true" label="Titel" variant="inline">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user