Merged PR 1274: Merge Dev => Release

Related work items: #2737, #2790, #3150, #3157, #3158
This commit is contained in:
Lorenz Hilpert
2022-06-10 13:16:11 +00:00
1672 changed files with 501 additions and 69289 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -877,6 +877,10 @@ export class DomainCheckoutService {
return this.store.select(DomainCheckoutSelectors.selectOrders);
}
removeAllOrders() {
this.store.dispatch(DomainCheckoutActions.removeAllOrders());
}
setSpecialComment({ processId, agentComment }: { processId: number; agentComment: string }) {
this.store.dispatch(DomainCheckoutActions.setSpecialComment({ processId, agentComment }));
}

View File

@@ -52,6 +52,8 @@ export const removeProcess = createAction(`${prefix} Remove Process`, props<{ pr
export const setOrders = createAction(`${prefix} Add Orders`, props<{ orders: DisplayOrderDTO[] }>());
export const removeAllOrders = createAction(`${prefix} Remove All Orders`);
export const setBuyer = createAction(`${prefix} Set Buyer`, props<{ processId: number; buyer: BuyerDTO }>());
export const setPayer = createAction(`${prefix} Set Payer`, props<{ processId: number; payer: PayerDTO }>());

View File

@@ -72,7 +72,11 @@ const _domainCheckoutReducer = createReducer(
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.removeProcess, (s, { processId }) => storeCheckoutAdapter.removeOne(processId, s)),
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders })),
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders: [...s.orders, ...orders] })),
on(DomainCheckoutActions.removeAllOrders, (s) => ({
...s,
orders: [],
})),
on(DomainCheckoutActions.setOlaError, (s, { processId, olaErrorIds }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.olaErrorIds = olaErrorIds;

View File

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

View File

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

View File

@@ -1,12 +1,13 @@
import { DOCUMENT } from '@angular/common';
import { Component, Inject, OnInit, Renderer2 } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { SwUpdate } from '@angular/service-worker';
import { SwUpdate, UpdateAvailableEvent } from '@angular/service-worker';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { NotificationsHub } from '@hub/notifications';
import packageInfo from 'package';
import { interval } from 'rxjs';
import { interval, Observable, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
@Component({
selector: 'app-root',
@@ -14,7 +15,8 @@ import { interval } from 'rxjs';
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
_checkForUpdates: number = this._config.get('checkForUpdates');
private _checkForUpdates: number = this._config.get('checkForUpdates');
updateAvailableObs: Observable<UpdateAvailableEvent>;
get checkForUpdates(): number {
return this._checkForUpdates;
@@ -25,6 +27,8 @@ export class AppComponent implements OnInit {
this._checkForUpdates = time;
}
subscriptions = new Subscription();
constructor(
private readonly _config: Config,
private readonly _title: Title,
@@ -75,21 +79,30 @@ export class AppComponent implements OnInit {
}
checkForUpdateInterval() {
this._swUpdate.available.subscribe((availableEvent) => {
if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
this._notifications.updateNotification();
}
});
interval(this._checkForUpdates).subscribe(async () => await this._swUpdate.checkForUpdate());
this.updateAvailableObs = this._swUpdate.available.pipe(
tap((availableEvent) => {
if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
this._notifications.updateNotification();
this.subscriptions.unsubscribe();
}
})
);
this.subscriptions.add(
interval(this._checkForUpdates).subscribe(async () => {
await this._swUpdate.checkForUpdate();
})
);
}
async initialCheckForUpdate() {
const obs = this._swUpdate.available.subscribe((availableEvent) => {
if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
location.reload();
}
obs.unsubscribe();
});
this.updateAvailableObs = this._swUpdate.available.pipe(
tap((availableEvent) => {
if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
location.reload();
}
})
);
this.subscriptions.add(this.updateAvailableObs.subscribe());
await this._swUpdate.checkForUpdate();
}

View File

@@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateGoodsOutWithProcessIdGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
.getProcessById$(+route.params.processId)
.pipe(first())
.toPromise();
if (!process) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
await this._applicationService.createProcess({
id: +route.params.processId,
type: 'goods-out',
section: 'customer',
name: `Warenausgabe ${this.processNumber(processes)}`,
});
}
this._applicationService.activateProcess(+route.params.processId);
return true;
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
}
}

View File

@@ -1,24 +1,49 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { first } from 'rxjs/operators';
import { first, map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateGoodsOutGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService.getProcessById$(this._config.get('process.ids.goodsOut')).pipe(first()).toPromise();
if (!process) {
await this._applicationService.createProcess({
id: this._config.get('process.ids.goodsOut'),
type: 'goods-out',
section: 'customer',
name: 'Warenausgabe',
});
const processesIds = await this._applicationService
.getProcesses$('customer')
.pipe(
first(),
map((p) => {
return p.filter((process) => process.type === 'goods-out').map((process) => +process.name.replace('Warenausgabe', '').trim());
})
)
.toPromise();
let lastActivatedProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
)?.id;
console.log(processesIds);
// if (!lastActivatedProcessId) {
lastActivatedProcessId = Date.now();
await this._applicationService.createProcess({
id: lastActivatedProcessId,
type: 'goods-out',
section: 'customer',
name: `Warenausgabe ${Math.max(...processesIds, 0) + 1}`,
});
// }
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
return false;
}
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
url.push(...route.url.map((segment) => segment.path));
if (route.firstChild) {
return this.getUrlFromSnapshot(route.firstChild, url);
}
this._applicationService.activateProcess(this._config.get('process.ids.goodsOut'));
return true;
return url.filter((segment) => !!segment);
}
}

View File

@@ -3,6 +3,7 @@ export * from './can-activate-cart.guard';
export * from './can-activate-customer-with-process-id.guard';
export * from './can-activate-customer.guard';
export * from './can-activate-goods-in.guard';
export * from './can-activate-goods-out-with-process-id.guard';
export * from './can-activate-goods-out.guard';
export * from './can-activate-product-with-process-id.guard';
export * from './can-activate-product.guard';

View File

@@ -1,6 +1,6 @@
import { Component, ChangeDetectionStrategy, ViewChildren, QueryList } from '@angular/core';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { first, map, shareReplay, switchMap } from 'rxjs/operators';
import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { ConfirmModalData, UiConfirmModalComponent, UiModalService } from '@ui/modal';
@@ -27,7 +27,11 @@ export class ShellComponent {
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
get activatedProcessId$() {
return this._appService.getActivatedProcessId$().pipe(shareReplay());
return this._appService.getActivatedProcessId$().pipe(
tap((activatedProcessId) => {
this.processTabs?.find((process) => process?.process?.id === activatedProcessId && !process?.isActive)?.slideIntoView();
})
);
}
get section$() {
@@ -98,7 +102,6 @@ export class ShellComponent {
async activateProcess(activatedProcessId: number) {
const latestCrumb = await this._breadcrumbService?.getLastActivatedBreadcrumbByKey$(activatedProcessId)?.pipe(first()).toPromise();
if (latestCrumb) {
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
} else {

View File

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

View File

@@ -28,7 +28,7 @@
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
@@ -62,3 +62,4 @@ import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
import 'hammerjs';

View File

@@ -48,7 +48,7 @@ hr {
.branch-info {
@apply flex flex-row;
max-width: 505px;
max-width: 485px;
.branch-name {
@apply font-bold whitespace-nowrap;

View File

@@ -1,10 +1,14 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainAvailabilityService, ItemData } from '@domain/availability';
import { DomainCatalogService } from '@domain/catalog';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ItemDTO, ResponseArgsOfItemDTO } from '@swagger/cat';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { combineLatest, Observable, of } from 'rxjs';
import { catchError, filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { catchError, filter, first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
export interface ArticleDetailsState {
fetchingItem?: boolean;
@@ -241,7 +245,11 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
constructor(
private readonly domainCatalogService: DomainCatalogService,
private readonly domainAvailabilityService: DomainAvailabilityService
private readonly domainAvailabilityService: DomainAvailabilityService,
private readonly _appService: ApplicationService,
private readonly _router: Router,
private readonly _breadcrumb: BreadcrumbService,
private readonly _modal: UiModalService
) {
super({});
}
@@ -253,9 +261,15 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
switchMap((id) => this.domainCatalogService.getDetailsById({ id })),
tapResponse<ResponseArgsOfItemDTO>(
(response) => this.patchState({ item: response.result, fetchingItem: false }),
(err) => {
console.error('loadItemById failed', err);
async (err) => {
this.patchState({ item: undefined, fetchingItem: false });
const errorModalRef = this._modal.open({
content: UiErrorModalComponent,
title: 'Fehler beim Laden des Artikels über die ID',
data: { message: 'Sie kehren nun auf die ursprüngliche Artikeldetailseite zurück' },
});
await errorModalRef.afterClosed$.toPromise();
await this.navigateBack();
}
)
)
@@ -268,11 +282,26 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
switchMap((ean) => this.domainCatalogService.getDetailsByEan({ ean })),
tapResponse<ResponseArgsOfItemDTO>(
(response) => this.patchState({ item: response.result, fetchingItem: false }),
(err) => {
console.error('loadItemByEan failed', err);
async (err) => {
this.patchState({ item: undefined, fetchingItem: false });
const errorModalRef = this._modal.open({
content: UiErrorModalComponent,
title: 'Fehler beim Laden des Artikels über die EAN',
data: { message: 'Sie kehren nun auf die ursprüngliche Artikeldetailseite zurück' },
});
await errorModalRef.afterClosed$.toPromise();
await this.navigateBack();
}
)
)
);
async navigateBack() {
const crumb = await this._breadcrumb.getLastActivatedBreadcrumbByKey$(this._appService.activatedProcessId).pipe(first()).toPromise();
if (crumb) {
await this._router.navigate([crumb.path]);
} else {
await this._router.navigate(['/kunde', this._appService.activatedProcessId, 'product', 'search']);
}
}
}

View File

@@ -1,4 +1,4 @@
<ng-container *ngIf="(groupedItems$ | async)?.length <= 0">
<ng-container *ngIf="(groupedItems$ | async)?.length <= 0 && !(fetching$ | async); else shoppingCart">
<div class="card stretch card-empty">
<div class="empty-message">
<span class="cart-icon">
@@ -20,7 +20,11 @@
</div>
</ng-container>
<ng-container *ngIf="(groupedItems$ | async)?.length > 0">
<div class="flex items-center justify-center card stretch card-empty" *ngIf="fetching$ | async">
<ui-spinner show="true"> </ui-spinner>
</div>
<ng-template #shoppingCart>
<ng-container *ngIf="shoppingCart$ | async; let shoppingCart">
<div class="card stretch">
<div class="cta-print-wrapper">
@@ -180,4 +184,4 @@
</div>
</div>
</ng-container>
</ng-container>
</ng-template>

View File

@@ -9,7 +9,7 @@ import { PrintModalData, PrintModalComponent } from '@modal/printer';
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from '../modals/purchasing-options-modal';
import { PurchasingOptions } from '../modals/purchasing-options-modal/purchasing-options-modal.store';
import { AuthService } from '@core/auth';
import { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { first, map, shareReplay, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
import { DomainCatalogService } from '@domain/catalog';
import { BreadcrumbService } from '@core/breadcrumb';
@@ -25,6 +25,7 @@ import { ComponentStore, tapResponse } from '@ngrx/component-store';
export interface CheckoutReviewComponentState {
shoppingCart: ShoppingCartDTO;
shoppingCartItems: ShoppingCartItemDTO[];
fetching: boolean;
}
@Component({
@@ -52,6 +53,14 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
}
readonly shoppingCartItems$ = this.select((s) => s.shoppingCartItems);
get fetching() {
return this.get((s) => s.fetching);
}
set fetching(fetching: boolean) {
this.patchState({ fetching });
}
readonly fetching$ = this.select((s) => s.fetching);
payer$ = this.applicationService.activatedProcessId$.pipe(
takeUntil(this._orderCompleted),
switchMap((processId) => this.domainCheckoutService.getPayer({ processId })),
@@ -234,6 +243,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
super({
shoppingCart: undefined,
shoppingCartItems: [],
fetching: false,
});
}
@@ -249,6 +259,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
loadShoppingCart = this.effect(($) =>
$.pipe(
tap(() => (this.fetching = true)),
withLatestFrom(this.applicationService.activatedProcessId$),
switchMap(([_, processId]) => {
return this.domainCheckoutService.getShoppingCart({ processId, latest: true }).pipe(
@@ -265,7 +276,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
() => {}
)
);
})
}),
tap(() => (this.fetching = false))
)
);
@@ -749,10 +761,11 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
} else {
try {
this.showOrderButtonSpinner = true;
await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
const orders = await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
const orderIds = orders.map((order) => order.id).join(',');
this._orderCompleted.next();
await this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'cart', 'summary']);
this.applicationService.removeProcess(this.applicationService.activatedProcessId);
await this.patchProcess(processId);
await this.router.navigate(['/kunde', processId, 'cart', 'summary', orderIds]);
} catch (error) {
const response = error?.error;
let message: string = response?.message ?? '';
@@ -771,8 +784,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
if (error.status === 409) {
this._orderCompleted.next();
await this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'cart', 'summary']);
this.applicationService.removeProcess(this.applicationService.activatedProcessId);
await this.patchProcess(processId);
await this.router.navigate(['/kunde', processId, 'cart', 'summary']);
}
} finally {
this.showOrderButtonSpinner = false;
@@ -780,4 +793,10 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
}
}
}
async patchProcess(processId: number) {
this.applicationService.patchProcess(processId, {
type: 'cart-checkout',
});
}
}

View File

@@ -63,7 +63,9 @@
<div class="row between">
<div class="product-name">
<img class="thumbnail" [src]="order.product?.ean | productImage: 30:50:true" />
<a class="name" [routerLink]="['/kunde', 'product', 'details', 'ean', order?.product?.ean]">{{ order?.product?.name }}</a>
<a class="name" [routerLink]="['/kunde', processId, 'product', 'details', 'ean', order?.product?.ean]">{{
order?.product?.name
}}</a>
</div>
<div class="product-details">

View File

@@ -1,17 +1,17 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core';
import { DomainCheckoutService } from '@domain/checkout';
import { UiModalService } from '@ui/modal';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { first, map, switchMap } from 'rxjs/operators';
import { debounceTime, first, map, shareReplay, switchMap } from 'rxjs/operators';
import { CrmCustomerService } from '@domain/crm';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { DomainOmsService } from '@domain/oms';
import { DomainCatalogService } from '@domain/catalog';
import { DisplayOrderItemDTO } from '@swagger/oms';
import { DisplayOrderDTO, DisplayOrderItemDTO } from '@swagger/oms';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
import { NEVER } from 'rxjs';
import { combineLatest, NEVER } from 'rxjs';
@Component({
selector: 'page-checkout-summary',
@@ -19,18 +19,26 @@ import { NEVER } from 'rxjs';
styleUrls: ['checkout-summary.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckoutSummaryComponent {
export class CheckoutSummaryComponent implements OnDestroy {
processId = Date.now();
displayOrders$ = this.domainCheckoutService.getOrders().pipe(
map((orders) =>
orders.map((order) => {
displayOrders$ = combineLatest([this.domainCheckoutService.getOrders(), this._route.params]).pipe(
map(([orders, params]) => {
let filteredOrders: DisplayOrderDTO[] = [];
if (params?.orderIds) {
const orderIds: String[] = params.orderIds.split(',');
filteredOrders = orders.filter((order) => orderIds.find((id) => Number(id) === order.id));
} else {
return filteredOrders;
}
return filteredOrders?.map((order) => {
return {
...order,
items: [...order.items]?.sort((a, b) => a.product?.name.localeCompare(b.product?.name)),
};
})
)
});
}),
shareReplay()
);
totalItemCount$ = this.displayOrders$.pipe(
@@ -102,6 +110,7 @@ export class CheckoutSummaryComponent {
private customerService: CrmCustomerService,
private domainCatalogService: DomainCatalogService,
private router: Router,
private _route: ActivatedRoute,
private omsService: DomainOmsService,
private uiModal: UiModalService,
private breadcrumb: BreadcrumbService,
@@ -118,13 +127,24 @@ export class CheckoutSummaryComponent {
this.breadcrumb.addBreadcrumbIfNotExists({
key: this.applicationService.activatedProcessId,
name: 'Bestellbestätigung',
path: `/kunde/${this.applicationService.activatedProcessId}/cart/summary`,
path: `/kunde/${this.applicationService.activatedProcessId}/cart/summary/${this._route.snapshot.params.orderIds}`,
tags: ['checkout', 'cart'],
section: 'customer',
});
});
}
async ngOnDestroy() {
const checkoutProcess = await this.applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout')
.pipe(first())
.toPromise();
if (!checkoutProcess) {
this.domainCheckoutService.removeAllOrders();
}
}
openPrintModal(id: number) {
this.uiModal.open({
content: PrintModalComponent,

View File

@@ -10,6 +10,7 @@ const routes: Routes = [
component: PageCheckoutComponent,
children: [
{ path: 'summary', component: CheckoutSummaryComponent },
{ path: 'summary/:orderIds', component: CheckoutSummaryComponent },
{ path: 'review', component: CheckoutReviewComponent },
{ path: '', pathMatch: 'full', redirectTo: 'review' },
],

View File

@@ -231,7 +231,6 @@ export abstract class CustomerCreateComponentBase {
this.control.reset(this.control.value);
if (invalidProperties) {
console.log('setValidationError', invalidProperties);
setInvalidPropertyErrors({ invalidProperties, formGroup });
}

View File

@@ -320,6 +320,8 @@ export class CustomerDetailsComponent implements OnInit {
name: isB2b ? (customer.organisation?.name ? customer.organisation?.name : customer.lastName) : customer.lastName,
});
const currentBuyer = await this.checkoutService.getBuyer({ processId: this.application.activatedProcessId }).pipe(first()).toPromise();
// Set Buyer For Process
this.checkoutService.setBuyer({
processId: this.application.activatedProcessId,
@@ -331,10 +333,12 @@ export class CustomerDetailsComponent implements OnInit {
customerFeatures: this.getCusomterFeatures(customer),
});
this.checkoutService.setNotificationChannels({
processId: this.application.activatedProcessId,
notificationChannels: customer.notificationChannels === (3 as NotificationChannel) ? 1 : customer.notificationChannels,
});
if (currentBuyer?.buyerNumber !== customer.customerNumber) {
this.checkoutService.setNotificationChannels({
processId: this.application.activatedProcessId,
notificationChannels: customer.notificationChannels === (3 as NotificationChannel) ? 1 : customer.notificationChannels,
});
}
// Set Invoice Address If Selected
const payer = await this.getSelectedPayerAddress();

View File

@@ -1,9 +1,9 @@
import { Component, Input, OnInit } from '@angular/core';
import { ProductImageService } from '@cdn/product-image';
import { HistoryComponent } from '@modal/history';
import { ImageService } from '@sales/core-services';
import { OrderDTO, OrderItemDTO, OrderItemSubsetDTO } from '@swagger/oms';
import { UiModalService } from '@ui/modal';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
@Component({
selector: 'page-customer-order-item-card',
@@ -41,13 +41,21 @@ export class CustomerOrderItemCardComponent implements OnInit {
}
}
constructor(private imageService: ImageService, private _modal: UiModalService) {}
constructor(private imageService: ProductImageService, private _modal: UiModalService) {}
ngOnInit() {
this.imageUrl = this.imageService.getImageUrl(this.orderItem.product.ean, {
width: 60,
height: 100,
});
// this.imageUrl = this.imageService.getImageUrl(this.orderItem.product.ean, {
// width: 60,
// height: 100,
// });
this.imageUrl = of(
this.imageService.getImageUrl({
imageId: this.orderItem.product.ean,
width: 60,
height: 100,
})
);
}
openHistory() {

View File

@@ -177,7 +177,7 @@ export class GoodsInCleanupListComponent implements OnInit, OnDestroy {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemId;
const orderItemId = orderItem.orderItemSubsetId;
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(

View File

@@ -54,7 +54,7 @@ export class GoodsInDetailsComponent extends ComponentStore<GoodsInDetailsCompon
);
selectedItem$ = combineLatest([this.selectedOrderItemId$, this.itemsWithProcessingStatus$]).pipe(
map(([orderItemId, items]) => items.find((item) => item.orderItemId === orderItemId) || items[0])
map(([orderItemId, items]) => items.find((item) => item.orderItemSubsetId === orderItemId) || items[0])
);
fetchingCoverItems$ = this.select((s) => s.fetchingCoverItems);
@@ -125,7 +125,7 @@ export class GoodsInDetailsComponent extends ComponentStore<GoodsInDetailsCompon
name: item?.orderNumber,
path: `/filiale/goods/in/details/customer/${encodeURIComponent(item.buyerNumber)}/order/${encodeURIComponent(
item?.orderNumber
)}/item/${item?.orderItemId}/${item?.processingStatus}`,
)}/item/${item?.orderItemSubsetId}/${item?.processingStatus}`,
section: 'branch',
tags: ['goods-in', 'details', item?.orderNumber],
});
@@ -151,7 +151,7 @@ export class GoodsInDetailsComponent extends ComponentStore<GoodsInDetailsCompon
orderId: res.result[0].orderId,
});
this.updateBreadcrumb(
res.result.find((item) => item.orderItemId === selectedOrderItemId && item.processingStatus === processingStatus)
res.result.find((item) => item.orderItemSubsetId === selectedOrderItemId && item.processingStatus === processingStatus)
);
},
() => {},
@@ -202,7 +202,7 @@ export class GoodsInDetailsComponent extends ComponentStore<GoodsInDetailsCompon
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(orderItem.buyerNumber)}/order/${encodeURIComponent(
orderItem?.orderNumber
)}/item/${orderItem?.orderItemId}/${orderItem?.processingStatus}/edit`,
)}/item/${orderItem?.orderItemSubsetId}/${orderItem?.processingStatus}/edit`,
]);
}
@@ -210,7 +210,7 @@ export class GoodsInDetailsComponent extends ComponentStore<GoodsInDetailsCompon
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(orderItem?.buyerNumber)}/order/${encodeURIComponent(
orderItem?.orderNumber
)}/item/${orderItem?.orderItemId}/${orderItem?.processingStatus}`,
)}/item/${orderItem?.orderItemSubsetId}/${orderItem?.processingStatus}`,
]);
}

View File

@@ -138,7 +138,7 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemId;
const orderItemId = orderItem.orderItemSubsetId;
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(

View File

@@ -135,7 +135,7 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemId;
const orderItemId = orderItem.orderItemSubsetId;
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(

View File

@@ -138,7 +138,7 @@ export class GoodsInReservationComponent implements OnInit, OnDestroy {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemId;
const orderItemId = orderItem.orderItemSubsetId;
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(

View File

@@ -74,7 +74,7 @@ export class GoodsInSearchFilterComponent implements OnInit, OnDestroy {
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(orderItem.buyerNumber)}/order/${encodeURIComponent(
orderItem.orderNumber
)}/item/${orderItem.orderItemId}/${orderItem.processingStatus}`,
)}/item/${orderItem.orderItemSubsetId}/${orderItem.processingStatus}`,
]);
} else {
this._router.navigate(['/filiale', 'goods', 'in', 'results'], {

View File

@@ -120,7 +120,7 @@ export class GoodsInSearchMainComponent implements OnInit, OnDestroy {
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(orderItem.buyerNumber)}/order/${encodeURIComponent(
orderItem.orderNumber
)}/item/${orderItem.orderItemId}/${orderItem.processingStatus}`,
)}/item/${orderItem.orderItemSubsetId}/${orderItem.processingStatus}`,
]);
} else {
this._router.navigate(['/filiale', 'goods', 'in', 'results'], {

View File

@@ -158,7 +158,7 @@ export class GoodsInSearchResultsComponent implements OnInit, OnDestroy {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemId;
const orderItemId = orderItem.orderItemSubsetId;
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(

View File

@@ -47,6 +47,12 @@ export class GoodsOutDetailsComponent extends ComponentStore<GoodsOutDetailsComp
shareReplay()
);
get processId() {
return +this._activatedRoute.snapshot.parent.data.processId;
}
processId$ = this._activatedRoute.parent.data.pipe(map((params) => +params.processId));
private _onDestroy$ = new Subject();
constructor(
@@ -83,7 +89,7 @@ export class GoodsOutDetailsComponent extends ComponentStore<GoodsOutDetailsComp
async updateBreadcrumb(item: OrderItemListItemDTO) {
if (item) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: 1000,
key: this.processId,
name: item?.compartmentCode || item?.orderNumber,
path: this.getDetailsPath(item),
section: 'customer',
@@ -93,7 +99,7 @@ export class GoodsOutDetailsComponent extends ComponentStore<GoodsOutDetailsComp
}
async removeBreadcrumbs() {
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(1000, ['goods-out', 'edit']).pipe(first()).toPromise();
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(this.processId, ['goods-out', 'edit']).pipe(first()).toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
@@ -101,7 +107,10 @@ export class GoodsOutDetailsComponent extends ComponentStore<GoodsOutDetailsComp
}
async removeDetailsCrumbs() {
const detailsCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(1000, ['goods-out', 'details']).pipe(first()).toPromise();
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this.processId, ['goods-out', 'details'])
.pipe(first())
.toPromise();
detailsCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
@@ -150,7 +159,7 @@ export class GoodsOutDetailsComponent extends ComponentStore<GoodsOutDetailsComp
}
navigateToLandingPage() {
this._router.navigate(['/kunde/goods/out']);
this._router.navigate([`/kunde/${this.processId}/goods/out`]);
}
async actionHandled(handler: { orderItemsContext: OrderItemsContext; command: string; navigation: 'details' | 'main' | 'reservation' }) {
@@ -165,13 +174,13 @@ export class GoodsOutDetailsComponent extends ComponentStore<GoodsOutDetailsComp
getDetailsPath(item: OrderItemListItemDTO) {
return item?.compartmentCode
? `/kunde/goods/out/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
? `/kunde/${this.processId}/goods/out/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/${this.processId}/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
}
getEditPath(item: OrderItemListItemDTO) {
return item?.compartmentCode
? `/kunde/goods/out/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}/edit`
: `/kunde/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}/edit`;
? `/kunde/${this.processId}/goods/out/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}/edit`
: `/kunde/${this.processId}/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}/edit`;
}
}

View File

@@ -1,7 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { DomainGoodsService } from '@domain/oms';
import { combineLatest, Observable } from 'rxjs';
import { map, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';
@@ -13,6 +12,12 @@ import { map, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsOutEditComponent implements OnInit {
get processId() {
return +this._activatedRoute.snapshot.parent.data.processId;
}
processId$ = this._activatedRoute.parent.data.pipe(map((params) => +params.processId));
orderNumber$: Observable<string> = this._activatedRoute.params.pipe(
map((params) => decodeURIComponent(params.orderNumber ?? '') || undefined)
);
@@ -38,8 +43,7 @@ export class GoodsOutEditComponent implements OnInit {
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService,
private _domainGoodsInService: DomainGoodsService,
private _router: Router,
private readonly _config: Config
private _router: Router
) {}
ngOnInit() {
@@ -51,11 +55,11 @@ export class GoodsOutEditComponent implements OnInit {
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
const processingStatus = this._activatedRoute.snapshot.params.processingStatus;
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsOut'),
key: this.processId,
name: 'Bearbeiten',
path: compartmentCode
? `/kunde/goods/out/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}/edit`
: `/kunde/goods/out/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}/edit`,
? `/kunde/${this.processId}/goods/out/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}/edit`
: `/kunde/${this.processId}/goods/out/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}/edit`,
section: 'customer',
tags: ['goods-out', 'edit', compartmentCode || orderNumber],
});
@@ -66,7 +70,9 @@ export class GoodsOutEditComponent implements OnInit {
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
compartmentCode
? this._router.navigate([`/kunde/goods/out/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`])
: this._router.navigate([`/kunde/goods/out/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`]);
? this._router.navigate([
`/kunde/${this.processId}/goods/out/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`,
])
: this._router.navigate([`/kunde/${this.processId}/goods/out/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`]);
}
}

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, Output, EventEmitter, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { Component, ChangeDetectionStrategy, Output, EventEmitter, ChangeDetectorRef, OnInit, OnDestroy, Input } from '@angular/core';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
@@ -24,6 +24,9 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
message: string;
@Input()
processId: number;
private _onDestroy$ = new Subject();
constructor(
@@ -74,7 +77,7 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
const orderItem = result.result[0];
this._router.navigate([this.getDetailsPath(orderItem)]);
} else {
this._router.navigate(['/kunde', 'goods', 'out', 'results'], {
this._router.navigate(['/kunde', this.processId, 'goods', 'out', 'results'], {
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
});
}
@@ -93,9 +96,9 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
async updateBreadcrumb() {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsOut'),
key: this.processId,
name: 'Warenausgabe',
path: '/kunde/goods/out',
path: `/kunde/${this.processId}/goods/out`,
tags: ['goods-out', 'main', 'filter'],
section: 'customer',
params: this._goodsOutSearchStore.filter?.getQueryParams(),
@@ -109,7 +112,7 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
getDetailsPath(item: OrderItemListItemDTO) {
return item?.compartmentCode
? `/kunde/goods/out/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
? `/kunde/${this.processId}/goods/out/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/${this.processId}/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
}
}

View File

@@ -7,6 +7,7 @@
<shell-filter-overlay #shellFilterOverlay>
<page-goods-out-search-filter
[processId]="processId$ | async"
*ngIf="showFilterOverlay"
(close)="toggleFilterOverlay(); shellFilterOverlay.close()"
></page-goods-out-search-filter>

View File

@@ -44,6 +44,8 @@ export class GoodsOutSearchComponent implements OnInit, OnDestroy {
map(([filter, initialFilter]) => !isEqual(filter?.getQueryParams(), initialFilter?.getQueryParams()))
);
processId$ = this._activatedRoute.data.pipe(map((data) => +data.processId));
constructor(
private _goodsOutSearchStore: GoodsOutSearchStore,
private _breadcrumb: BreadcrumbService,
@@ -61,12 +63,14 @@ export class GoodsOutSearchComponent implements OnInit, OnDestroy {
}
});
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsOut'),
name: 'Warenausgabe',
path: '/kunde/goods/out',
tags: ['goods-out', 'main', 'filter'],
section: 'customer',
this.processId$.pipe(takeUntil(this._onDestroy$)).subscribe((processId) => {
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: 'Warenausgabe',
path: `/kunde/${processId}/goods/out`,
tags: ['goods-out', 'main', 'filter'],
section: 'customer',
});
});
}

View File

@@ -1,10 +1,9 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { OrderItemListItemDTO } from '@swagger/oms';
import { Subscription } from 'rxjs';
import { first } from 'rxjs/operators';
import { distinctUntilChanged, first, map } from 'rxjs/operators';
import { GoodsOutSearchStore } from '../goods-out-search.store';
@Component({
@@ -22,13 +21,18 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
private _subscriptions = new Subscription();
get processId() {
return +this._activatedRoute.snapshot.data.processId;
}
processId$ = this._activatedRoute.data.pipe(map((data) => +data.processId));
constructor(
private _goodsOutSearchStore: GoodsOutSearchStore,
private _cdr: ChangeDetectorRef,
private _router: Router,
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService,
private readonly _config: Config
private _breadcrumb: BreadcrumbService
) {}
ngOnInit() {
@@ -38,29 +42,24 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
})
);
this._goodsOutSearchStore.setQueryParams(this._activatedRoute.snapshot.queryParams);
this.removeBreadcrumbs();
this._subscriptions.add(
this.processId$.pipe(distinctUntilChanged()).subscribe((processId) => {
this._goodsOutSearchStore.setQueryParams(this._activatedRoute.snapshot.queryParams);
// this.updateBreadcrumb(processId);
this.removeBreadcrumbs(processId);
})
);
}
ngOnDestroy() {
this._subscriptions.unsubscribe();
}
async removeBreadcrumbs() {
const resultCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsOut'), ['goods-out', 'results'])
.pipe(first())
.toPromise();
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsOut'), ['goods-out', 'details'])
.pipe(first())
.toPromise();
async removeBreadcrumbs(processId: number) {
const resultCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'results']).pipe(first()).toPromise();
const detailsCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'details']).pipe(first()).toPromise();
const editCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsOut'), ['goods-out', 'edit'])
.pipe(first())
.toPromise();
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'edit']).pipe(first()).toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
@@ -77,7 +76,7 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
async search() {
this._goodsOutSearchStore.clearResults();
await this.updateQueryParams();
await this.updateQueryParams(this.processId);
this.message = undefined;
this._goodsOutSearchStore.searchResult$.pipe(first()).subscribe((result) => {
@@ -86,9 +85,9 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
if (result.hits > 0) {
if (result.hits === 1) {
const orderItem = result.result[0];
this._router.navigate([this.getDetailsPath(orderItem)]);
this._router.navigate([this.getDetailsPath(orderItem, this.processId)]);
} else {
this._router.navigate(['/kunde', 'goods', 'out', 'results'], {
this._router.navigate(['/kunde', this.processId, 'goods', 'out', 'results'], {
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
});
}
@@ -103,25 +102,25 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
this._goodsOutSearchStore.search();
}
async updateBreadcrumb() {
async updateBreadcrumb(processId: number) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsOut'),
key: this.processId,
name: 'Warenausgabe',
path: '/kunde/goods/out',
path: `/kunde/${this.processId}/goods/out`,
tags: ['goods-out', 'main', 'filter'],
section: 'customer',
params: this._goodsOutSearchStore.filter?.getQueryParams(),
});
}
async updateQueryParams() {
async updateQueryParams(processId: number) {
await this._router.navigate([], { queryParams: this._goodsOutSearchStore.filter?.getQueryParams() });
this.updateBreadcrumb();
this.updateBreadcrumb(processId);
}
getDetailsPath(item: OrderItemListItemDTO) {
getDetailsPath(item: OrderItemListItemDTO, processId: number) {
return item?.compartmentCode
? `/kunde/goods/out/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
? `/kunde/${processId}/goods/out/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/${processId}/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
}
}

View File

@@ -67,6 +67,12 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
byCompartmentCodeFn = (item: OrderItemListItemDTO) => item.compartmentCode;
get processId() {
return +this._activatedRoute.snapshot.parent.data.processId;
}
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
private _onDestroy$ = new Subject();
constructor(
@@ -75,8 +81,7 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService,
private _commandService: CommandService,
private _modal: UiModalService,
private readonly _config: Config
private _modal: UiModalService
) {
super({
selectedOrderItemSubsetIds: [],
@@ -84,14 +89,16 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
}
ngOnInit() {
this._goodsOutSearchStore.setQueryParams(this._activatedRoute.snapshot.queryParams);
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((queryParams) => {
this.updateBreadcrumb(queryParams);
});
this.processId$.pipe(takeUntil(this._onDestroy$)).subscribe((processId) => {
this._goodsOutSearchStore.setQueryParams(this._activatedRoute.snapshot.queryParams);
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((queryParams) => {
this.updateBreadcrumb(queryParams);
});
this.initInitialSearch();
this.createBreadcrumb();
this.removeBreadcrumbs();
this.initInitialSearch(processId);
this.createBreadcrumb(processId);
this.removeBreadcrumbs(processId);
});
this._goodsOutSearchStore.searchResultCleared.pipe(takeUntil(this._onDestroy$)).subscribe((_) => this.clearSelectedItems());
}
@@ -103,16 +110,10 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
this.updateBreadcrumb(this._goodsOutSearchStore.filter?.getQueryParams());
}
async removeBreadcrumbs() {
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsOut'), ['goods-out', 'details'])
.pipe(first())
.toPromise();
async removeBreadcrumbs(processId) {
const detailsCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'details']).pipe(first()).toPromise();
const editCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsOut'), ['goods-out', 'edit'])
.pipe(first())
.toPromise();
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'edit']).pipe(first()).toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
@@ -123,11 +124,11 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
});
}
async createBreadcrumb() {
async createBreadcrumb(processId: number) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsOut'),
key: processId,
name: this.getBreadcrumbName(),
path: '/kunde/goods/out/results',
path: `/kunde/${processId}/goods/out/results`,
section: 'customer',
params: this._goodsOutSearchStore.filter?.getQueryParams(),
tags: ['goods-out', 'results', 'filter'],
@@ -140,7 +141,7 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
if (queryParams) {
const crumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsOut'), ['goods-out', 'results', 'filter'])
.getBreadcrumbsByKeyAndTags$(this.processId, ['goods-out', 'results', 'filter'])
.pipe(first())
.toPromise();
@@ -162,13 +163,15 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
return input?.replace('ORD:', '') ?? 'Alle';
}
initInitialSearch() {
initInitialSearch(processId: number) {
if (this._goodsOutSearchStore.hits === 0) {
this._goodsOutSearchStore.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.hits === 0) {
await this._router.navigate(['/kunde/goods/out'], { queryParams: this._goodsOutSearchStore.filter.getQueryParams() });
await this._router.navigate([`/kunde/${this.processId}/goods/out`], {
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
});
} else {
await this.createBreadcrumb();
await this.createBreadcrumb(processId);
if (result.hits === 1) {
await this.navigateToDetails(result.result[0]);
} else {
@@ -200,9 +203,11 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
const compartmentCode = orderItem.compartmentCode;
if (compartmentCode) {
this._router.navigate([`/kunde/goods/out/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`]);
this._router.navigate([
`/kunde/${this.processId}/goods/out/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`,
]);
} else {
this._router.navigate([`/kunde/goods/out/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`]);
this._router.navigate([`/kunde/${this.processId}/goods/out/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`]);
}
}

View File

@@ -1,3 +1,3 @@
<shell-breadcrumb [key]="goodsOutKey" [includesTags]="['goods-out']"></shell-breadcrumb>
<shell-breadcrumb [key]="processId$ | async" [includesTags]="['goods-out']"></shell-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -1,5 +1,6 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Config } from '@core/config';
import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators';
@Component({
selector: 'page-goods-out',
@@ -8,7 +9,7 @@ import { Config } from '@core/config';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsOutComponent {
goodsOutKey = this._config.get('process.ids.goodsOut');
processId$ = this._activatedRoute.data.pipe(map((data) => +data.processId));
constructor(private readonly _config: Config) {}
constructor(private _activatedRoute: ActivatedRoute) {}
}

View File

@@ -188,17 +188,24 @@ export class RemissionListItemComponent implements OnDestroy {
async returnImpediment() {
this.loading$.next(true);
let updatedDto: ReturnItemDTO | ReturnSuggestionDTO;
try {
// Pflichtremission
if (this.item.dtoType === 'return') {
await this._remissionService.returnImpediment(this.item.dto?.id).toPromise();
updatedDto = await this._remissionService.returnImpediment(this.item.dto?.id).toPromise();
}
// Abteilungsremission
else if (this.item.dtoType === 'suggestion') {
await this._remissionService.returnSuggestion(this.item.dto?.id).toPromise();
updatedDto = await this._remissionService.returnSuggestion(this.item.dto?.id).toPromise();
}
this._store.removeItem(this.item);
if (updatedDto.impediment?.attempts > 3) {
this._store.removeItem(this.item);
} else {
this._store.updateItemDto(updatedDto);
}
this._store.updateCache();
} catch (err) {
this._modal.open({

View File

@@ -4,7 +4,7 @@ import { CacheService } from '@core/cache';
import { Config } from '@core/config';
import { DomainRemissionService, RemissionListItem } from '@domain/remission';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { SupplierDTO } from '@swagger/remi';
import { ReturnItemDTO, ReturnSuggestionDTO, SupplierDTO } from '@swagger/remi';
import { UiFilter } from '@ui/filter';
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { debounceTime, filter, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
@@ -236,6 +236,7 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
}
this.setFilter(filter);
this._filterChange$.next(true);
},
(err) => {}
)
@@ -269,6 +270,38 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
const results = options?.newSearch
? { result: [...res.result], hits: res.hits ?? 0 }
: { result: [...(items ?? []), ...(res.result ?? [])], hits: res.hits ?? 0 };
// find items that are already in the list and remove the first duplicate
// why do we need it? when we use the action item not found we will add this item to the end of the list
// if we load more items this item appears again at the end of the list
// to keep the list clean we remove all duplicates and just keep the last one
const itemsToRemove: number[] = [];
results.result.forEach((item) => {
const duplicates = items.filter((i) => i.dto.id === item.dto.id);
if (duplicates.length > 1) {
// skip the first duplicate
// and add ids of the duplicates to the list of items to remove
itemsToRemove.push(...duplicates.slice(1).map((i) => i.dto.id));
}
});
itemsToRemove.forEach((id) => {
const index = results.result.findIndex((i) => i.dto.id === id);
if (index > -1) {
results.result.splice(index, 1);
}
});
// move all items with impediment to the end of the list
const itemsToMoveToTheEnd = results.result.filter((i) => i.dto.impediment);
itemsToMoveToTheEnd.forEach((item) => {
const index = results.result.findIndex((i) => i.dto.id === item.dto.id);
if (index > -1) {
results.result.splice(index, 1);
results.result.push(item);
}
});
this.setSearchResult(results);
this.setFetching(false);
this._searchCompleted.next(this.get());
@@ -347,6 +380,15 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
hits: state.hits - 1,
}));
updateItemDto = this.updater<ReturnItemDTO | ReturnSuggestionDTO>((state, itemDto) => {
const itemToUpdate = state.items.find((i) => i.dto?.id === itemDto.id);
return {
...state,
items: [...state.items.filter((i) => i.dto?.id !== itemDto?.id), { ...itemToUpdate, dto: itemDto }],
};
});
setRequiredCapacities = this.updater<any>((state, requiredCapacities) => ({
...state,
requiredCapacities,

View File

@@ -120,6 +120,9 @@ export class RemissionListComponent implements OnInit, OnDestroy {
ngOnInit() {
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$), debounceTime(0)).subscribe(async (queryParams) => {
const { supplier, source } = queryParams;
this._remissionListStore.loadFilter();
if (supplier) {
this._remissionListStore.setSelectedSupplierId(+supplier);
}

View File

@@ -1,26 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: ['./src/**/*.e2e-spec.ts'],
capabilities: {
browserName: 'chrome',
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () {},
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json'),
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
},
};

View File

@@ -1,14 +0,0 @@
import { AppPage } from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to sales!');
});
});

View File

@@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getTitleText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@@ -1,13 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@@ -1,11 +0,0 @@
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11

View File

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

View File

@@ -1,23 +0,0 @@
{
"index": "/index.html",
"assetGroups": [
{
"name": "sales",
"installMode": "prefetch",
"resources": {
"files": ["/favicon.ico", "/*.webmanifest", "/*.css", "/*.js"]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}

View File

@@ -1,22 +0,0 @@
import { Injectable } from '@angular/core';
import { SignalRHubOptions } from '@core/signalr';
import { AuthConfig } from 'angular-oauth2-oidc';
@Injectable()
export class AppConfiguration {
includeGoogleAnalytics = false;
title?: string;
cdn: { [key: string]: string };
sso: AuthConfig;
hubs: { [key: string]: SignalRHubOptions };
swagger: { [key: string]: { rootUrl: string } };
remissionModuleOptions: {
useMock: false;
endpoints: { [key: string]: string };
};
}

View File

@@ -1,94 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes, PreloadAllModules } from '@angular/router';
import { LogInComponent } from './components/log-in/log-in.component';
import { DashboardComponent } from './modules/dashboard/pages/dashboard.component';
import { AuthenticationGuard } from './resolvers/authentication.guard';
import { ProcessIdResolver } from './resolvers/process-id.resolver';
import { BranchSectionResolver, CustomerSectionResolver } from './resolvers/section.resolver';
const routes: Routes = [
{
path: 'login',
resolve: { section: CustomerSectionResolver },
children: [
{ path: '', component: LogInComponent, pathMatch: 'full' },
{ path: ':token', component: LogInComponent },
],
},
{ path: 'dashboard', resolve: { section: CustomerSectionResolver }, component: DashboardComponent, canActivate: [AuthenticationGuard] },
{
path: 'product',
resolve: { processId: ProcessIdResolver, section: CustomerSectionResolver },
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [AuthenticationGuard],
},
{
path: 'customer',
resolve: { processId: ProcessIdResolver, section: CustomerSectionResolver },
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),
canActivate: [AuthenticationGuard],
},
{
path: 'cart',
resolve: { processId: ProcessIdResolver, section: CustomerSectionResolver },
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [AuthenticationGuard],
},
{
path: 'branch',
resolve: { section: BranchSectionResolver },
loadChildren: () => import('./modules/branch/branch.module').then((m) => m.BranchModule),
canActivate: [AuthenticationGuard],
},
{
path: 'debug',
loadChildren: () => import('./modules/debug/debug.module').then((m) => m.DebugModule),
canActivate: [AuthenticationGuard],
},
{
path: 'goods',
children: [
{
path: 'in',
resolve: { section: BranchSectionResolver },
loadChildren: () => import('@page/goods-in').then((m) => m.GoodsInModule),
},
{
path: 'out',
resolve: { section: CustomerSectionResolver },
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
},
],
canActivate: [AuthenticationGuard],
},
{
path: 'remission',
resolve: { section: BranchSectionResolver },
loadChildren: () => import('./modules/remission/remission-client.module').then((m) => m.RemissionClientModule),
canActivate: [AuthenticationGuard],
},
{
path: 'task-calendar',
resolve: { section: BranchSectionResolver },
loadChildren: () => import('@page/task-calendar').then((m) => m.PageTaskCalendarModule),
canActivate: [AuthenticationGuard],
},
{
path: 'shelf',
loadChildren: () => import('./modules/shelf/shelf.module').then((m) => m.ShelfModule),
canActivate: [AuthenticationGuard],
},
{ path: '**', redirectTo: 'dashboard' },
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
scrollPositionRestoration: 'enabled',
relativeLinkResolution: 'legacy',
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@@ -1,40 +0,0 @@
import { NgModule } from '@angular/core';
import { StoreModule, MetaReducer, ActionReducer, INIT } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { storeFreeze } from 'ngrx-store-freeze';
import { environment } from '../environments/environment';
import { RootState } from './store/root.state';
import { rootReducer } from './store/root.reducer';
import { EffectsModule } from '@ngrx/effects';
import { SearchEffects } from './store/customer';
import { HistoryEffects } from '@shelf-store/history';
import { DetailsEffects } from '@shelf-store/details';
import packageInfo from 'package';
// TODO: In Service Speichern
export function storeInLocalStorage(reducer: ActionReducer<any>): ActionReducer<any> {
const lsKey = 'ISA_NGRX_STATE_1';
return function (state, action) {
if (action.type === INIT) {
const storedState = JSON.parse(localStorage.getItem(lsKey));
if (storedState?.version === packageInfo.version) {
return reducer(storedState, action);
}
}
const nextState = reducer(state, action);
localStorage.setItem(lsKey, JSON.stringify({ ...nextState, version: packageInfo.version }));
return nextState;
};
}
export const metaReducers: MetaReducer<RootState>[] = !environment.production ? [storeFreeze, storeInLocalStorage] : [storeInLocalStorage];
@NgModule({
imports: [
StoreModule.forRoot(rootReducer, { metaReducers }),
EffectsModule.forRoot([SearchEffects, HistoryEffects, DetailsEffects]),
StoreDevtoolsModule.instrument({ name: 'ISA Ngrx Store' }),
],
})
export class AppStoreModule {}

View File

@@ -1,64 +0,0 @@
import { NgModule } from '@angular/core';
import { PrintConfiguration } from '@swagger/print';
import { OmsConfiguration } from '@swagger/oms';
import { IsaConfiguration } from '@swagger/isa';
import { CrmConfiguration } from '@swagger/crm';
import { CheckoutConfiguration } from '@swagger/checkout';
import { AvConfiguration } from '@swagger/availability';
import { CatConfiguration } from '@swagger/cat';
import { EisConfiguration } from '@swagger/eis';
import { RemiConfiguration } from '@swagger/remi';
import { AppConfiguration } from './app-configuration';
export function catConfigurationFactory(config: AppConfiguration) {
return config.swagger.api;
}
export function avConfigurationFactory(config: AppConfiguration) {
return config.swagger.av;
}
export function checkoutConfigurationFactory(config: AppConfiguration) {
return config.swagger.checkout;
}
export function crmConfigurationFactory(config: AppConfiguration) {
return config.swagger.crm;
}
export function isaConfigurationFactory(config: AppConfiguration) {
return config.swagger.isa;
}
export function omsConfigurationFactory(config: AppConfiguration) {
return config.swagger.oms;
}
export function printConfigurationFactory(config: AppConfiguration) {
return config.swagger.print;
}
export function eisConfigurationFactory(config: AppConfiguration) {
return config.swagger.eis;
}
export function remiConfigurationFactory(config: AppConfiguration) {
return config.swagger.remi;
}
@NgModule({
providers: [
{ provide: CatConfiguration, useFactory: catConfigurationFactory, deps: [AppConfiguration] },
{ provide: AvConfiguration, useFactory: avConfigurationFactory, deps: [AppConfiguration] },
{ provide: CheckoutConfiguration, useFactory: checkoutConfigurationFactory, deps: [AppConfiguration] },
{ provide: CrmConfiguration, useFactory: crmConfigurationFactory, deps: [AppConfiguration] },
{ provide: IsaConfiguration, useFactory: isaConfigurationFactory, deps: [AppConfiguration] },
{ provide: OmsConfiguration, useFactory: omsConfigurationFactory, deps: [AppConfiguration] },
{ provide: PrintConfiguration, useFactory: printConfigurationFactory, deps: [AppConfiguration] },
{ provide: EisConfiguration, useFactory: eisConfigurationFactory, deps: [AppConfiguration] },
{ provide: RemiConfiguration, useFactory: remiConfigurationFactory, deps: [AppConfiguration] },
],
})
export class AppSwaggerModule {}

View File

@@ -1,7 +0,0 @@
<lib-offline-overlay>
<!-- lib offline depends on these three elements to be present inside of it -->
<app-header [ngClass]="{ loading: loading$ | async }" *ngIf="authenticated"></app-header>
<app-content [ngClass]="{ loading: loading$ | async }"></app-content>
<app-menu [ngClass]="{ loading: loading$ | async }" *ngIf="authenticated"></app-menu>
<img *ngIf="loading$ | async" src="/assets/images/Icon_Loading.svg" class="app-loader" />
</lib-offline-overlay>

View File

@@ -1,36 +0,0 @@
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
.app-loader {
position: absolute;
top: 50%;
left: 50%;
margin: -20px 0 0 -20px;
-webkit-animation: spin 1.5s linear infinite;
-moz-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite;
}
.loading {
opacity: 0;
}
@-moz-keyframes spin {
100% {
-moz-transform: rotate(360deg);
}
}
@-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

View File

@@ -1,14 +0,0 @@
import { TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent],
}).compileComponents();
})
);
});

View File

@@ -1,132 +0,0 @@
import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { Store } from '@ngxs/store';
import { BehaviorSubject, Subject } from 'rxjs';
import { take, filter, takeUntil } from 'rxjs/operators';
import { SsoService } from 'sso';
import { AppService } from './core/services/app.service';
import { AppState } from './core/store/state/app.state';
import { AppConfiguration } from './app-configuration';
import { NativeContainerService } from 'shared/lib/barcode-scanner';
import { DOCUMENT } from '@angular/common';
import { ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { Actions as NgxsActions, ofActionDispatched } from '@ngxs/store';
import { DeleteProcess } from './core/store/actions/process.actions';
import { DeviceDetectorService } from 'ngx-device-detector';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy {
title = 'Hugendubel InstoreApp';
loading$ = new BehaviorSubject(true);
includeGoogleAnalytics = this.config.includeGoogleAnalytics;
destroy$ = new Subject();
get authenticated() {
return this.ssoService.isAuthenticated();
}
constructor(
private config: AppConfiguration,
private ssoService: SsoService,
private appService: AppService,
private router: Router,
private store: Store,
private nativeContainer: NativeContainerService,
@Inject(DOCUMENT) private document: Document,
private renderer: Renderer2,
private applicationService: ApplicationService,
private domainCheckoutService: DomainCheckoutService,
private ngxsActions$: NgxsActions,
private deviceDetectorService: DeviceDetectorService
) {
this.appService.loader$.subscribe(() => {
this.loading$.next(false);
});
this.appService.loader$.pipe(take(1)).subscribe(() => {
this.containerNotificationMessage();
});
// intialisations done only when app loads
this.appService.appLoadInitialisations();
// this.router.events.subscribe((event) => {
// if (event instanceof NavigationStart) {
// try {
// throw new Error(event.toString());
// } catch (error) {
// console.error(error);
// }
// }
// });
this.router.events.pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd)).subscribe(() => {
if (!this.router.url.includes('login')) {
// send router navigation events
this.appService.registerNavigationEvents();
}
});
this.applicationService.section$.pipe(takeUntil(this.destroy$)).subscribe((section) => {
document.body.classList.remove('branch', 'customer');
document.body.classList.add(section);
});
this.store
.select(AppState.getCurrentProcessId)
.pipe(takeUntil(this.destroy$))
.subscribe((processId) => this.applicationService.setActivatedProcessId(processId));
this.ngxsActions$.pipe(ofActionDispatched(DeleteProcess)).subscribe((action: DeleteProcess) => {
this.domainCheckoutService.removeProcess({ processId: action.payload.id });
this.applicationService.removeProcess(action.payload.id);
});
}
ngOnInit() {
if (this.includeGoogleAnalytics) {
this.initGoogleAnalytics();
}
this.renderer.addClass(this.document.body, this.deviceDetectorService.deviceType);
}
ngOnDestroy() {
this.destroy$.next();
}
private containerNotificationMessage() {
// Notify the container app if the user has initialized app loggeed in or not
// For the purpposes of removing the container Header element
if (this.nativeContainer.isUiWebview()) {
this.nativeContainer.sendMessage({ userAuthenticated: this.ssoService.isAuthenticated() });
}
}
private initGoogleAnalytics() {
const htmlScript: HTMLScriptElement = this.renderer.createElement('script');
htmlScript.text = `
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
(i[r] =
i[r] ||
function () {
(i[r].q = i[r].q || []).push(arguments);
}),
(i[r].l = 1 * new Date());
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-76423009-5', 'auto');
`;
this.renderer.appendChild(this.document.body, htmlScript);
}
}

View File

@@ -1,218 +0,0 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule, ErrorHandler, LOCALE_ID, APP_INITIALIZER } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ComponentsModule } from './modules/components.module';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgxsModule } from '@ngxs/store';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';
import { ProcessState } from './core/store/state/process.state';
import { BreadcrumbsState } from './core/store/state/breadcrumbs.state';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SharedModule } from './shared/shared.module';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { NotifierState } from './core/store/state/notifier.state';
import { environment } from '../environments/environment';
import { ModalModule, IconModule, OfflineOverlayModule } from '@libs/ui';
import { ProductState } from './core/store/state/product.state';
import { AppState } from './core/store/state/app.state';
import { BranchState } from './core/store/state/branches.state';
import { SsoModule, SsoInterface, SsoService } from 'sso';
import { SsoAuthorizationInterceptor, HttpErrorHandlerInterceptor } from './core/interceptors';
import { DatePipe } from '@angular/common';
import { HimaSalesErrorHandler } from './core/error/hima-sales.error-handler';
import { CollectingShelfState } from './core/store/state/collecting-shelf.state';
import 'apps/sales/src/app/core/utils/app.prototypes';
import { VatState } from './core/store/state/vat.state';
import { SupplierState } from './core/store/state/supplier.state';
import { GoodsInState } from './core/store/state/goods-in.state';
import { BranchProcessState } from './core/store/state/branch-process.state';
import { RemissionModule, RemissionModuleOptions } from '@isa/remission';
import { RemissionState } from './core/store/state/remission.state';
import { NgIdleKeepaliveModule } from '@ng-idle/keepalive';
import { AppSwaggerModule } from './app-swagger.module';
import { AppConfiguration } from './app-configuration';
import { FormsState } from './core/store/state/forms.state';
import localeDe from '@angular/common/locales/de';
import localeDeExtra from '@angular/common/locales/extra/de';
import { registerLocaleData } from '@angular/common';
import { RemissionStateHandler } from './core/store/handlers/remission.handlers';
import { SsoConfigurationService } from './core/services/sso-configuration.service';
import { OverlayModule } from '@angular/cdk/overlay';
import { ModalDialogueModule } from './core/overlay/component';
import { OverlaysModule } from './core/overlay/overlays.module';
import { CoreBreadcrumbModule } from '@core/breadcrumb';
import { ApplicationService, CoreApplicationModule, ProcessService } from '@core/application';
import { UiModalModule } from '@ui/modal';
import { ProcessRefactImp } from './refact-imp/process.refact-imp';
import { DomainCheckoutModule } from '@domain/checkout';
import { CDN_PRODUCT_PICTURES } from './tokens';
import { DateAdapter } from '@ui/common';
import { ApplicationRefactImp } from './refact-imp/application.refact-imp';
import { CDN_PRODUCT_IMAGE } from 'apps/cdn/product-image/src/lib/tokens';
import { DomainCatalogModule } from '@domain/catalog';
import { AppStoreModule } from './app-store.module';
import { DomainOmsModule } from '@domain/oms';
import { DomainAvailabilityModule } from '@domain/availability';
import { CoreCommandModule } from '@core/command';
import { NotificationsHubModule, NOTIFICATIONS_HUB_OPTIONS } from '@hub/notifications';
import { SignalRHubOptions } from '@core/signalr';
import { UiIconModule } from '@ui/icon';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
const states = [
AppState,
ProcessState,
BreadcrumbsState,
NotifierState,
ProductState,
BranchState,
CollectingShelfState,
VatState,
SupplierState,
BranchProcessState,
GoodsInState,
RemissionState,
FormsState,
];
export function noop() {
return function () {};
}
export function remissionModuleOptionsFactory(config: AppConfiguration): RemissionModuleOptions {
return config.remissionModuleOptions;
}
export function cdnProdutctPictures(config: AppConfiguration): string {
return config.cdn.productPictures;
}
export function _notificationsHubOptionsFactory(config: AppConfiguration, sso: SsoService): SignalRHubOptions {
const options = { ...config.hubs.notifications };
options.httpOptions.accessTokenFactory = () => sso.getToken();
return options;
}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
AppSwaggerModule,
AppStoreModule,
ComponentsModule,
HttpClientModule,
NgxsModule.forRoot(states, { developmentMode: !environment.production }),
NgxsReduxDevtoolsPluginModule.forRoot({ name: 'ISA NGXS Store' }),
NgxsLoggerPluginModule,
FormsModule,
ReactiveFormsModule,
SharedModule,
ScrollingModule,
IconModule,
ModalModule.forRoot(),
SsoModule.forRoot(environment.production),
OfflineOverlayModule,
NgIdleKeepaliveModule.forRoot(),
RemissionModule.forRoot(undefined),
OverlayModule,
OverlaysModule,
ModalDialogueModule,
/**
* @core Modules
*/
CoreBreadcrumbModule.forRoot(),
CoreApplicationModule.forRoot(),
CoreCommandModule.forRoot([]),
/**
* @domain Modules
*/
DomainAvailabilityModule.forRoot(),
DomainCheckoutModule.forRoot(),
DomainCatalogModule.forRoot(),
DomainOmsModule.forRoot(),
/**
* @ui Modules
*/
UiModalModule.forRoot(),
UiIconModule,
/**
* @hub Modules
*/
NotificationsHubModule.forRoot(),
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: noop,
deps: [RemissionStateHandler],
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: SsoAuthorizationInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorHandlerInterceptor,
multi: true,
},
{
provide: RemissionModuleOptions,
useFactory: remissionModuleOptionsFactory,
deps: [AppConfiguration],
},
DatePipe,
{
provide: ErrorHandler,
useClass: HimaSalesErrorHandler,
},
{ provide: LOCALE_ID, useValue: 'de-DE' },
{
provide: SsoInterface,
useClass: SsoConfigurationService,
},
DateAdapter,
{
provide: ProcessService,
useClass: ProcessRefactImp,
},
{
provide: ApplicationService,
useClass: ApplicationRefactImp,
},
{
provide: CDN_PRODUCT_PICTURES,
useFactory: cdnProdutctPictures,
deps: [AppConfiguration],
},
{
provide: CDN_PRODUCT_IMAGE,
useFactory: cdnProdutctPictures,
deps: [AppConfiguration],
},
{
provide: NOTIFICATIONS_HUB_OPTIONS,
useFactory: _notificationsHubOptionsFactory,
deps: [AppConfiguration, SsoService],
},
],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@@ -1,40 +0,0 @@
<div class="breadacrumb-grid" [ngClass]="{ 'grid-with-arrow': !showBack, 'breadcumb-mb-5': lowerMargin }" *ngIf="breadcrumbs">
<app-back-arrow *ngIf="showBack" (back)="goBack(breadcrumbs[breadcrumbs.length - 2])" class="align-right back-arrow"></app-back-arrow>
<div
class="layer-fade-start"
*ngIf="showBackNavigationArrow"
(mouseover)="showBackNavArrow()"
(mouseleave)="hideBackNavArrow($event)"
></div>
<div class="icon-start" *ngIf="showBackNavigationArrow && showBackArrow" (click)="navigateBack()" (mouseleave)="hideBackNavArrowIcon()">
<lib-icon class="icon-start" width="40px" name="tab_Arrow_3" type="png"></lib-icon>
</div>
<div class="breadcrumb-start-layer-fade" *ngIf="!showBackNavigationArrow"></div>
<div class="align-center breadcrumb-container" #container>
<span
*ngFor="let breadcrumb of breadcrumbs; let i = index; let last = last"
class="breadcrumb show"
(click)="selectBreadcrumb(breadcrumb)"
[ngClass]="{ selected: last, branch: module === 1 }"
>
<lib-icon *ngIf="breadcrumbs.indexOf(breadcrumb) > 0 && i != 0 && i !== last" class="next" name="Arrow_Next" alt="next"></lib-icon>
<span>{{ breadcrumb ? breadcrumb.name : '' }}</span>
</span>
<span class="breadcrumb last" *ngIf="showBack"></span>
</div>
<div class="breadcrumb-end-layer-fade" *ngIf="!showForwardNavigationalArrow"></div>
<div
class="layer-fade-end"
*ngIf="showForwardNavigationalArrow"
(mouseover)="showForwardNavArrow()"
(mouseleave)="hideForwardNavArrow($event)"
></div>
<div
class="icon-end"
*ngIf="showForwardNavigationalArrow && showForrwardArrow"
(click)="navigateForward()"
(mouseleave)="hideForwardNavArrowIcon()"
>
<lib-icon class="icon-end" width="40px" name="tab_Arrow_2" type="png"></lib-icon>
</div>
</div>

View File

@@ -1,232 +0,0 @@
@import 'variables';
.breadacrumb-grid {
display: grid;
grid-template-columns: min-content auto;
grid-gap: 15px;
height: 40px;
position: relative;
}
.breadcumb-mb-5 {
margin-bottom: 5px;
}
.grid-with-arrow {
grid-template-columns: auto;
}
.breadcrumb-container {
// display: flex;
align-items: center;
// justify-content: center;
white-space: nowrap;
overflow-y: hidden;
scroll-behavior: smooth;
overflow-x: scroll;
height: 45px;
padding-top: 10px;
// width: calc(100% - 100px);
}
:only-child {
.breadcrumb-end-layer-fade {
position: absolute;
min-height: 45px;
min-width: 5px;
z-index: 20;
opacity: 0.7;
background-color: #e6eff9;
box-shadow: -8px 0px 14px 3px #e6eff9;
border-radius: 5px;
}
}
.breadcrumb-start-layer-fade {
position: absolute;
min-height: 45px;
min-width: 5px;
z-index: 20;
opacity: 0.7;
background-color: #e6eff9;
box-shadow: 8px 0px 14px 3px #e6eff9;
border-radius: 5px;
}
.breadcrumb {
outline: none;
font-size: 16px;
color: #1f466c;
line-height: 21px;
padding: 10px 0;
padding-bottom: 0px;
cursor: pointer;
&.branch {
color: #596470;
}
}
.hide {
display: none;
opacity: 0;
&:nth-last-child(4) {
/*declarations*/
display: block;
overflow: hidden;
opacity: 0;
flex: 0.0001;
animation: fadeSlide 400ms;
}
&:nth-last-child(-n + 3) {
/*declarations*/
opacity: 1;
display: block;
}
}
.last {
padding: 10px 50px;
}
.back-arrow-container {
width: 118px;
height: 50px;
display: flex;
justify-content: center;
align-items: flex-start;
position: absolute;
z-index: 50;
transform: rotate(180deg);
cursor: pointer;
}
.foward-arrow-container {
width: 118px;
height: 50px;
display: flex;
justify-content: center;
align-items: flex-start;
position: absolute;
z-index: 50;
cursor: pointer;
}
@keyframes fadeSlide {
0% {
opacity: 1;
transform: translateX(80px);
flex: 0.5;
overflow: hidden;
}
50% {
opacity: 0;
}
100% {
transform: translateX(0);
flex: 0.0001;
}
}
.back-arrow {
padding: 10px 0;
}
.next {
padding: 0 7px;
}
.selected {
font-weight: bold;
}
.icon-start {
position: absolute;
left: 50px;
margin-top: 1px;
z-index: 30;
opacity: 1;
cursor: pointer;
}
.layer-fade-start {
position: absolute;
min-height: 40px;
min-width: 35px;
z-index: 20;
left: 100px;
opacity: 0.4;
background-color: white;
box-shadow: 3px 0 14px 8px white;
border-radius: 5px;
}
.icon-end {
position: absolute;
right: 5px;
margin-top: 1px;
z-index: 30;
opacity: 1;
cursor: pointer;
}
.layer-fade-end {
position: absolute;
min-height: 40px;
min-width: 35px;
z-index: 20;
right: 15px;
opacity: 0.4;
background-color: white;
box-shadow: 3px 0 14px 8px white;
border-radius: 5px;
}
/*
##Device = Big Desktops
*/
@media (min-width: 1281px) {
.breadcrumb-end-layer-fade {
right: 0px;
}
.breadcrumb-start-layer-fade {
left: 100px;
}
}
/*
##Device = Laptops, Desktops, Ipad pro
*/
@media (min-width: 1025px) and (max-width: 1280px) {
.breadcrumb-end-layer-fade {
right: 0px;
}
.breadcrumb-start-layer-fade {
left: 100px;
}
}
/*
##Device = Tablets, Ipads
*/
@media (min-width: 768px) and (max-width: 1024px) {
.breadcrumb-end-layer-fade {
right: 15px;
}
.breadcrumb-start-layer-fade {
left: 112px;
}
}
/*
##Device = Low Resolution Tablets, Mobiles (Landscape)
*/
@media (min-width: 481px) and (max-width: 767px) {
.breadcrumb-end-layer-fade {
right: 15px;
}
}

View File

@@ -1,13 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { BreadcrumbsComponent } from './breadcrumbs.component';
describe('BreadcrumbsComponent', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [BreadcrumbsComponent],
}).compileComponents();
})
);
});

View File

@@ -1,286 +0,0 @@
import {
Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ChangeDetectionStrategy,
ViewChild,
ElementRef,
AfterViewInit,
} from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { Breadcrumb } from '../../core/models/breadcrumb.model';
import { Process } from '../../core/models/process.model';
import { Store, Select } from '@ngxs/store';
import { ChangeCurrentRoute, ResetProcessProductFilters } from '../../core/store/actions/process.actions';
import { Router } from '@angular/router';
import { takeUntil, switchMap, distinctUntilChanged } from 'rxjs/operators';
import { SharedSelectors } from '../../core/store/selectors/shared.selectors';
import { ResetFilters } from '../../core/store/actions/filter.actions';
import { AppService } from '../../core/services/app.service';
import { ModuleSwitcher } from '../../core/models/app-switcher.enum';
import { BranchProcess } from '../../core/models/branch-process.model';
import { SetBranchProcessCurrentPath } from '../../core/store/actions/branch-process.actions';
import { AppState } from '../../core/store/state/app.state';
import { FILIALE_LANDING_PAGE } from '../../core/utils/app.constants';
@Component({
selector: 'app-breadcrumbs',
templateUrl: './breadcrumbs.component.html',
styleUrls: ['./breadcrumbs.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BreadcrumbsComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild('container', { read: ElementRef })
public container: ElementRef<any>;
@Select(SharedSelectors.getBreadcrumbs) breadcrumbs$: Observable<Breadcrumb[]>;
@Select(SharedSelectors.getCurrentProcess) currentProcess$: Observable<Process>;
module: ModuleSwitcher;
destroy$ = new Subject();
breadcrumbs: Breadcrumb[] = [];
currentRoute = '';
start: number;
end: number;
breadsCount: number;
showBack = true;
showBackNavigationArrow = false;
showBackArrow = false;
showForwardNavigationalArrow = false;
showForrwardArrow = false;
isIPad = false;
get firstVisibleItem() {
if (this.start) {
return this.start;
}
return 0;
}
get lastVisibleItem() {
if (this.end) {
return this.end;
}
return this.breadcrumbs ? this.breadcrumbs.length - 1 : 0;
}
get backArrow() {
return this.router.url.substring(0, 16) === '/product/details' && this.breadsCount > 1;
}
get selectedBreadCrumbIndex() {
if (!this.currentRoute) {
return 0;
}
let route = this.currentRoute;
const hasParams = this.currentRoute.includes('?');
if (hasParams) {
const endOfRouteWithOutParams = this.currentRoute.indexOf('?');
route = this.currentRoute.substring(0, endOfRouteWithOutParams);
}
return this.breadcrumbs.findIndex((t) => t && t.path.indexOf(route) >= 0);
}
get lowerMargin() {
if (this.router.url === '/customer/results') {
return true;
}
if (this.router.url.substring(0, 14) === '/customer/edit') {
return true;
}
if (this.router.url.substring(0, 16) === '/product/details') {
return true;
}
return false;
}
constructor(private store: Store, private router: Router, private cdrf: ChangeDetectorRef, private appService: AppService) {}
getBreadcrumbsFromCurentProcess() {
this.currentProcess$
.pipe(
switchMap((process: Process | BranchProcess) => {
if (process) {
this.currentRoute = `${process.currentRoute}`;
this.cdrf.detectChanges();
}
setTimeout(() => {
this.container.nativeElement.scrollTo({
left: this.container.nativeElement.scrollWidth,
behavior: 'smooth',
});
}, 400);
return this.breadcrumbs$;
}),
takeUntil(this.destroy$),
distinctUntilChanged()
)
.subscribe((breadcrumbs: Breadcrumb[]) => {
this.showForwardNavigationalArrow = false;
this.showBackNavigationArrow = false;
if (breadcrumbs) {
const breadName =
breadcrumbs.length > 0 ? (breadcrumbs[breadcrumbs.length - 1] ? breadcrumbs[breadcrumbs.length - 1].name : '') : '';
this.showBack = !(breadName === 'Bestellbestätigung' || breadName === 'Remission');
this.breadcrumbs = breadcrumbs;
this.breadsCount = Object.keys(this.breadcrumbs) ? Object.keys(this.breadcrumbs).length : 0;
this.cdrf.detectChanges();
if (!this.isIPad) {
setTimeout(() => {
try {
this.initNavigationalArrow();
} catch (error) {}
}, 1000);
}
}
});
}
selectBreadcrumb(breadcrumb: Breadcrumb) {
if (breadcrumb && breadcrumb.path) {
if (breadcrumb.path === '/product/search') {
this.store.dispatch(new ResetFilters());
this.store.dispatch(new ResetProcessProductFilters());
}
if (this.module === ModuleSwitcher.Customer) {
this.store.dispatch(new ChangeCurrentRoute(breadcrumb.path, true));
} else {
this.store.dispatch(new SetBranchProcessCurrentPath(breadcrumb.path, true));
}
this.router.navigate([breadcrumb.path], {
queryParams: breadcrumb.queryParams ? breadcrumb.queryParams : {},
});
}
}
goBack(breadcrumb: Breadcrumb) {
if (breadcrumb && breadcrumb.path) {
if (this.module === ModuleSwitcher.Customer) {
this.store.dispatch(new ChangeCurrentRoute(breadcrumb.path, false));
this.router.navigate(['/product/results']);
} else {
this.store.dispatch(new SetBranchProcessCurrentPath(breadcrumb.path, false));
this.router.navigate([FILIALE_LANDING_PAGE]);
}
}
}
ngOnInit() {
this.isIPad = this.appService.isIPadEnv();
this.getBreadcrumbsFromCurentProcess();
this.store
.select(AppState.activeModule)
.pipe(takeUntil(this.destroy$))
.subscribe((v: ModuleSwitcher) => {
this.module = v;
this.cdrf.detectChanges();
});
}
ngAfterViewInit() {
if (!this.isIPad) {
setTimeout(() => {
try {
this.initNavigationalArrow();
} catch (error) {}
}, 100);
}
}
ngOnDestroy(): void {
this.destroy$.next();
}
addOne() {
this.breadcrumbs.push({
name: 'test' + (this.breadcrumbs ? this.breadcrumbs.length : 0),
path: './i',
});
}
initNavigationalArrow() {
if (this.container) {
const containerWidth = this.container.nativeElement.offsetWidth;
let childrenWidth = 60;
let elementProcessed = 0;
if (this.container.nativeElement.children) {
const breadcrumbs = this.container.nativeElement.children.length - 1;
for (let i = 0; i < breadcrumbs; i++) {
elementProcessed++;
childrenWidth += this.container.nativeElement.children[i].offsetWidth;
}
if (breadcrumbs === elementProcessed && containerWidth <= childrenWidth) {
this.showBackNavigationArrow = true;
this.cdrf.detectChanges();
} else {
this.showBackNavigationArrow = false;
this.showForwardNavigationalArrow = false;
}
}
}
}
showBackNavArrow() {
this.showBackArrow = true;
}
hideBackNavArrow(event) {
if (event) {
const e = event.toElement || event.relatedTarget;
if (e && e.classList && (e.classList[0] === 'icon' || e.classList[0] === 'ng-star-inserted')) {
return;
}
this.showBackArrow = false;
}
}
hideBackNavArrowIcon() {
this.showBackArrow = false;
}
showForwardNavArrow() {
this.showForrwardArrow = true;
}
hideForwardNavArrow(event) {
if (event) {
const e = event.toElement || event.relatedTarget;
if (e && e.classList && (e.classList[0] === 'icon' || e.classList[0] === 'ng-star-inserted')) {
return;
}
this.showForrwardArrow = false;
}
}
hideForwardNavArrowIcon() {
this.showForrwardArrow = false;
}
navigateBack() {
const scrollLeft = this.container.nativeElement.scrollLeft;
if (scrollLeft <= 121) {
this.showBackNavigationArrow = false;
}
this.container.nativeElement.scrollTo({
left: scrollLeft - 120,
behavior: 'smooth',
});
this.showForwardNavigationalArrow = true;
this.cdrf.detectChanges();
}
navigateForward() {
const scrollLeft = this.container.nativeElement.scrollLeft;
const containerWidth = this.container.nativeElement.offsetWidth;
const scrollWidth = this.container.nativeElement.scrollWidth;
if (scrollLeft + 119 + containerWidth <= scrollWidth) {
this.showForwardNavigationalArrow = false;
}
this.container.nativeElement.scrollTo({
left: scrollLeft + 120,
behavior: 'smooth',
});
this.showBackNavigationArrow = true;
this.cdrf.detectChanges();
}
}

View File

@@ -1,9 +0,0 @@
<div class="container">
<app-breadcrumbs *ngIf="showBreadCrumbs$ | async"> </app-breadcrumbs>
<app-filter-button
[active]="isFilterActive$ | async"
[module]="activeModule$ | async"
*ngIf="showFilter$ | async"
(toggleFilter)="toggleFilter()"
></app-filter-button>
</div>

View File

@@ -1,31 +0,0 @@
$filter-width: 106px;
.container {
display: flex;
align-items: center;
margin-top: 8px;
margin-bottom: 16px;
app-breadcrumbs {
flex-grow: 1;
margin-left: $filter-width;
::ng-deep app-back-arrow {
margin-right: $filter-width;
margin-left: -$filter-width;
& ~ .breadcrumb-container {
margin-left: 0;
}
}
&:only-child {
margin-left: 0;
::ng-deep app-back-arrow {
margin-right: 0;
margin-left: 0;
}
}
}
}

View File

@@ -1,25 +0,0 @@
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { ComponentsModule } from '../../modules/components.module';
import { ContentHeaderComponent } from './content-header.component';
describe('ContentHeaderComponent', () => {
let fixture: ComponentFixture<ContentHeaderComponent>;
let component: ContentHeaderComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ComponentsModule],
});
fixture = TestBed.createComponent(ContentHeaderComponent);
component = fixture.componentInstance;
});
it('should create the component', () => {
expect(component).toBeTruthy();
});
it('should show breadcrumbs', () => {});
it('should show filter button', () => {});
});

View File

@@ -1,34 +0,0 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { RemissionSelectors } from '../../core/store/selectors/remission.selectors';
import { Select } from '@ngxs/store';
import { ContentHeaderService } from '../../core/services/content-header.service';
@Component({
selector: 'app-content-header',
templateUrl: 'content-header.component.html',
styleUrls: ['./content-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentHeaderComponent implements OnInit {
@Select(RemissionSelectors.getRemissionActiveFilters)
remissionFilters$: Observable<string[]>;
showFilter$: Observable<boolean>;
showBreadCrumbs$: Observable<boolean>;
isFilterActive$: Observable<boolean>;
activeModule$: Observable<'Customer' | 'Branch'>;
constructor(private contentHeaderService: ContentHeaderService) {}
ngOnInit() {
this.showFilter$ = this.contentHeaderService.showFilter$;
this.showBreadCrumbs$ = this.contentHeaderService.showBreadcrumbs$;
this.activeModule$ = this.contentHeaderService.module$;
this.isFilterActive$ = this.contentHeaderService.isFilterActive$;
}
toggleFilter() {
this.contentHeaderService.toggleFilter();
}
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './content-header.component';
// end:ng42.barrel

View File

@@ -1,52 +0,0 @@
<div class="header-wrapper" [ngClass]="{ 'branch-header-wrapper': module === 1 }">
<div class="app-header px-16">
<div class="three-col-grid-container">
<div class="align-left">
<a *ngIf="module === 0" (click)="goToDashboard()" class="nav-link">
<lib-icon width="190px" name="Logo-Refined_2-3x" type="png"></lib-icon>
</a>
<a *ngIf="module === 1">
<lib-icon width="190px" name="Logo-Refined_2-3x" type="png"></lib-icon>
</a>
</div>
<div class="align-center">
<div class="icons-grid pt-5">
<div class="header-item align-center">
<button class="header-icon dashboard" type="button" [routerLink]="['/dashboard']" routerLinkActive="active">
<ui-icon icon="dashboard" size="26px"></ui-icon>
</button>
</div>
<div class="header-item align-center">
<button
class="header-icon notification"
type="button"
[class.active]="notificationCount$ | async"
[disabled]="(notificationCount$ | async) === 0"
(click)="openNotifications()"
>
<div class="notification-counter" *ngIf="notificationCount$ | async; let count">{{ count }}</div>
<ui-icon icon="notification" size="26px"></ui-icon>
</button>
</div>
<div class="header-item align-center">
<span class="current-branch" *ngIf="currentBranch$ | async; let currentBranch" [title]="currentBranch.name">
{{ currentBranch.key | uppercase }}
</span>
<button class="header-icon logout" type="button" (click)="logoff()">
<ui-icon icon="logout" size="26px"></ui-icon>
</button>
</div>
</div>
</div>
<div class="align-right">
<div class="switch-wrapper">
<div></div>
<lib-double-choice-switch [model]="doubleChoiceSwitch" (change)="siteChange($event)"></lib-double-choice-switch>
</div>
</div>
</div>
<app-process-header></app-process-header>
</div>
</div>
<app-log-out #logOut></app-log-out>

View File

@@ -1,177 +0,0 @@
@import 'variables';
button.header-icon {
@apply border-none outline-none bg-transparent;
}
::ng-deep .customer app-header {
button.header-icon {
ui-icon {
@apply text-wild-blue-yonder;
}
&.active ui-icon {
@apply text-inactive-customer;
}
}
.current-branch {
@apply text-wild-blue-yonder;
}
}
::ng-deep .branch app-header {
button.header-icon {
ui-icon {
@apply text-cool-grey;
}
&.active ui-icon {
@apply text-active-branch;
}
}
.current-branch {
@apply text-cool-grey;
}
}
button.notification {
@apply relative;
}
.notification-counter {
@apply absolute flex items-center justify-center -top-2 -right-1 bg-brand text-white text-sm rounded-full w-6 h-6 font-semibold;
z-index: 10;
}
.header-item {
@apply flex;
.current-branch {
@apply flex items-center font-bold;
}
}
.header-wrapper {
background-color: white;
position: fixed;
top: 0;
height: 105px;
width: 100%;
z-index: 150;
padding-top: 30px;
box-shadow: 0px 2px 6px 0px #dde5ec;
display: flex;
justify-content: center;
align-items: center;
}
.branch-header-wrapper {
box-shadow: 0px 2px 6px 0px #e9ebee;
}
.logout {
cursor: pointer;
}
.app-header {
position: fixed;
top: 0;
width: 737px;
height: 105px;
padding-top: 30px;
z-index: 100;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.three-col-grid-container {
display: grid;
grid-template-columns: auto auto auto;
}
.three-col-grid-container-fixed {
display: grid;
grid-template-columns: auto max-content 40px;
grid-column-gap: 1vh;
}
.profile-name {
font-weight: bold;
color: $color-active;
font-size: 16px;
}
.two-col-grid-container {
display: grid;
grid-template-columns: auto auto;
grid-column-gap: 2vh;
}
.icons-grid {
display: grid;
grid-template-columns: auto auto auto;
grid-column-gap: 1vh;
}
.grid-item {
background-color: rgba(255, 255, 255, 0.8);
}
.switch-wrapper {
display: grid;
grid-template-columns: auto min-content;
}
.nav-link {
cursor: pointer;
}
/*
##Device = Big Desktops
*/
@media (min-width: 1281px) {
.app-header {
width: 916px;
}
}
/*
##Device = Laptops, Desktops, Ipad pro
*/
@media (min-width: 1025px) and (max-width: 1280px) {
.app-header {
width: 916px;
}
}
/*
##Device = Tablets, Ipads
*/
@media (min-width: 768px) and (max-width: 1024px) {
.app-header {
width: 737px;
}
}
/*
##Device = Low Resolution Tablets, Mobiles (Landscape)
*/
@media (min-width: 481px) and (max-width: 767px) {
.app-header {
width: 94%;
}
}
/*
##Device = Most of the Smartphones Mobiles (Portrait)
*/
@media (min-width: 320px) and (max-width: 480px) {
.app-header {
width: 94%;
}
}

View File

@@ -1,22 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [HeaderComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});

View File

@@ -1,112 +0,0 @@
import { Component, OnInit, ViewChild, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { LogOutComponent } from '../log-out/log-out.component';
import { DoubleChoiceSwitch, DoubleChoiceSwitchColor } from '@libs/ui';
import { ModuleSwitcher } from '../../core/models/app-switcher.enum';
import { Subject } from 'rxjs';
import { distinctUntilChanged, first, map, takeUntil } from 'rxjs/operators';
import { Store } from '@ngxs/store';
import { ChangeCurrentRoute } from '../../core/store/actions/process.actions';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { NotificationsHub } from '@hub/notifications';
import { UiModalService } from '@ui/modal';
import { ModalNotificationsComponent } from 'apps/modal/notifications/src/public-api';
import { DomainAvailabilityService } from '@domain/availability';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit, OnDestroy {
@ViewChild('logOut') logOutDialog: LogOutComponent;
doubleChoiceSwitch: DoubleChoiceSwitch;
module: ModuleSwitcher = ModuleSwitcher.Customer;
destroy$ = new Subject();
notifications$ = this._notificationsHub.notifications$;
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
currentBranch$ = this._availabilityService.getCurrentBranch();
constructor(
private store: Store,
private router: Router,
private breadcrumb: BreadcrumbService,
private applicationService: ApplicationService,
private cdr: ChangeDetectorRef,
private _notificationsHub: NotificationsHub,
private _modal: UiModalService,
private _availabilityService: DomainAvailabilityService
) {}
customer = 'Kunden';
ngOnInit() {
this.doubleChoiceSwitch = <DoubleChoiceSwitch>{
firstChoice: 'Kunden',
secondChoice: 'Filiale',
firstChoiceColor: <DoubleChoiceSwitchColor>{
selectedBackground: '#1F466C',
selectedText: '#ffffff',
unSelectedbackground: '#edeff0',
unSelectedText: '#000000',
},
secondChoiceColor: <DoubleChoiceSwitchColor>{
selectedBackground: '#596470',
selectedText: '#ffffff',
unSelectedbackground: '#E6EFF9',
unSelectedText: '#000000',
},
isFirstSwitchedOn: true,
};
this.applicationService.section$.pipe(distinctUntilChanged(), takeUntil(this.destroy$)).subscribe((section) => {
this.module = section === 'branch' ? ModuleSwitcher.Branch : ModuleSwitcher.Customer;
this.doubleChoiceSwitch.isFirstSwitchedOn = section === 'customer';
this.cdr.markForCheck();
});
}
ngOnDestroy() {
this.destroy$.next();
}
logoff() {
this.logOutDialog.openDialog();
}
async siteChange(model: DoubleChoiceSwitch) {
const section = model.isFirstSwitchedOn ? 'customer' : 'branch';
const crumb = await this.breadcrumb.getLatestBreadcrumbForSection(section).pipe(first()).toPromise();
if (crumb) {
return this.router.navigate([crumb.path], { queryParams: crumb.params });
}
this.router.navigate([section === 'customer' ? '/dashboard' : '/task-calendar']);
}
goToDashboard() {
this.store.dispatch(new ChangeCurrentRoute('/dashboard'));
this.router.navigate(['/dashboard']);
}
notificationAction() {
// navigations needed for development purpouse
// this.router.navigate(['/remission/finish']);
}
async openNotifications() {
const notifications = await this.notifications$.pipe(first()).toPromise();
this._modal.open({
content: ModalNotificationsComponent,
data: notifications,
config: {
showScrollbarY: false,
},
});
}
}

View File

@@ -1,21 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { SsoService } from 'sso';
@Component({
selector: 'app-log-in',
templateUrl: './log-in.component.html',
styleUrls: ['./log-in.component.scss'],
})
export class LogInComponent implements OnInit {
constructor(private route: ActivatedRoute, private ssoService: SsoService) {}
ngOnInit() {
const token = this.route.snapshot.paramMap.get('token');
this.logIn(token);
}
private logIn(token: string) {
this.ssoService.keyCardLogin(token);
}
}

View File

@@ -1,17 +0,0 @@
<app-modal id="logout-modal">
<div class="logout-modal">
<div class="header">
<h1>Möchten Sie sich wirklich ausloggen?</h1>
<lib-icon (click)="closeModal()" height="21px" class="close-icon" name="close" alt="close"></lib-icon>
</div>
<div class="body"></div>
<div class="actions">
<div class="align-right">
<app-button (action)="closeModal()">Abbrechen</app-button>
</div>
<div class="align-right">
<app-button [primary]="true" (action)="logoff()">Ausloggen</app-button>
</div>
</div>
</div>
</app-modal>

View File

@@ -1,36 +0,0 @@
.logout-modal {
font-family: 'Open Sans';
line-height: 21px;
margin: 16px 0;
display: flex;
align-items: center;
flex-direction: column;
min-height: 185px;
h1 {
font-size: 20px;
font-weight: bold;
text-align: center;
margin-top: 20px;
}
.header {
.close-icon {
position: absolute;
top: 25px;
right: 25px;
height: 21px;
}
.close-icon:hover {
cursor: pointer;
}
}
.actions {
margin-top: 50px;
display: flex;
justify-content: center;
align-items: center;
}
}

View File

@@ -1,22 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { LogOutComponent } from './log-out.component';
describe('LogOutComponent', () => {
let component: LogOutComponent;
let fixture: ComponentFixture<LogOutComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [LogOutComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(LogOutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});

View File

@@ -1,28 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { SsoService } from 'sso';
import { ModalService } from '@libs/ui';
import { NativeContainerService } from 'shared/lib/barcode-scanner';
@Component({
selector: 'app-log-out',
templateUrl: './log-out.component.html',
styleUrls: ['./log-out.component.scss'],
})
export class LogOutComponent implements OnInit {
id = 'logout-modal';
constructor(private ssoService: SsoService, private modalService: ModalService, private nativeContainer: NativeContainerService) {}
ngOnInit() {}
logoff() {
this.ssoService.logoff();
}
openDialog() {
this.modalService.open(this.id);
}
closeModal() {
this.modalService.close(this.id);
}
}

View File

@@ -1,145 +0,0 @@
<!-- Customer module menus -->
<div class="menu" *ngIf="module === 0">
<div class="menu-grid">
<div class="menu-item-grid align-center active" (click)="routeToMenu('/product/search', 'productsearch')">
<div>
<lib-icon
class="menu-icon"
name="{{
router.url === '/product/search' || router.url.startsWith('/product/results') || router.url.startsWith('/product/details')
? 'Icon_Artikelsuche'
: 'Icon_Artikelsuche_inactive'
}}"
width="34px"
height="24px"
></lib-icon>
</div>
<span
*ngIf="
router.url === '/product/search' || router.url.startsWith('/product/results') || router.url.startsWith('/product/details');
else articleSearchLabelElse
"
class="menu-item selected"
>Artikelsuche</span
>
<ng-template #articleSearchLabelElse>
<span class="menu-item">Artikelsuche</span>
</ng-template>
</div>
<div
class="menu-item-grid align-center active"
(click)="routeToMenu('/customer/search', 'customersearch', { customertype: 'store;loyalty;webshop' })"
>
<div>
<lib-icon
class="menu-icon"
name="{{
router.url === '/customer/search' ||
router.url === '/customer/results' ||
router.url.startsWith('/customer/edit') ||
router.url.startsWith('/customer')
? 'Icon_Kundensuche'
: 'Icon_Kundensuche_inactive'
}}"
width="34px"
height="24px"
></lib-icon>
</div>
<span
*ngIf="
router.url === '/customer/search' ||
router.url === '/customer/results' ||
router.url.startsWith('/customer/edit') ||
router.url.startsWith('/customer');
else customerSearchLabelElse
"
class="menu-item selected"
>Kundensuche</span
>
<ng-template #customerSearchLabelElse>
<span class="menu-item">Kundensuche</span>
</ng-template>
</div>
<div class="menu-item-grid align-center active" (click)="routeToMenu('/goods/out', 'shelfsearch')">
<!-- <div class="menu-item-grid align-center"> -->
<div>
<lib-icon
class="menu-icon"
name="{{ router.url === '/goods/out' || router.url.startsWith('/goods/out') ? 'Icon_Abholfach' : 'Icon_Abholfach_inactive' }}"
width="34px"
height="24px"
></lib-icon>
</div>
<span
*ngIf="router.url === '/shelf/search' || router.url.startsWith('/goods/out'); else shelfSearchLabelElse"
class="menu-item selected"
>Warenausgabe</span
>
<ng-template #shelfSearchLabelElse>
<span class="menu-item">Warenausgabe</span>
</ng-template>
</div>
</div>
</div>
<!-- Branch module menus -->
<div class="branch-menu" *ngIf="module === 1">
<div class="menu-grid">
<!-- <div class="menu-item-grid align-center disabled"></div> -->
<div class="menu-item-grid align-center active" (click)="routeToMenu('/task-calendar/calendar', 'calendar')">
<div>
<lib-icon
class="menu-icon"
name="{{
router.url === '/task-calendar/calendar' || router.url === '/task-calendar/tasks'
? 'Icon_Tatigkeitskalender'
: 'Icon_Tatigkeitskalender_inactive'
}}"
width="34px"
height="24px"
></lib-icon>
</div>
<span
*ngIf="router.url === '/task-calendar/calendar' || router.url === '/task-calendar/tasks'; else taskCalendarLabelElse"
class="branch-menu-item branch-selected"
>Tätigkeitskalender</span
>
<ng-template #taskCalendarLabelElse>
<span class="branch-menu-item">Tätigkeitskalender</span>
</ng-template>
</div>
<div class="menu-item-grid align-center active" (click)="routeToMenu('/goods/in', 'goods-in')">
<div>
<lib-icon
class="menu-icon"
name="{{ router.url.startsWith('/goods/in') ? 'Icon_Abholfach_B' : 'Icon_Abholfach_B_inactive' }}"
width="34px"
height="24px"
></lib-icon>
</div>
<span *ngIf="router.url.startsWith('/goods/in'); else abholfachLabelElse" class="branch-menu-item branch-selected">Abholfach</span>
<ng-template #abholfachLabelElse>
<span class="branch-menu-item">Abholfach</span>
</ng-template>
</div>
<div class="menu-item-grid align-center active" (click)="routeToMenu('/remission/create', 'remission')">
<div>
<lib-icon
class="menu-icon"
name="{{
router.url === '/remission/create' || router.url === '/remission/started' ? 'Icon_Remission' : 'Icon_Remission_inactive'
}}"
width="34px"
height="24px"
></lib-icon>
</div>
<span
*ngIf="router.url === '/remission/create' || router.url === '/remission/started'; else remissionLabelElse"
class="branch-menu-item branch-selected"
>Remission</span
>
<ng-template #remissionLabelElse>
<span class="branch-menu-item">Remission</span>
</ng-template>
</div>
</div>
</div>

View File

@@ -1,121 +0,0 @@
@import 'variables';
.menu {
position: fixed;
bottom: 0;
width: 100%;
height: 80px;
background-color: white;
z-index: 100;
box-shadow: 0px -2px 6px 0px #dde5ec;
}
.branch-menu {
position: fixed;
bottom: 0;
width: 100%;
height: 80px;
background-color: white;
z-index: 100;
box-shadow: 0px -2px 6px 0px #e9ebee;
}
.menu-grid {
display: grid;
grid-template-columns: 33% 33% 33%;
padding-top: 20px;
margin: auto;
}
.menu-item-grid {
display: grid;
grid-template-columns: auto;
outline: none;
}
.menu-item {
font-size: 15px;
line-height: 21px;
font-weight: 600;
color: $color-inactive;
}
.selected {
color: $color-active;
}
.disabled {
opacity: 0.2;
}
.branch-menu-item {
font-size: 15px;
line-height: 21px;
font-weight: 600;
color: $branch-color-inactive;
&.branch-disabled {
color: #edeff0;
}
}
.branch-selected {
color: $branch-color-active;
}
.missing-menu {
font-family: 'Open Sans';
font-size: 15px;
font-weight: 600;
color: #89949e;
opacity: 0.6;
}
.active {
cursor: pointer;
}
/*
##Device = Big Desktops
*/
@media (min-width: 1281px) {
.menu-grid {
width: 980px;
}
}
/*
##Device = Laptops, Desktops, Ipad pro
*/
@media (min-width: 1025px) and (max-width: 1280px) {
.menu-grid {
width: 980px;
}
}
/*
##Device = Tablets, Ipads
*/
@media (min-width: 768px) and (max-width: 1024px) {
.menu-grid {
width: 768px;
}
}
/*
##Device = Low Resolution Tablets, Mobiles (Landscape)
*/
@media (min-width: 481px) and (max-width: 767px) {
.menu-grid {
width: 94%;
}
}
/*
##Device = Most of the Smartphones Mobiles (Portrait)
*/
@media (min-width: 320px) and (max-width: 480px) {
.menu-grid {
width: 94%;
}
}

View File

@@ -1,22 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MenuComponent } from './menu.component';
describe('MenuComponent', () => {
let component: MenuComponent;
let fixture: ComponentFixture<MenuComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [MenuComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});

View File

@@ -1,168 +0,0 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { Observable, Subject } from 'rxjs';
import { Process } from '../../core/models/process.model';
import { Store, Select } from '@ngxs/store';
import {
AddProcess,
ChangeCurrentRoute,
ResetProcessProductFilters,
SetOnlineCustomerCreationStatus,
} from '../../core/store/actions/process.actions';
import { Breadcrumb } from '../../core/models/breadcrumb.model';
import { ResetBreadcrumbsTo } from '../../core/store/actions/breadcrumb.actions';
import { takeUntil, distinctUntilChanged } from 'rxjs/operators';
import { ProcessSelectors } from '../../core/store/selectors/process.selectors';
import { ResetFilters } from '../../core/store/actions/filter.actions';
import { ModuleSwitcher } from '../../core/models/app-switcher.enum';
import { ModuleSwitcherService } from '../../core/services/module-switcher.service';
import { SetBranchProcessCurrentPath } from '../../core/store/actions/branch-process.actions';
import { InitRemissionState, SetRemissionFinishedProcessStatus, ResetRemissionState } from '../../core/store/actions/remission.actions';
import { RemissionSelectors } from '../../core/store/selectors/remission.selectors';
import { RemissionFinishingProcessStatus } from '../../modules/remission/models/remission-finishing-process-status.enum';
import { DeleteFormState } from '../../core/store/actions/forms.actions';
import { USER_FORM_STATE_KEY, USER_EXTRAS_FORM_STATE_KEY, USER_ERRORS_FORM_STATE_KEY } from '../../core/utils/app.constants';
import { ApplicationService } from '@core/application';
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.scss'],
})
export class MenuComponent implements OnInit, OnDestroy {
@Select(ProcessSelectors.getProcesses) processes$: Observable<Process[]>;
module: ModuleSwitcher = ModuleSwitcher.Customer;
processes: Process[] = [];
destroy$ = new Subject();
breadCrumbId = 'product';
constructor(
public router: Router,
private store: Store,
private moduleSwitcherService: ModuleSwitcherService,
private applicationService: ApplicationService
) {}
activeMenu = '';
routeToMenu(menuPath: string, menuTag: string, queryParams?: { [key: string]: string }): void {
if (this.processes && this.processes.length < 1 && this.module === ModuleSwitcher.Customer) {
this.createProcess(menuPath);
}
this.activeMenu = menuTag;
this.store.dispatch(
new ResetBreadcrumbsTo(
<Breadcrumb>{
name: this.nameFromPath(menuPath),
path: menuPath,
},
this.breadCrumbId,
true
)
);
if (menuTag === 'productsearch') {
this.store.dispatch(new ResetFilters());
this.store.dispatch(new ResetProcessProductFilters());
}
if (menuTag === 'remission') {
this.store.dispatch(new InitRemissionState());
}
if (menuTag === 'customersearch') {
this.store.dispatch(new DeleteFormState(USER_FORM_STATE_KEY));
this.store.dispatch(new DeleteFormState(USER_EXTRAS_FORM_STATE_KEY));
this.store.dispatch(new DeleteFormState(USER_ERRORS_FORM_STATE_KEY));
this.store.dispatch(new SetOnlineCustomerCreationStatus({ error: false, invalidProperties: null }));
}
if (this.router.url === '/remission/finish' && menuTag === 'remission') {
const processStatus = this.store.selectSnapshot(RemissionSelectors.getRemissionFinishingProcessStatus);
if (processStatus === RemissionFinishingProcessStatus.start) {
this.store.dispatch(new SetRemissionFinishedProcessStatus(RemissionFinishingProcessStatus.notSet));
} else {
this.store.dispatch(new SetRemissionFinishedProcessStatus(RemissionFinishingProcessStatus.notSet));
this.navigateToNewRemissionList();
return;
}
}
if (this.module === ModuleSwitcher.Branch) {
this.store.dispatch(new SetBranchProcessCurrentPath(menuPath));
} else {
this.store.dispatch(new ChangeCurrentRoute(menuPath));
}
this.router.navigate([menuPath], { queryParams });
}
routeToMenuBranch(menuPath: string, menuTag: string): void {
this.activeMenu = menuTag;
this.router.navigate([menuPath]);
}
createProcess(menuPath: string) {
const newProcess = <Process>{
id: 1,
name: 'Vorgang 1',
currentRoute: menuPath,
};
this.store.dispatch(new AddProcess(newProcess));
}
routeFromPath(path: string) {
if (path) {
return path.substring(1, path.length);
}
}
nameFromPath(path: string) {
switch (path) {
case '/product/search':
this.breadCrumbId = 'product';
return 'Artikelsuche';
case '/customer/search':
this.breadCrumbId = 'customer';
return 'Kundensuche';
case '/shelf/search':
this.breadCrumbId = 'shelf';
return 'Warenausgabe';
case '/branch/main':
this.breadCrumbId = 'goodsin';
return 'Abholfach';
case '/remission/create':
this.breadCrumbId = 'remission';
return 'Remission';
case '/task-calendar/calendar':
this.breadCrumbId = 'taskCalendar';
return 'Tätigkeitskalendar';
default:
this.breadCrumbId = 'product';
return 'Artikelsuche';
}
}
ngOnInit() {
this.processes$.pipe(takeUntil(this.destroy$)).subscribe((data: Process[]) => (this.processes = data));
this.applicationService.section$.pipe(takeUntil(this.destroy$), distinctUntilChanged()).subscribe((section) => {
this.module = section === 'branch' ? ModuleSwitcher.Branch : ModuleSwitcher.Customer;
});
}
ngOnDestroy() {
this.destroy$.next();
}
private navigateToNewRemissionList() {
this.store.dispatch(new ResetRemissionState());
const path = '/remission/create';
this.store.dispatch(
new ResetBreadcrumbsTo(
<Breadcrumb>{
name: 'Remission',
path: path,
},
'remission',
true
)
);
this.store.dispatch(new SetBranchProcessCurrentPath(path));
this.router.navigate([path]);
}
}

View File

@@ -1,26 +0,0 @@
<app-modal id="printer-modal">
<div class="printer-modal">
<div class="header">
<h1>Wählen Sie einen Drucker aus</h1>
<lib-icon (click)="closeModal()" height="21px" class="close-icon" name="close" alt="close"></lib-icon>
</div>
<span *ngIf="error && errorMessage" class="error-message isa-font-color-warning">{{ errorMessage }}</span>
<ng-container *ngIf="!error">
<div class="body">
<app-dropdown
[load]="true"
(valueChanges)="printerSelected($event)"
[options]="options"
[selected]="selected"
[loading]="!loaded"
[showFull]="true"
></app-dropdown>
</div>
<div class="actions">
<div>
<app-button [primary]="true" [load]="true" [disabled]="!loaded" (action)="emitPrint()" #printBtn>Drucken </app-button>
</div>
</div>
</ng-container>
</div>
</app-modal>

View File

@@ -1,48 +0,0 @@
.printer-modal {
font-family: 'Open Sans';
line-height: 21px;
margin: 16px 0;
display: flex;
align-items: center;
flex-direction: column;
min-height: 185px;
h1 {
font-size: 20px;
font-weight: bold;
text-align: center;
margin-top: 20px;
}
.header {
.close-icon {
position: absolute;
top: 25px;
right: 25px;
height: 21px;
}
.close-icon:hover {
cursor: pointer;
}
}
.actions {
margin-top: 35px;
margin-bottom: 30px;
display: flex;
justify-content: center;
align-items: center;
}
.error-message {
padding: 1rem 2rem;
text-align: center;
font-size: 16px;
font-weight: 600;
}
}
.body {
padding-top: 30px;
}

View File

@@ -1,22 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PrinterSelectionComponent } from './printer-selection.component';
describe('PrinterSelectionComponent', () => {
let component: PrinterSelectionComponent;
let fixture: ComponentFixture<PrinterSelectionComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PrinterSelectionComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(PrinterSelectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});

View File

@@ -1,115 +0,0 @@
import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core';
import { PrinterService } from '../../core/services/printer.service';
import { ModalService, ButtonComponent } from '@libs/ui';
import { Subject } from 'rxjs';
import { takeUntil, map, tap, delay } from 'rxjs/operators';
import { Printer } from '../../core/models/printer.model';
import { PRINT_ERROR_MSG } from './printer-selection.constants';
@Component({
selector: 'app-printer-selection',
templateUrl: './printer-selection.component.html',
styleUrls: ['./printer-selection.component.scss'],
})
export class PrinterSelectionComponent implements OnInit, OnDestroy {
id = 'printer-modal';
options: string[] | number[];
selected: string | number;
selectedPrinterValue: string;
printers: { key: string; text: string; selected: boolean }[] = [];
destroy$ = new Subject();
error = false;
errorMessage = PRINT_ERROR_MSG;
loaded = false;
printingRequested = false;
submited = false;
@Output() print: EventEmitter<string> = new EventEmitter();
@Output() closed = new EventEmitter();
@ViewChild('printBtn') printBtn: ButtonComponent;
constructor(private printerService: PrinterService, private modalService: ModalService, private cdr: ChangeDetectorRef) {}
ngOnInit() {}
printerSelected(value: string | number) {
this.selected = value;
this.selectedPrinterValue = this.printers.find((t) => t.text === this.selected).key;
}
emitPrint() {
if (this.loaded) {
this.print.emit(this.selectedPrinterValue);
this.printBtn.startLoading();
} else {
this.printingRequested = true;
this.printBtn.startLoading();
}
}
loadPrinters() {
this.loaded = false;
this.error = false;
this.printerService
.getAvailablePrinters()
.pipe(takeUntil(this.destroy$))
.subscribe((response) => {
if ((response as { error: string }).error) {
const errorResponse = response as { error: string };
this.error = true;
this.errorMessage = errorResponse.error;
return;
}
const result = response as Printer[];
this.printers = result.map((t) => {
return { key: t.key, text: t.description, selected: t.selected };
});
const selectedPrinter = this.printers.find((printer) => printer.selected);
this.options = this.printers.map((t) => t.text);
this.selectedPrinterValue = selectedPrinter ? selectedPrinter.key : this.printers[0].key;
this.selected = selectedPrinter ? selectedPrinter.text : this.options[0];
this.error = false;
this.loaded = true;
if (this.printBtn) {
this.printBtn.stopLoading();
}
if (!this.selected) {
this.error = true;
this.errorMessage = 'No available printers';
}
this.cdr.markForCheck();
});
}
openDialog() {
this.loadPrinters();
this.modalService.open(this.id);
}
closeModal() {
this.modalService.close(this.id);
if (this.printBtn) {
this.printBtn.stopLoading();
}
this.closed.emit();
}
setError(errorMessage?: string) {
this.error = true;
if (!!errorMessage) {
this.errorMessage = errorMessage;
}
}
ngOnDestroy() {
if (this.printBtn) {
this.printBtn.stopLoading();
}
this.destroy$.next();
}
contentLoaded() {
if (this.printingRequested) {
this.print.emit(this.selectedPrinterValue);
}
}
}

View File

@@ -1 +0,0 @@
export const PRINT_ERROR_MSG = 'Der Druckauftrag konnte nicht ausgeführt werden.';

View File

@@ -1,11 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PrinterSelectionComponent } from './printer-selection.component';
import { IconModule, DropdownModule, ModalModule, ButtonModule } from '@libs/ui';
@NgModule({
declarations: [PrinterSelectionComponent],
imports: [CommonModule, IconModule, DropdownModule, ModalModule, ButtonModule],
exports: [PrinterSelectionComponent],
})
export class PrinterSelectionModule {}

View File

@@ -1,8 +0,0 @@
import { trigger, transition, animate, style } from '@angular/animations';
export const addAnimation = trigger('add', [
transition('void => true', [
style({ opacity: 0, transform: 'translateX(200%)' }),
animate('0.3s ease-out', style({ opacity: 1, transform: 'translateX(0%)' })),
]),
]);

View File

@@ -1,46 +0,0 @@
<div class="grid-container">
<div class="align-left">
<div class="back-arrow-container" *ngIf="showBackContainer && !isIPad" (mouseover)="showBackIcon()" (mouseleave)="hideBackIcon()">
<lib-icon class="icon" width="118px" height="50px" name="tab_Arrow" type="png" (click)="scrollBack()" *ngIf="showBack"></lib-icon>
</div>
<div class="process-grid-container" [ngClass]="{ adding: showAddingPadding }" *ngIf="module === 0" #panel>
<app-process-tab
[id]="process.id"
style="display: inline-block; height: 100%;"
*ngFor="let process of processes; let last = last"
[module]="module"
[last]="last"
[process]="process"
[@add]="process.new"
></app-process-tab>
</div>
<div class="process-grid-container" *ngIf="module === 1" #panel>
<app-process-tab
id="{{ branchProcess.id }}"
style="display: inline-block; height: 100%;"
[module]="module"
[process]="branchProcess"
></app-process-tab>
</div>
<div
class="foward-arrow-container"
*ngIf="showFowardContainer && !isIPad"
(mouseover)="showFowardIcon()"
(mouseleave)="hideFowardIcon()"
>
<lib-icon class="icon" width="118px" height="50px" name="tab_Arrow" type="png" (click)="scrollFoward()" *ngIf="showNext"></lib-icon>
</div>
</div>
<div class="align-right">
<div class="grid-container-fix-width-last-col">
<div class="align-right add-process-label">
<span *ngIf="processes && processes.length === 0 && module === 0" class="process-span" (click)="addProcess()">{{
startProcessLabel
}}</span>
</div>
<app-button *ngIf="module === 0" (action)="addProcess()" class="add-process" [type]="'small'">
<lib-icon width="34px" height="36px" name="add-red"></lib-icon>
</app-button>
</div>
</div>
</div>

View File

@@ -1,113 +0,0 @@
@import 'variables';
.grid-container {
@apply grid grid-flow-col;
grid-template-columns: 1fr auto;
}
.align-left {
@apply grid grid-flow-col;
grid-template-columns: auto 1fr auto;
}
.grid-container-fix-width-last-col {
display: grid;
grid-template-columns: auto;
padding-top: 7px;
}
.process-span {
color: $hima-color-red;
font-weight: bold;
}
.process-grid-container {
white-space: nowrap;
padding-top: 13px;
padding-right: 25px;
overflow-y: scroll;
scroll-behavior: smooth;
display: block;
}
.adding {
padding-right: 200px;
}
.add-process-label {
position: absolute;
top: 99px;
right: 63px;
cursor: pointer;
}
.back-arrow-container {
width: 118px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
z-index: 50;
top: 83px;
cursor: pointer;
transform: rotate(180deg);
}
.foward-arrow-container {
width: 118px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
z-index: 50;
top: 87px;
cursor: pointer;
}
.placeholder-tab {
width: 300px;
height: 42px;
display: inline-block;
height: 100%;
}
/*
##Device = Big Desktops
*/
@media (min-width: 1281px) {
.grid-container {
grid-template-columns: 93% auto;
}
.foward-arrow-container {
right: 80px;
}
}
/*
##Device = Laptops, Desktops, Ipad pro
*/
@media (min-width: 1025px) and (max-width: 1280px) {
.grid-container {
grid-template-columns: 93% auto;
}
.foward-arrow-container {
right: 80px;
}
}
/*
##Device = Tablets, Ipads
*/
@media (min-width: 768px) and (max-width: 1024px) {
.grid-container {
grid-template-columns: 685px auto;
}
.foward-arrow-container {
right: 68px;
}
}

View File

@@ -1,22 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ProcessHeaderComponent } from './process-header.component';
describe('ProcessHeaderComponent', () => {
let component: ProcessHeaderComponent;
let fixture: ComponentFixture<ProcessHeaderComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ProcessHeaderComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(ProcessHeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});

View File

@@ -1,248 +0,0 @@
import { Component, OnInit, OnDestroy, ElementRef, ViewChild, ChangeDetectorRef, ChangeDetectionStrategy, Input } from '@angular/core';
import { Process } from '../../core/models/process.model';
import { Observable, Subject } from 'rxjs';
import { Breadcrumb } from '../../core/models/breadcrumb.model';
import { Store, Select } from '@ngxs/store';
import { AddProcess } from '../../core/store/actions/process.actions';
import { addAnimation } from './add.animation';
import { Router } from '@angular/router';
import { AddBreadcrumb } from '../../core/store/actions/breadcrumb.actions';
import { takeUntil, switchMap, distinctUntilChanged } from 'rxjs/operators';
import { ProcessSelectors } from '../../core/store/selectors/process.selectors';
import { WindowRef } from '../../core/services/window-ref.service';
import { ModuleSwitcher } from '../../core/models/app-switcher.enum';
import { ApplicationService } from '@core/application';
@Component({
selector: 'app-process-header',
templateUrl: './process-header.component.html',
styleUrls: ['./process-header.component.scss'],
animations: [addAnimation],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProcessHeaderComponent implements OnInit, OnDestroy {
startProcessLabel = 'VORGANG STARTEN';
@ViewChild('panel', { read: ElementRef }) public panel: ElementRef<any>;
@Select(ProcessSelectors.getProcesses) process$: Observable<Process[]>;
@Select(ProcessSelectors.getCurrentProcess) selectedProcess$: Observable<Process>;
module: ModuleSwitcher;
branchProcess = <Process>{
id: 1,
name: 'Wareneingang',
};
processes: Process[] = [];
destroy$ = new Subject();
showNext = false;
showBack = false;
showFowardContainer = false;
showBackContainer = false;
offset: number;
selectedProcessId: string;
selectedProcess: any;
isIPad = false;
showAddingPadding = false;
private iPadDetected = false;
private iPadEventRecieved = false;
constructor(
private store: Store,
private router: Router,
private cdr: ChangeDetectorRef,
private windowRef: WindowRef,
private applicationService: ApplicationService
) {}
addProcess() {
const itemNo = !this.processes
? 0
: this.processes && this.processes.length === 0
? 1
: this.processes[this.processes.length - 1].id + 1;
const newProcess = <Process>{
id: itemNo,
new: true,
name: `Vorgang ${itemNo}`,
currentRoute: '/product/search',
};
if (itemNo >= 4) {
this.initializeAnimation(itemNo);
}
this.store
.dispatch(new AddProcess(newProcess))
.toPromise()
.then(() => {
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: 'Artikelsuche',
path: '/product/search',
},
'product',
true
)
);
this.router.navigate(['/product/search']);
});
this.applicationService.setActivatedProcessId(newProcess.id);
}
ngOnInit() {
this.isIPad = this.isIPadEnv();
this.selectedProcess$
.pipe(
switchMap((proc: Process) => {
if (proc) {
this.selectedProcessId = proc.id + '';
}
return this.process$;
}),
takeUntil(this.destroy$),
distinctUntilChanged((prev, curr) => this.processComperer(prev, curr))
)
.subscribe(
(data) => {
this.processes = data;
this.cdr.detectChanges();
if (this.panel?.nativeElement?.children.length > 0) {
this.scrollableArrows();
}
},
(err) => console.error(err)
);
this.applicationService.section$.pipe(distinctUntilChanged(), takeUntil(this.destroy$)).subscribe((section) => {
this.module = section === 'branch' ? ModuleSwitcher.Branch : ModuleSwitcher.Customer;
this.cdr.detectChanges();
});
}
private initializeAnimation(processCount: number) {
this.showAddingPadding = true;
this.cdr.detectChanges();
setTimeout(() => {
this.panel.nativeElement.scrollTo({ left: this.panel.nativeElement.scrollLeft + 300 * processCount, behavior: 'smooth' });
this.cdr.detectChanges();
}, 100);
}
processComperer(prev: Process[], curr: Process[]) {
try {
if (!prev || !curr) {
return false;
}
if (prev.length !== curr.length) {
return false;
}
if (prev.length === 0 || curr.length === 0) {
return false;
}
const prevStr = JSON.stringify(
prev.map((t) => {
return { new: t.new, name: t.name };
})
);
const currStr = JSON.stringify(
curr.map((t) => {
return { new: t.new, name: t.name };
})
);
const status = prevStr === currStr;
return status;
} catch (error) {
return false;
}
}
ngOnDestroy() {
this.destroy$.next();
}
isIPadEnv() {
const navigator = this.windowRef.nativeWindow.navigator as Navigator;
const standalone = (navigator as any).standalone,
userAgent = navigator.userAgent.toLowerCase(),
ios = /iphone|ipod|ipad/.test(userAgent);
this.iPadDetected = ios && !standalone;
return this.iPadDetected || this.iPadEventRecieved;
}
scrollableArrows() {
this.offset = Math.ceil(this.panel.nativeElement.clientWidth / 2);
let selected: any;
for (let _i = 0; _i < this.panel.nativeElement.children.length; _i++) {
const proc = this.panel.nativeElement.children[_i];
if (proc.id === this.selectedProcessId) {
selected = proc;
this.selectedProcess = proc;
}
}
const lastProcess = this.panel.nativeElement.children[this.panel.nativeElement.children.length - 1];
if (selected) {
const clientWidth = this.panel.nativeElement.clientWidth;
this.showAddingPadding = false;
this.cdr.detectChanges();
const selecetedPos = selected.offsetLeft + selected.offsetWidth;
const lastPos = lastProcess.offsetLeft + lastProcess.offsetWidth;
this.showFowardContainer =
(selecetedPos + this.offset * 2 > clientWidth || selecetedPos + this.offset < lastPos) &&
lastPos > clientWidth &&
lastPos > this.panel.nativeElement.scrollWidth;
// This number is from testing > 643
this.showBackContainer = selecetedPos > 643;
}
this.cdr.detectChanges();
}
scrollBack() {
const moveCalc = this.panel.nativeElement.scrollLeft - this.offset;
this.panel.nativeElement.scrollTo({ left: moveCalc, behavior: 'smooth' });
this.cdr.detectChanges();
this.showBackContainer = this.panel.nativeElement.scrollLeft - this.offset > 0;
this.showBack = false;
this.showFowardContainer = this.panel.nativeElement.scrollLeft < this.panel.nativeElement.scrollWidth;
this.cdr.detectChanges();
}
scrollFoward() {
const moveCalc = this.panel.nativeElement.scrollLeft + this.offset;
this.panel.nativeElement.scrollTo({ left: moveCalc, behavior: 'smooth' });
this.cdr.detectChanges();
this.showFowardContainer = moveCalc + this.offset * 2 < this.panel.nativeElement.scrollWidth;
this.showNext = false;
// If clicked next, then back button is always there
this.showBackContainer = true;
this.cdr.detectChanges();
}
showFowardIcon() {
this.showNext = true;
}
hideFowardIcon() {
this.showNext = false;
}
showBackIcon() {
this.showBack = true;
}
hideBackIcon() {
this.showBack = false;
}
}

View File

@@ -1,58 +0,0 @@
<!-- Customer module process -->
<div class="grid-item" id="{{ process.id }}" *ngIf="module === 0" [ngClass]="{ last: last }">
<div class="grid-container" [ngClass]="{ 'selected-process': process.id === (currentProcessId$ | async) }">
<div class="process-name-container pt-3" (click)="selectProcess(process)">
<span class="process-name">{{ process.name }}</span>
</div>
<ng-container *ngIf="{ length: cartCount$ | async }; let cartCount">
<div
[class.items-in-cart]="cartCount.length > 0"
class="cart-container"
[ngClass]="{ download: cartBackgroundForDownload }"
(click)="openCart(process)"
>
<lib-icon
mt="12px"
ml="15px"
width="17px"
height="16px"
name="Shopping_Cart"
*ngIf="cartCount.length === 0 && !cartBackgroundForDownload && process.id === (currentProcessId$ | async)"
class="process-cart-icon"
></lib-icon>
<lib-icon
mt="12px"
ml="15px"
width="17px"
height="16px"
name="Shopping_Cart_Inactive"
*ngIf="cartCount.length === 0 && !cartBackgroundForDownload && process.id !== (currentProcessId$ | async)"
class="process-cart-icon"
></lib-icon>
<lib-icon
mt="12px"
ml="15px"
width="17px"
height="16px"
name="shopping_cart_white"
*ngIf="cartCount.length > 0 || cartBackgroundForDownload"
class="process-cart-icon"
></lib-icon>
<div [@cartnumber]="cartanimation" class="pt-3 process-cart-number-container">
<span
[class.items-in-cart]="cartCount.length > 0"
class="process-cart-number"
[ngClass]="{ 'number-download': cartBackgroundForDownload }"
>{{ cartCount.length }}</span
>
</div>
</div>
</ng-container>
<div *ngIf="process.id === (currentProcessId$ | async)">
<a (click)="openDeleteConfirmationDialog()">
<lib-icon class="process-delete-icon" name="close" width="15px" height="15px" mt="12px"></lib-icon>
</a>
</div>
</div>
<app-process-delete-dialog #deleteporcessdialog (deleted)="deleteProcess($event)" [process]="process"></app-process-delete-dialog>
</div>

View File

@@ -1,133 +0,0 @@
@import 'variables';
.grid-container {
display: flex;
flex-direction: row;
height: 42px;
border-bottom: 3px solid #fff;
& > .cart-container {
display: flex;
flex-direction: row;
height: 38px;
background-color: #e6eff9;
border-radius: 25px;
z-index: 2;
position: relative;
bottom: 1px;
padding-right: 20px;
cursor: pointer;
}
}
.download {
background-color: $hima-download-cart-color !important;
}
.process-name-container {
cursor: pointer;
max-width: 100px;
}
.process-name-container-branch {
cursor: pointer;
max-width: 200px;
}
.process-name {
font-size: 18px;
font-weight: bold;
color: #557596;
// opacity: 0.3;
margin-top: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 10px;
display: block;
}
.process-name-branch {
font-size: 18px;
font-weight: bold;
color: #596470;
opacity: 0.3;
margin-top: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 10px;
display: block;
}
.process-cart-number {
font-size: 18px;
font-weight: bold;
color: #557596;
// opacity: 0.3;
position: relative;
top: 4px;
left: 8px;
}
.number-download {
color: #ffffff !important;
}
// .process-cart-icon {
// opacity: 0.3;
// }
.selected-process {
border-bottom: 3px solid $hima-color-red;
.process-name {
color: $color-active;
opacity: 1;
}
.process-name-branch {
color: #596470;
opacity: 1;
}
.process-cart-number {
color: $color-active;
opacity: 1;
}
.process-cart-icon {
opacity: 1;
}
}
.process-delete-icon {
margin-left: 10px;
cursor: pointer;
}
.process-cart-icon-numbered {
margin-left: 3px;
}
.process-cart-number-container {
margin-left: 1px;
}
.grid-item {
display: inline-block;
padding-right: 30px;
height: 100%;
}
.last {
padding-right: 0px;
}
.items-in-cart {
@apply bg-active-customer !important;
color: #fff !important;
}

View File

@@ -1,22 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ProcessTabComponent } from './process-tab.component';
describe('ProcessTabComponent', () => {
let component: ProcessTabComponent;
let fixture: ComponentFixture<ProcessTabComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ProcessTabComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(ProcessTabComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});

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