Merge branch 'develop' into feature/responsive-customer-orders

This commit is contained in:
Nino Righi
2023-04-24 17:41:36 +02:00
417 changed files with 4833 additions and 4042 deletions

View File

@@ -3,6 +3,5 @@
"johnpapa.angular2",
"esbenp.prettier-vscode",
"angular.ng-template",
"eg2.vscode-npm-script"
]
}

View File

@@ -375,37 +375,6 @@
}
}
},
"@shell/breadcrumb": {
"projectType": "library",
"root": "apps/shell/breadcrumb",
"sourceRoot": "apps/shell/breadcrumb/src",
"prefix": "shell",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/shell/breadcrumb/tsconfig.lib.json",
"project": "apps/shell/breadcrumb/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/breadcrumb/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/breadcrumb/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@domain/defs": {
"projectType": "library",
"root": "apps/domain/defs",
@@ -840,37 +809,6 @@
}
}
},
"@shell/header": {
"projectType": "library",
"root": "apps/shell/header",
"sourceRoot": "apps/shell/header/src",
"prefix": "shell",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/shell/header/tsconfig.lib.json",
"project": "apps/shell/header/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/header/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/header/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@modal/reorder": {
"projectType": "library",
"root": "apps/modal/reorder",
@@ -1058,74 +996,6 @@
}
}
},
"@shell/footer": {
"projectType": "library",
"root": "apps/shell/footer",
"sourceRoot": "apps/shell/footer/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/footer/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/footer/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/footer/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/footer/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@shell/process": {
"projectType": "library",
"root": "apps/shell/process",
"sourceRoot": "apps/shell/process/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/process/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/process/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/process/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/process/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@domain/isa": {
"projectType": "library",
"root": "apps/domain/isa",
@@ -1160,40 +1030,6 @@
}
}
},
"@shell/filter-overlay": {
"projectType": "library",
"root": "apps/shell/filter-overlay",
"sourceRoot": "apps/shell/filter-overlay/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/filter-overlay/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/filter-overlay/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/filter-overlay/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/filter-overlay/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@store/search-component-store": {
"projectType": "library",
"root": "apps/store/search-component-store",
@@ -1634,6 +1470,39 @@
}
}
}
},
"shell": {
"projectType": "library",
"root": "apps/shell",
"sourceRoot": "apps/shell/src",
"prefix": "shell",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/tsconfig.spec.json",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
}
}
}

View File

@@ -1,5 +1,4 @@
import { Injectable } from '@angular/core';
import { DomainAvailabilityService } from '@domain/availability';
import { Store } from '@ngrx/store';
import { BranchDTO } from '@swagger/checkout';
import { isBoolean, isNumber } from '@utils/common';
@@ -16,25 +15,30 @@ import {
selectActivatedProcess,
patchProcess,
patchProcessData,
selectTitle,
setTitle,
} from './store';
@Injectable()
export class ApplicationService {
/** @deprecated */
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
/** @deprecated */
get activatedProcessId() {
return this.activatedProcessIdSubject.value;
}
/** @deprecated */
get activatedProcessId$() {
return this.activatedProcessIdSubject.asObservable();
}
title$ = this.store.select(selectTitle);
constructor(private store: Store) {}
setTitle(title: string) {
this.store.dispatch(setTitle({ title }));
}
getProcesses$(section?: 'customer' | 'branch') {
const processes$ = this.store.select(selectProcesses);
return processes$.pipe(map((processes) => processes.filter((process) => (section ? process.section === section : true))));

View File

@@ -3,6 +3,8 @@ import { ApplicationProcess } from '..';
const prefix = '[CORE-APPLICATION]';
export const setTitle = createAction(`${prefix} Set Title`, props<{ title: string }>());
export const setSection = createAction(`${prefix} Set Section`, props<{ section: 'customer' | 'branch' }>());
export const addProcess = createAction(`${prefix} Add Process`, props<{ process: ApplicationProcess }>());

View File

@@ -1,9 +1,18 @@
import { Action, createReducer, on } from '@ngrx/store';
import { setSection, addProcess, removeProcess, setActivatedProcess, patchProcess, patchProcessData } from './application.actions';
import {
setSection,
addProcess,
removeProcess,
setActivatedProcess,
patchProcess,
patchProcessData,
setTitle,
} from './application.actions';
import { ApplicationState, INITIAL_APPLICATION_STATE } from './application.state';
const _applicationReducer = createReducer(
INITIAL_APPLICATION_STATE,
on(setTitle, (state, { title }) => ({ ...state, title })),
on(setSection, (state, { section }) => ({ ...state, section })),
on(addProcess, (state, { process }) => ({ ...state, processes: [...state.processes, { data: {}, ...process }] })),
on(removeProcess, (state, { processId }) => {

View File

@@ -2,6 +2,8 @@ import { createFeatureSelector, createSelector } from '@ngrx/store';
import { ApplicationState } from './application.state';
export const selectApplicationState = createFeatureSelector<ApplicationState>('core-application');
export const selectTitle = createSelector(selectApplicationState, (s) => s.title);
export const selectSection = createSelector(selectApplicationState, (s) => s.section);
export const selectProcesses = createSelector(selectApplicationState, (s) => s.processes);

View File

@@ -1,11 +1,13 @@
import { ApplicationProcess } from '../defs';
export interface ApplicationState {
title: string;
processes: ApplicationProcess[];
section: 'customer' | 'branch';
}
export const INITIAL_APPLICATION_STATE: ApplicationState = {
title: '',
processes: [],
section: 'customer',
};

View File

@@ -135,9 +135,9 @@ export class BreadcrumbService {
crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
}
getLatestBreadcrumbForSection(section: 'customer' | 'branch') {
getLatestBreadcrumbForSection(section: 'customer' | 'branch', predicate: (crumb: Breadcrumb) => boolean = (_) => true) {
return this.store
.select(selectors.selectBreadcrumbsBySection, { section })
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => true)));
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => predicate(f))));
}
}

View File

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

View File

@@ -0,0 +1,8 @@
import { NgModule } from '@angular/core';
@NgModule({
declarations: [],
imports: [],
exports: [],
})
export class NavigationModule {}

View File

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

View File

@@ -21,7 +21,7 @@ export interface OutletLocations {
}
@Injectable({ providedIn: 'root' })
export class AppNavigationService {
export class NavigationService {
get isTablet() {
return this._environment.isTablet();
}

View File

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

View File

@@ -45,6 +45,8 @@ export class DomainInStockService {
const key = this.getKey({ itemId, branchId });
this._addToInStockQueue({ itemId, branchId });
let _previousValue: InStock;
const sub = combineLatest([this._inStockMap, this._inStockFetchingMap])
.pipe(distinctUntilChanged(isEqual))
.subscribe(([inStockMap, inStockFetchingMap]) => {
@@ -54,7 +56,12 @@ export class DomainInStockService {
inStock: inStockMap[key],
fetching: inStockFetchingMap[key] ?? false,
};
obs.next(inStock);
if (!isEqual(inStock, _previousValue)) {
obs.next(inStock);
}
_previousValue = inStock;
});
return () => {
sub.unsubscribe();

View File

@@ -447,7 +447,7 @@ export class DomainRemissionService {
* Create a new receipt for the given return/remission
* @param returnId Return ID
* @param receiptNumber Receipt number
* @returns ReturnDTO - ShippingDocument
* @returns ReceiptDTO
*/
async createReceipt(returnDTO: ReturnDTO, receiptNumber?: string): Promise<ReceiptDTO> {
const stock = await this._getStock();
@@ -471,14 +471,41 @@ export class DomainRemissionService {
return receipt;
}
async completeReceipt(returnId: number, receiptId: number, packageCode: string): Promise<ReceiptDTO> {
/**
* Create a new Package and assign it to a receipt
* @param returnId Return ID
* @param receiptId Receipt ID
* @param packageNumber Packagenumber
* @returns ReceiptDTO
*/
async createReceiptAndAssignPackage({
returnId,
receiptId,
packageNumber,
}: {
returnId: number;
receiptId: number;
packageNumber: string;
}): Promise<ReceiptDTO> {
const response = await this._returnService
.ReturnCreateAndAssignPackage({
returnId,
receiptId,
data: {
packageNumber,
},
})
.toPromise();
const receipt: ReceiptDTO = response.result;
return receipt;
}
async completeReceipt(returnId: number, receiptId: number): Promise<ReceiptDTO> {
const res = await this._returnService
.ReturnFinalizeReceipt({
returnId,
receiptId,
data: {
packageCode,
},
data: {},
})
.toPromise();

View File

@@ -1,6 +1,5 @@
import { isDevMode, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DebugComponent } from './debug/debug.component';
import {
CanActivateCartGuard,
CanActivateCartWithProcessIdGuard,
@@ -17,9 +16,9 @@ import {
} from './guards';
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
import { MainComponent } from './main.component';
import { PreviewComponent } from './preview';
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
import { ShellComponent, ShellModule } from './shell';
import { TokenLoginComponent, TokenLoginModule } from './token-login';
const routes: Routes = [
@@ -40,7 +39,7 @@ const routes: Routes = [
children: [
{
path: 'kunde',
component: ShellComponent,
component: MainComponent,
children: [
{
path: 'dashboard',
@@ -106,7 +105,7 @@ const routes: Routes = [
},
{
path: 'filiale',
component: ShellComponent,
component: MainComponent,
children: [
{
path: 'task-calendar',
@@ -152,7 +151,7 @@ if (isDevMode()) {
}
@NgModule({
imports: [RouterModule.forRoot(routes), ShellModule, TokenLoginModule],
imports: [RouterModule.forRoot(routes), TokenLoginModule],
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@@ -1,7 +1,3 @@
:host {
@apply block box-border;
}
button {
@apply fixed bottom-4 right-2 bg-blue-500 text-white font-bold py-2 px-4 rounded z-tooltip;
@apply block;
}

View File

@@ -32,9 +32,11 @@ import { IsaErrorHandler } from './providers/isa.error-handler';
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
import { RootStateService } from './store/root-state.service';
import * as Commands from './commands';
import { UiIconModule } from '@ui/icon';
import { UiIconModule, UI_ICON_CFG } from '@ui/icon';
import { PreviewComponent } from './preview';
import { NativeContainerService } from 'native-container';
import { ShellModule } from '@shared/shell';
import { MainComponent } from './main.component';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
@@ -74,11 +76,12 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
}
@NgModule({
declarations: [AppComponent],
declarations: [AppComponent, MainComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
ShellModule.forRoot(),
AppRoutingModule,
AppSwaggerModule,
AppDomainModule,
@@ -103,31 +106,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
ScanAdapterModule.forRoot(),
ScanditScanAdapterModule.forRoot(),
PlatformModule,
UiIconModule.forRoot({
aliases: [
{ alias: 'd-account', name: 'account' },
{ alias: 'd-no-account', name: 'package-variant-closed' },
{ name: 'isa-audio', alias: 'AU' },
{ name: 'isa-audio', alias: 'CAS' },
{ name: 'isa-audio', alias: 'DL' },
{ name: 'isa-audio', alias: 'KAS' },
{ name: 'isa-hard-cover', alias: 'BUCH' },
{ name: 'isa-hard-cover', alias: 'GEB' },
{ name: 'isa-hard-cover', alias: 'HC' },
{ name: 'isa-hard-cover', alias: 'KT' },
{ name: 'isa-ebook', alias: 'EB' },
{ name: 'isa-non-book', alias: 'GLO' },
{ name: 'isa-non-book', alias: 'HDL' },
{ name: 'isa-non-book', alias: 'NB' },
{ name: 'isa-non-book', alias: 'SPL' },
{ name: 'isa-calendar', alias: 'KA' },
{ name: 'isa-scroll', alias: 'MA' },
{ name: 'isa-software', alias: 'SW' },
{ name: 'isa-soft-cover', alias: 'TB' },
{ name: 'isa-video', alias: 'VI' },
{ name: 'isa-news-paper', alias: 'ZS' },
],
}),
UiIconModule.forRoot(),
],
providers: [
{
@@ -156,6 +135,11 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
useClass: IsaErrorHandler,
},
{ provide: LOCALE_ID, useValue: 'de-DE' },
{
provide: UI_ICON_CFG,
useFactory: (config: Config) => config.get('@ui/icon'),
deps: [Config],
},
],
bootstrap: [AppComponent],
})

View File

@@ -2,9 +2,9 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { EnvironmentService } from '@core/environment';
import { NavigationService } from '@core/navigation';
import { DomainCheckoutService } from '@domain/checkout';
import { first } from 'rxjs/operators';
import { AppNavigationService } from '../app-navigation.service';
@Injectable({ providedIn: 'root' })
export class CanActivateProductGuard implements CanActivate {
@@ -16,7 +16,7 @@ export class CanActivateProductGuard implements CanActivate {
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _environment: EnvironmentService,
private readonly _navigationService: AppNavigationService,
private readonly _navigationService: NavigationService,
private readonly _router: Router
) {}

View File

@@ -0,0 +1,3 @@
<shell-root>
<router-outlet></router-outlet>
</shell-root>

View File

@@ -0,0 +1,10 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-main',
templateUrl: 'main.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MainComponent {
constructor() {}
}

View File

@@ -1,4 +0,0 @@
// start:ng42.barrel
export * from './shell.component';
export * from './shell.module';
// end:ng42.barrel

View File

@@ -1,88 +0,0 @@
<div class="shell-header-wrapper">
<shell-header [section]="section$ | async" (sectionChange)="setSection($event)">
<a [routerLink]="['/kunde/dashboard']" routerLinkActive="active" class="dashboard-btn">
<ui-icon icon="dashboard" size="26px"></ui-icon>
</a>
<button class="notifications-btn" [disabled]="(notificationCount$ | async) === 0" (click)="openNotifications()">
<ui-icon icon="notification" size="26px"></ui-icon>
<span class="notification-counter" *ngIf="notificationCount$ | async; let count">{{ count }}</span>
</button>
<button (click)="logout()" class="logout-btn">
<span *ngIf="currentBranch$ | async; let currentBranch">{{ currentBranch.key | uppercase }}</span>
<ui-icon icon="logout" size="26px"></ui-icon>
</button>
</shell-header>
</div>
<div class="shell-process-wrapper">
<shell-process
[label]="addProcessLabel$ | async"
[canAddProcess]="canAddProcess$ | async"
(addProcess)="addProcess(); processTabs?.last?.triggerAnimation()"
>
<shell-process-tab
#processTabs
(activateProcess)="activateProcess($event)"
(closeProcess)="closeProcess($event)"
(processAction)="processAction($event)"
*ngFor="let process of processes$ | async; trackBy: trackByIdFn"
[isActive]="(activatedProcessId$ | async) === process.id"
[process]="process"
></shell-process-tab>
</shell-process>
</div>
<div class="main-wrapper">
<main>
<router-outlet></router-outlet>
</main>
</div>
<div class="shell-footer-wrapper">
<shell-footer *ngIf="section$ | async; let section">
<ng-container *ngIf="section === 'customer'">
<a [routerLink]="productRoutePath$ | async" routerLinkActive="active">
<ui-icon icon="catalog" size="30px"></ui-icon>
Artikelsuche
</a>
<a [routerLink]="[customerBasePath$ | async, 'customer']" routerLinkActive="active">
<ui-icon icon="customer" size="24px"></ui-icon>
Kundensuche
</a>
<a *ifRole="'Store'" [routerLink]="[customerBasePath$ | async, 'goods', 'out']" routerLinkActive="active">
<ui-icon icon="box_out" size="24px"></ui-icon>
Warenausgabe
</a>
<a *ifRole="'CallCenter'" [routerLink]="customerOrdersPath$ | async" routerLinkActive="active">
<ui-svg-icon icon="package-variant-closed" [size]="28"></ui-svg-icon>
Kundenbestellungen
</a>
</ng-container>
<ng-container *ngIf="section === 'branch'">
<a [routerLink]="['/filiale/assortment']" routerLinkActive="active">
<ui-svg-icon icon="shape-outline" [size]="24"></ui-svg-icon>
Sortiment
</a>
<a [routerLink]="['/filiale/task-calendar']" routerLinkActive="active">
<ui-icon icon="calendar_check" size="24px"></ui-icon>
Tätigkeitskalender
</a>
<a [routerLink]="['/filiale/goods/in']" routerLinkActive="active">
<ui-icon icon="box_return" size="24px"></ui-icon>
Abholfach
</a>
<a [routerLink]="[remissionUrl$ | async]" [queryParams]="remissionQueryParams$ | async" routerLinkActive="active">
<ui-icon icon="documents_refresh" size="24px"></ui-icon>
Remission
</a>
<a [routerLink]="['/filiale/package-inspection']" routerLinkActive="active" (click)="fetchAndOpenPackages()">
<ui-svg-icon icon="clipboard-check-outline" [size]="24"></ui-svg-icon>
Wareneingang
</a>
</ng-container>
</shell-footer>
</div>
<button *ngIf="isDevelopment" class="block absolute bottom-0 right-0 z-tooltip p-4 opacity-5" (click)="debugOpen = !debugOpen">
<ui-svg-icon icon="bug-outline"></ui-svg-icon>
</button>
<app-debug *ngIf="debugOpen" class="absolute inset-x-0 top-0 max-h-[calc(100vh-80px)]"></app-debug>

View File

@@ -1,59 +0,0 @@
:host {
@apply block relative min-h-screen;
}
.main-wrapper {
@apply fixed right-0 left-0 overflow-auto;
top: 8.375rem;
bottom: 5rem;
}
main {
@apply w-full mx-auto max-w-desktop px-px-15 desktop:px-6 self-stretch;
}
.shell-header-wrapper {
@apply fixed top-0 left-0 right-0 bg-white;
shell-header {
@apply w-full max-w-desktop px-px-15 desktop:px-6 mx-auto;
}
button.notifications-btn {
@apply relative;
.notification-counter {
@apply absolute flex items-center justify-center top-2 right-px-3 text-sm rounded-full w-6 h-6 font-semibold;
background-color: var(--shell-notification-counter-background);
color: var(--shell-notification-counter-text);
z-index: 10;
}
}
}
.shell-process-wrapper {
@apply fixed left-0 right-0 bg-white;
top: 5.125rem;
shell-process {
@apply w-full max-w-desktop px-px-15 desktop:px-6 mx-auto;
height: 52px;
}
}
shell-process {
height: 52px;
grid-area: process;
}
.shell-footer-wrapper {
@apply fixed bottom-0 left-0 right-0 bg-white z-fixed shadow-card;
shell-footer {
@apply w-full max-w-desktop px-px-15 desktop:px-6 mx-auto;
.active {
@apply font-bold;
color: var(--shell-footer-link-active);
}
}
}

View File

@@ -1,445 +0,0 @@
// unit test ShellComponent with Spectator
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { AuthModule, AuthService } from '@core/auth';
import { Config } from '@core/config';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainDashboardService } from '@domain/isa';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { Spectator, createComponentFactory, SpyObject, createSpyObject } from '@ngneat/spectator';
import { DashboardComponent } from '@page/dashboard';
import { ShellFooterComponent } from '@shell/footer';
import { ShellHeaderComponent } from '@shell/header';
import { ShellProcessComponent, ShellProcessTabComponent } from '@shell/process';
import { IconRegistry, UiIconComponent, UiIconModule } from '@ui/icon';
import { UiModalService } from '@ui/modal';
import { EnvelopeDTO, MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
import { MockComponent } from 'ng-mocks';
import { of } from 'rxjs';
import { first } from 'rxjs/operators';
import { ShellComponent } from './shell.component';
import { WrongDestinationModalService } from 'apps/page/package-inspection/src/lib/components/wrong-destination-modal/wrong-destination-modal.service';
// DummyComponent Class
@Component({
selector: 'dummy-component',
template: '<div></div>',
})
class DummyComponent {
constructor() {}
}
describe('ShellComponent', () => {
let spectator: Spectator<ShellComponent>;
let applicationServiceMock: SpyObject<ApplicationService>;
let modalServiceMock: SpyObject<UiModalService>;
let notificationsHubMock: SpyObject<NotificationsHub>;
let router: Router;
let breadcrumbServiceMock: SpyObject<BreadcrumbService>;
let authServiceMock: SpyObject<AuthService>;
const createComponent = createComponentFactory({
component: ShellComponent,
imports: [
UiIconModule,
RouterTestingModule.withRoutes([
{ path: 'kunde', component: DummyComponent },
{ path: 'kunde/dashboard', component: DashboardComponent },
]),
AuthModule,
],
declarations: [
MockComponent(ShellHeaderComponent),
MockComponent(ShellFooterComponent),
MockComponent(ShellProcessComponent),
MockComponent(ShellProcessTabComponent),
],
mocks: [
BreadcrumbService,
DomainAvailabilityService,
AuthService,
DomainDashboardService,
Config,
WrongDestinationModalService,
IconRegistry,
],
});
beforeEach(() => {
applicationServiceMock = createSpyObject(ApplicationService);
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of([]));
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
applicationServiceMock.getLastActivatedProcessWithSectionAndType$.and.returnValue(of({}));
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({}));
notificationsHubMock = createSpyObject(NotificationsHub);
notificationsHubMock.notifications$ = of({});
modalServiceMock = createSpyObject(UiModalService);
authServiceMock = createSpyObject(AuthService);
spectator = createComponent({
providers: [
{ provide: ApplicationService, useValue: applicationServiceMock },
{ provide: NotificationsHub, useValue: notificationsHubMock },
{ provide: UiModalService, useValue: modalServiceMock },
{ provide: AuthService, useValue: authServiceMock },
],
});
breadcrumbServiceMock = spectator.inject(BreadcrumbService);
router = spectator.inject(Router);
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('shell-header', () => {
it('should call setSection() on sectionChange event with the section argument', () => {
spyOn(spectator.component, 'setSection');
spectator.triggerEventHandler('shell-header', 'sectionChange', 'branch');
expect(spectator.component.setSection).toHaveBeenCalledWith('branch');
});
it('should render the header buttons', () => {
// Test verhält sich anders, wenn die größe des Browserfensters kleiner ist als 640px, da
// die Buttons dann unsichtbar werden und ins Drei-Punkt Menü verschoben werden.
if (document.body.clientWidth > 639) {
expect(spectator.query('shell-header .notifications-btn')).toBeVisible();
expect(spectator.query('shell-header .dashboard-btn')).toBeVisible();
expect(spectator.query('shell-header .logout-btn')).toBeVisible();
} else {
expect(spectator.query('shell-header .notifications-btn')).not.toBeVisible();
expect(spectator.query('shell-header .dashboard-btn')).not.toBeVisible();
expect(spectator.query('shell-header .logout-btn')).not.toBeVisible();
}
});
it('should have a anchor tag which navigates to /kunde/dashboard', () => {
const anchor = spectator.query('shell-header a');
expect(anchor).toHaveAttribute('href', '/kunde/dashboard');
});
});
describe('shell-process', () => {
it('should call addProcess() on addProcess event', () => {
spyOn(spectator.component, 'addProcess');
spectator.triggerEventHandler('shell-process', 'addProcess', undefined);
expect(spectator.component.addProcess).toHaveBeenCalled();
});
describe('shell-process-tab', () => {
it('should render for each process', () => {
const processes = [{}, {}, {}];
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
spectator.detectComponentChanges();
expect(spectator.queryAll('shell-process-tab')).toHaveLength(processes.length);
});
it('should call activateProcess() on activateProcess event', () => {
const processes = [{ id: 1 }];
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
spectator.detectComponentChanges();
spyOn(spectator.component, 'activateProcess');
spectator.triggerEventHandler('shell-process-tab', 'activateProcess', processes[0].id);
expect(spectator.component.activateProcess).toHaveBeenCalledWith(1);
});
it('should call closeProcess() on closeProcess event', () => {
const processes = [{ id: 1 }];
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
spectator.detectComponentChanges();
spyOn(spectator.component, 'closeProcess');
spectator.triggerEventHandler('shell-process-tab', 'closeProcess', processes[0].id);
expect(spectator.component.closeProcess).toHaveBeenCalledWith(1);
});
});
});
describe('shell-footer', () => {
it('should render when section is set', () => {
applicationServiceMock.getSection$.and.returnValue(of('customer'));
spectator.detectComponentChanges();
expect(spectator.query('shell-footer')).toBeVisible();
});
it('should not render when section is undefined', () => {
applicationServiceMock.getSection$.and.returnValue(of(undefined));
spectator.detectComponentChanges();
expect(spectator.query('shell-footer')).not.toBeVisible();
});
xit('should display the menu items for section customer', () => {
applicationServiceMock.getSection$.and.returnValue(of('customer'));
spectator.component.customerBasePath$ = of('/kunde/1');
spectator.detectComponentChanges();
authServiceMock.hasRole.and.returnValue(true);
const anchors = spectator.queryAll('shell-footer a');
expect(anchors[0]).toHaveText('Artikelsuche');
expect(anchors[0]).toHaveAttribute('href', '/kunde/1/product');
expect(anchors[1]).toHaveText('Kundensuche');
expect(anchors[1]).toHaveAttribute('href', '/kunde/1/customer');
expect(anchors[2]).toHaveText('Warenausgabe');
expect(anchors[2]).toHaveAttribute('href', '/kunde/1/goods/out');
});
it('should display the menu items for section branch', () => {
applicationServiceMock.getSection$.and.returnValue(of('branch'));
spectator.detectComponentChanges();
const anchors = spectator.queryAll('shell-footer a');
expect(anchors[0]).toHaveText('Sortiment');
expect(anchors[0]).toHaveAttribute('href', '/filiale/assortment');
expect(anchors[1]).toHaveText('Tätigkeitskalender');
expect(anchors[1]).toHaveAttribute('href', '/filiale/task-calendar');
expect(anchors[2]).toHaveText('Abholfach');
expect(anchors[2]).toHaveAttribute('href', '/filiale/goods/in');
expect(anchors[3]).toHaveText('Remission');
expect(anchors[3]).toHaveAttribute('href', '/filiale/remission');
expect(anchors[4]).toHaveText('Wareneingang');
expect(anchors[4]).toHaveAttribute('href', '/filiale/package-inspection');
});
});
describe('activatedProcessId$', () => {
it('should call _appService.getActivatedProcessId$() and return its value', async () => {
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(1));
const processId = await spectator.component.activatedProcessId$.pipe(first()).toPromise();
expect(processId).toBe(1);
expect(applicationServiceMock.getActivatedProcessId$).toHaveBeenCalled();
});
});
describe('section$', () => {
it('should call _appService.getSection$() and return its value', async () => {
applicationServiceMock.getSection$.and.returnValue(of('branch'));
const section = await spectator.component.section$.pipe(first()).toPromise();
expect(section).toBe('branch');
expect(applicationServiceMock.getSection$).toHaveBeenCalled();
});
});
describe('processes$', () => {
it('should call _appService.processes$() and return its value', async () => {
applicationServiceMock.getProcesses$.and.returnValue(of([{}, {}]));
const processes = await spectator.component.processes$.pipe(first()).toPromise();
expect(processes).toHaveLength(2);
expect(applicationServiceMock.getProcesses$).toHaveBeenCalledWith('customer');
});
});
describe('remissionProcess$', () => {
it('should call _appService.getProcessById$() with Remission Id and return its value', async () => {
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
await spectator.component.remissionProcess$.pipe(first()).toPromise();
expect(applicationServiceMock.getProcessById$).toHaveBeenCalled();
});
});
describe('remissionUrl$', () => {
it('should return the correct url if process.data.active is available', async () => {
const process = {
id: 4000,
data: {
active: 9999,
},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
expect(url).toBe('/filiale/remission/9999/list');
});
it('should return the correct url if process.data.active is not available', async () => {
const process = {
id: 4000,
data: {},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
expect(url).toBe('/filiale/remission');
});
});
describe('remissionQueryParams$', () => {
it('should return the correct queryParams if process.data.active and process.data.queryParams are available', async () => {
const process = {
id: 4000,
data: {
active: 9999,
queryParams: { filter: 'test' },
},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
expect(queryParams).toEqual(process.data.queryParams);
});
it('should return the correct queryParams if process.data.active and process.data.queryParams are not available', async () => {
const process = {
id: 4000,
data: {},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
expect(queryParams).toEqual({});
});
});
describe('setSection()', () => {
it('should call _appService.setSection() with the argument section', async () => {
await spectator.component.setSection('customer');
expect(applicationServiceMock.setSection).toHaveBeenCalledWith('customer');
});
it('should call activateProcess if getLastActivatedProcessWithSection returns a value', async () => {
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({ id: 1 }));
spyOn(spectator.component, 'activateProcess');
await spectator.component.setSection('customer');
expect(spectator.component.activateProcess).toHaveBeenCalledWith(1);
});
});
describe('logout()', () => {
it('should call _authService.logout()', () => {
spectator.component.logout();
expect(authServiceMock.logout).toHaveBeenCalled();
});
});
describe('addProcess()', () => {
it('should call navigate to /kunde/{timestamp}/product', () => {
spyOn(router, 'navigate');
spyOn(Date, 'now').and.returnValue(123);
spectator.component.addProcess();
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 123, 'product']);
});
});
describe('closeProcess()', () => {
it('should call _appService.removeProcess() with the processId argument', () => {
const processes = [{}, {}, {}];
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
spectator.component.closeProcess(1);
expect(applicationServiceMock.removeProcess).toHaveBeenCalledWith(1);
});
it('should navigate to kunde/dashboard if no process is available', async () => {
spyOn(router, 'navigate');
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of([]));
spectator.detectComponentChanges();
await spectator.component.closeProcess(1);
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 'dashboard']);
});
it('should not navigate to kunde/dashboard if processes are available', async () => {
spyOn(router, 'navigate');
const processes = [
{ id: 1, name: 'test', section: 'customer' },
{ id: 2, name: 'test', section: 'customer' },
];
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({}));
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
await spectator.component.closeProcess(1);
expect(router.navigate).not.toHaveBeenCalledWith(['/kunde', 'dashboard']);
});
it('should activate the next process when it was not the last process', async () => {
spyOn(spectator.component, 'activateProcess');
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(
of({
id: 2,
name: 'test',
section: 'customer',
activated: 2,
})
);
const processes = [
{ id: 1, name: 'test', section: 'customer', activated: 1 },
{ id: 2, name: 'test', section: 'customer', activated: 2 },
];
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
await spectator.component.closeProcess(1);
expect(spectator.component.activateProcess).toHaveBeenCalledWith(2);
});
});
describe('activateProcess()', () => {
it('should get the last activated breadcrumb by key and if it is defined it navigates to its path with queryParams', async () => {
const crumb = { path: '/kunde/product', params: { id: 1 } };
spyOn(router, 'navigate');
breadcrumbServiceMock.getLastActivatedBreadcrumbByKey$.and.returnValue(of(crumb));
await spectator.component.activateProcess(1);
expect(router.navigate).toHaveBeenCalledWith([crumb.path], { queryParams: crumb.params });
});
it('should navigate to /kunde if no breadcrumb for this process exists', async () => {
breadcrumbServiceMock.getLastActivatedBreadcrumbByKey$.and.returnValue(of(undefined));
spyOn(router, 'navigate');
await spectator.component.activateProcess(1);
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 1, 'product']);
});
});
describe('processAction()', () => {
it('should navigate to cart when process type is cart', () => {
spyOn(router, 'navigate');
const process: ApplicationProcess = { id: 1, name: 'Vorgang', section: 'customer', type: 'cart' };
spectator.component.processAction(process);
expect(router.navigate).toHaveBeenCalledWith(['/kunde', process.id, 'cart']);
});
it('should not navigate to when process type is not cart', () => {
spyOn(router, 'navigate');
const process: ApplicationProcess = { id: 1, name: 'Vorgang', section: 'customer', type: 'goods-out' };
spectator.component.processAction(process);
expect(router.navigate).not.toHaveBeenCalled();
});
});
describe('openNotifications()', () => {
it('should call modalService.open() with the ModalNotificationComponent', async () => {
const notifications: EnvelopeDTO<MessageBoardItemDTO[]> = {
data: [{}, {}, {}],
};
spectator.component.notifications$ = of(notifications);
await spectator.component.openNotifications();
expect(modalServiceMock.open).toHaveBeenCalledWith({
content: ModalNotificationsComponent,
data: notifications,
config: {
showScrollbarY: false,
},
});
});
});
});

View File

@@ -1,209 +0,0 @@
import { Component, ChangeDetectionStrategy, ViewChildren, QueryList, TrackByFunction, NgZone } from '@angular/core';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { first, map, shareReplay, switchMap, take, tap } from 'rxjs/operators';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { UiModalService } from '@ui/modal';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { combineLatest } from 'rxjs';
import { AuthService } from '@core/auth';
import { DomainAvailabilityService } from '@domain/availability';
import { ShellProcessTabComponent } from '@shell/process';
import { Config } from '@core/config';
import { WrongDestinationModalService } from 'apps/page/package-inspection/src/lib/components/wrong-destination-modal/wrong-destination-modal.service';
import { EnvironmentService } from '@core/environment';
import { AppNavigationService } from '../app-navigation.service';
@Component({
selector: 'app-shell',
templateUrl: 'shell.component.html',
styleUrls: ['shell.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShellComponent {
isDevelopment = Boolean(this._config.get('debug'));
debugOpen = false;
@ViewChildren('processTabs')
readonly processTabs: QueryList<ShellProcessTabComponent>;
notifications$ = this._notificationsHub.notifications$;
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
get activatedProcessId$() {
return this._appService.getActivatedProcessId$().pipe(
tap((activatedProcessId) => {
this.processTabs?.find((process) => process?.process?.id === activatedProcessId && !process?.isActive)?.slideIntoView();
})
);
}
customerBasePath$ = this.activatedProcessId$.pipe(
switchMap((processId) => this._appService.getProcessById$(processId)),
map((process) => {
if (!!process && process.section === 'customer' && process.type !== 'cart-checkout') {
// Übernehme aktiven Prozess
return `/kunde/${process.id}`;
} else {
// Über Guards wird ein neuer Prozess erstellt
return '/kunde';
}
})
);
productRoutePath$ = this.activatedProcessId$.pipe(
switchMap((processId) => this._appService.getProcessById$(processId)),
map((process) => {
if (!!process && process.section === 'customer' && process.type !== 'cart-checkout') {
// Übernehme aktiven Prozess
return this._navigationService.getArticleSearchBasePath(process.id);
} else {
// Über Guards wird ein neuer Prozess erstellt
return this._navigationService.getArticleSearchBasePath();
}
})
);
customerOrdersPath$ = this.customerBasePath$.pipe(
map((basePath) => {
if (this.isTablet) {
return [basePath, 'order'];
} else {
return [basePath, 'order', { outlets: { main: null, left: 'search', right: 'filter' } }];
}
})
);
get section$() {
return this._appService.getSection$().pipe(shareReplay());
}
get processes$() {
return this.section$.pipe(switchMap((section) => this._appService.getProcesses$(section)));
}
get remissionProcess$() {
return this._appService.getProcessById$(this._config.get('process.ids.remission'));
}
get remissionUrl$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active ? `/filiale/remission/${process.data.active}/list` : '/filiale/remission'))
);
}
get remissionQueryParams$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active && process?.data?.queryParams ? process.data.queryParams : {}))
);
}
get addProcessLabel$() {
return combineLatest([this.section$, this.processes$]).pipe(
map(([section, processes]) => (section === 'customer' && processes.length === 0 ? 'VORGANG STARTEN' : ''))
);
}
get canAddProcess$() {
return this.section$.pipe(map((section) => section === 'customer'));
}
get currentBranch$() {
return this._availabilityService.getDefaultBranch();
}
get isTablet() {
return this._environment.isTablet();
}
constructor(
private readonly _appService: ApplicationService,
private readonly _config: Config,
private readonly _notificationsHub: NotificationsHub,
private readonly _modal: UiModalService,
private readonly _router: Router,
private readonly _breadcrumbService: BreadcrumbService,
private readonly _authService: AuthService,
private readonly _availabilityService: DomainAvailabilityService,
private readonly _zone: NgZone,
private readonly _wrongDestinationModalService: WrongDestinationModalService,
private readonly _environment: EnvironmentService,
private readonly _navigationService: AppNavigationService
) {}
async setSection(section: 'customer' | 'branch') {
this._appService.setSection(section);
const lastProcessId = (await this._appService.getLastActivatedProcessWithSection$(section).pipe(first()).toPromise())?.id;
if (lastProcessId) {
this.activateProcess(lastProcessId);
} else {
this._router.navigate([section === 'customer' ? '/kunde' : '/filiale']);
}
}
// Process werden über Guards erstellt und aktiviert. An dieser Stelle wird nur navigiert
async addProcess() {
const processId = Date.now();
await this._navigationService.navigateToProductSearch({ processId });
}
async activateProcess(activatedProcessId: number) {
try {
const latestCrumb = await this._breadcrumbService?.getLastActivatedBreadcrumbByKey$(activatedProcessId)?.pipe(take(1)).toPromise();
await this._zone.run(async () => {
if (latestCrumb) {
if (latestCrumb.path instanceof Array) {
await this._router.navigate(latestCrumb.path, { queryParams: latestCrumb.params });
} else {
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
}
} else {
await this._navigationService.navigateToProductSearch({ processId: activatedProcessId });
}
});
} catch (error) {}
}
async closeProcess(processId: number) {
this._appService.removeProcess(processId);
const processes = await this.processes$.pipe(first()).toPromise();
if (processes.length === 0) {
await this._router.navigate(['/kunde', 'dashboard']);
return;
}
const section = await this.section$.pipe(first()).toPromise();
const lastActivatedProcess = await this._appService.getLastActivatedProcessWithSection$(section).pipe(first()).toPromise();
this.activateProcess(lastActivatedProcess?.id);
}
processAction(process: ApplicationProcess) {
if (process?.type === 'cart') {
this._router.navigate(['/kunde', process.id, 'cart']);
}
}
async logout() {
await this._authService.logout();
}
async openNotifications() {
const notifications = await this.notifications$.pipe(first()).toPromise();
this._modal.open({
content: ModalNotificationsComponent,
data: notifications,
config: {
showScrollbarY: false,
},
});
}
trackByIdFn: TrackByFunction<ApplicationProcess> = (_, process) => process.id;
fetchAndOpenPackages = () => this._wrongDestinationModalService.fetchAndOpen();
}

View File

@@ -1,31 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OverlayModule } from '@angular/cdk/overlay';
import { ShellHeaderModule } from '@shell/header';
import { ShellProcessModule } from '@shell/process';
import { ShellFooterModule } from '@shell/footer';
import { ShellComponent } from './shell.component';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { AuthModule } from '@core/auth';
import { DebugComponent } from '../debug/debug.component';
@NgModule({
imports: [
RouterModule,
CommonModule,
ShellHeaderModule,
ShellProcessModule,
ShellFooterModule,
UiIconModule,
OverlayModule,
AuthModule,
DebugComponent,
],
exports: [ShellComponent],
declarations: [ShellComponent],
providers: [],
})
export class ShellModule {}

View File

@@ -1,20 +0,0 @@
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-outlined.woff2) format('woff2');
}
@font-face {
font-family: 'Material Symbols Rounded';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-rounded.woff2) format('woff2');
}
@font-face {
font-family: 'Material Symbols Sharp';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-sharp.woff2) format('woff2');
}

View File

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

View File

@@ -8,9 +8,8 @@
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"clientId": "isa-client",
"responseType": "code",
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
},
"@core/logger": {
@@ -41,7 +40,7 @@
"rootUrl": "https://filialinformationsystem-integration.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-integration.paragon-data.net/inv/v1"
"rootUrl": "https://isa-integration.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-integration.paragon-data.net/wws/v1"

View File

File diff suppressed because one or more lines are too long

View File

@@ -41,10 +41,10 @@
"rootUrl": "https://filialinformationsystem.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa.paragon-systems.de/inv/v1"
"rootUrl": "https://isa.paragon-systems.de/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa.paragon-data.net/wws/v1"
"rootUrl": "https://isa.paragon-systems.de/wws/v1"
},
"hubs": {
"notifications": {
@@ -69,6 +69,6 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": ""
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
}
}

View File

@@ -41,7 +41,7 @@
"rootUrl": "https://filialinformationsystem-staging.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v1"
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-staging.paragon-systems.de/wws/v1"
@@ -69,6 +69,6 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": ""
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
}
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
<link href="/assets/icons/icons.css" rel="stylesheet" />
<link rel="manifest" href="manifest.webmanifest" />
<meta name="theme-color" content="#1976d2" />
</head>

View File

@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
@@ -13,10 +14,11 @@ export class AssortmentComponent implements OnInit {
return this._config.get('process.ids.assortment');
}
constructor(private _config: Config, private _breadcrumb: BreadcrumbService) {}
constructor(private _config: Config, private _breadcrumb: BreadcrumbService, private _app: ApplicationService) {}
ngOnInit() {
this.createBreadcrumbIfNotExists();
this._app.setTitle('Sortiment');
}
async createBreadcrumbIfNotExists(): Promise<void> {

View File

@@ -5,8 +5,9 @@ import { EnvironmentService } from '@core/environment';
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
import { ProductListItemDTO } from '@swagger/wws';
import { DateAdapter } from '@ui/common';
import { debounceTime, map, shareReplay, switchMap } from 'rxjs/operators';
import { debounceTime, filter, map, shareReplay, switchMap } from 'rxjs/operators';
import { PriceUpdateComponentStore } from '../price-update.component.store';
import { ReplaySubject, combineLatest } from 'rxjs';
@Component({
selector: 'page-price-update-item',
@@ -16,8 +17,18 @@ import { PriceUpdateComponentStore } from '../price-update.component.store';
providers: [DatePipe],
})
export class PriceUpdateItemComponent {
private _item$ = new ReplaySubject<ProductListItemDTO>(1);
private _item: ProductListItemDTO;
@Input()
item: ProductListItemDTO;
get item() {
return this._item;
}
set item(value) {
this._item = value;
this._item$.next(value);
}
get publicationDate() {
if (!!this.item?.product?.publicationDate) {
@@ -40,21 +51,14 @@ export class PriceUpdateItemComponent {
defaultBranch$ = this._availability.getDefaultBranch();
inStock$ = this.defaultBranch$.pipe(
inStock$ = combineLatest([this.defaultBranch$, this._item$]).pipe(
debounceTime(100),
switchMap(
(defaultBranch) =>
this._stockService.getInStock$({
itemId: Number(this.item?.product?.catalogProductNumber),
branchId: defaultBranch?.id,
})
// TODO: Bugfixing INSTOCK
// .pipe(
// map((instock) => {
// this.item.product.ean === '9783551775559' ? console.log({ item: this.item, instock }) : '';
// return instock;
// })
// )
filter(([defaultBranch, item]) => !!defaultBranch && !!item),
switchMap(([defaultBranch, item]) =>
this._stockService.getInStock$({
itemId: Number(item?.product?.catalogProductNumber),
branchId: defaultBranch?.id,
})
),
shareReplay(1)
);

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
import { Config } from '@core/config';
import { provideComponentStore } from '@ngrx/component-store';
import { ShellFilterOverlayComponent } from '@shell/filter-overlay';
import { SharedFilterOverlayComponent } from '@shared/components/filter-overlay';
import { UiFilter, UiFilterComponent } from '@ui/filter';
import { PriceUpdateComponentStore } from './price-update.component.store';
import { combineLatest, Subject, Subscription } from 'rxjs';
@@ -26,8 +26,8 @@ export class PriceUpdateComponent implements OnInit {
hint$ = new Subject<string>();
@ViewChild(ShellFilterOverlayComponent)
filterOverlay: ShellFilterOverlayComponent;
@ViewChild(SharedFilterOverlayComponent)
filterOverlay: SharedFilterOverlayComponent;
/**
* Zeigt die liste an, wenn entweder keine items geladen werden oder wenn items geladen wurden

View File

@@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiFilterNextModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon';
import { UiSpinnerModule } from '@ui/spinner';
@@ -8,7 +8,7 @@ import { PriceUpdateListModule } from './price-update-list';
import { PriceUpdateComponent } from './price-update.component';
@NgModule({
imports: [CommonModule, PriceUpdateListModule, UiIconModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
imports: [CommonModule, PriceUpdateListModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
exports: [PriceUpdateComponent],
declarations: [PriceUpdateComponent],
providers: [],

View File

@@ -224,7 +224,7 @@
<div class="product-actions">
<button *ngIf="!(store.isDownload$ | async)" class="cta-availabilities" (click)="showAvailabilities()">
Vorrätig in anderer Filiale
Vorrätig in anderer Filiale?
</button>
<button
class="cta-continue"

View File

@@ -1,5 +1,5 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
import { ItemDTO as PrinterItemDTO } from '@swagger/print';
@@ -137,7 +137,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
private _purchaseOptionsModalService: PurchaseOptionsModalService,
private _availability: DomainAvailabilityService,
private _navigationService: ProductCatalogNavigationService,
private _environment: EnvironmentService
private _environment: EnvironmentService,
private _router: Router
) {}
ngOnInit() {
@@ -312,11 +313,41 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
async showPurchasingModal(selectedBranch?: BranchDTO) {
const item = await this.store.item$.pipe(first()).toPromise();
this._purchaseOptionsModalService.open({
type: 'add',
processId: this.applicationService.activatedProcessId,
items: [item],
});
this._purchaseOptionsModalService
.open({
type: 'add',
processId: this.applicationService.activatedProcessId,
items: [item],
})
.afterClosed$.subscribe((result) => {
if (result?.data === 'continue') {
this.navigateToShoppingCart();
console.log('continue');
} else if (result?.data === 'continue-shopping') {
this.navigateToResultList();
console.log('continue-shopping');
}
});
}
navigateToShoppingCart() {
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/cart/review`]);
}
async navigateToResultList() {
let crumbs = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog'])
.pipe(first())
.toPromise();
crumbs = crumbs.filter((crumb) => !crumb.tags?.includes('details'));
const crumb = crumbs[crumbs.length - 1];
if (crumb) {
this._router.navigate([crumb.path], { queryParams: crumb.params });
} else {
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/product`]);
}
}
scrollTop() {

View File

@@ -6,11 +6,11 @@ import { ArticleSearchComponent } from './article-search.component';
import { SearchResultsModule } from './search-results/search-results.module';
import { SearchMainModule } from './search-main/search-main.module';
import { SearchFilterModule } from './search-filter/search-filter.module';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { ArticleSearchService } from './article-search.store';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
@NgModule({
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule, ShellFilterOverlayModule],
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule, SharedFilterOverlayModule],
exports: [ArticleSearchComponent],
declarations: [ArticleSearchComponent],
providers: [ArticleSearchService],

View File

@@ -1,5 +1,5 @@
<a
class="page-search-result-item__item-card p-5 desktop:p-px-10 h-[212px] desktop:h-[181px] bg-white border border-solid rounded-card"
class="page-search-result-item__item-card p-5 desktop:p-px-10 h-[212px] desktop:h-[181px] bg-white border border-solid border-transparent rounded-card"
[routerLink]="detailsPath"
[routerLinkActive]="!isTablet ? 'active' : ''"
[queryParamsHandling]="!isTablet ? 'preserve' : ''"

View File

@@ -1,6 +1,5 @@
import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { EnvironmentService } from '@core/environment';
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
@@ -9,7 +8,7 @@ import { ItemDTO } from '@swagger/cat';
import { DateAdapter } from '@ui/common';
import { isEqual } from 'lodash';
import { combineLatest } from 'rxjs';
import { debounceTime, switchMap, map, shareReplay } from 'rxjs/operators';
import { debounceTime, switchMap, map, shareReplay, filter } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
import { ProductCatalogNavigationService } from '../../product-catalog-navigation.service';
@@ -116,9 +115,11 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
inStock$ = combineLatest([this.item$, this.selectedBranchId$, this.defaultBranch$]).pipe(
debounceTime(100),
filter(([item, branch, defaultBranch]) => !!item && !!defaultBranch),
switchMap(([item, branch, defaultBranch]) =>
this._stockService.getInStock$({ itemId: item.id, branchId: branch?.id ?? defaultBranch?.id })
)
),
shareReplay(1)
);
constructor(

View File

@@ -1,5 +1,5 @@
:host {
@apply box-border grid h-[100vh] max-h-[calc(100vh-364px)] desktop:max-h-[calc(100vh-300px)];
@apply box-border grid h-[100vh] max-h-[calc(100vh-364px)] desktop:max-h-[calc(100vh-245px)];
grid-template-rows: auto auto 1fr;
}

View File

@@ -1,5 +1,11 @@
<shared-breadcrumb class="my-4" [key]="activatedProcessId$ | async" [tags]="['catalog']">
<shared-branch-selector [branchType]="1" [value]="selectedBranch$ | async" (valueChange)="patchProcessData($event)">
<shared-branch-selector
[filterCurrentBranch]="!!auth.hasRole('Store')"
[orderBy]="auth.hasRole('Store') ? 'distance' : 'name'"
[branchType]="1"
[value]="selectedBranch$ | async"
(valueChange)="patchProcessData($event)"
>
</shared-branch-selector>
</shared-breadcrumb>

View File

@@ -1,5 +1,6 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
import { ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { EnvironmentService } from '@core/environment';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
@@ -34,14 +35,18 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
constructor(
public application: ApplicationService,
private _uiModal: UiModalService,
public auth: AuthService,
private _environmentService: EnvironmentService,
private _renderer: Renderer2
) {}
ngOnInit() {
// this.auth.getClaims();
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
this.application.setTitle('Artikelsuche');
}
ngAfterViewInit(): void {

View File

@@ -2,22 +2,13 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { ArticleDetailsModule } from './article-details/article-details.module';
import { ArticleSearchModule } from './article-search/article-search.module';
import { PageCatalogRoutingModule } from './page-catalog-routing.module';
import { PageCatalogComponent } from './page-catalog.component';
@NgModule({
imports: [
CommonModule,
PageCatalogRoutingModule,
ShellBreadcrumbModule,
ArticleSearchModule,
ArticleDetailsModule,
BreadcrumbModule,
BranchSelectorComponent,
],
imports: [CommonModule, PageCatalogRoutingModule, ArticleSearchModule, ArticleDetailsModule, BreadcrumbModule, BranchSelectorComponent],
exports: [],
declarations: [PageCatalogComponent],
})

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { EnvironmentService } from '@core/environment';
import { AppNavigationService } from 'apps/isa-app/src/app/app-navigation.service';
import { NavigationService } from '@core/navigation';
@Injectable({ providedIn: 'root' })
export class ProductCatalogNavigationService extends AppNavigationService {
export class ProductCatalogNavigationService extends NavigationService {
constructor(_router: Router, _environment: EnvironmentService) {
super(_router, _environment);
}

View File

@@ -271,7 +271,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
shoppingCart,
shoppingCartItems,
});
this.checkQuantityErrors(shoppingCartItems);
// this.checkQuantityErrors(shoppingCartItems);
},
(err) => {},
() => {}
@@ -282,15 +282,15 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
)
);
checkQuantityErrors(shoppingCartItems: ShoppingCartItemDTO[]) {
shoppingCartItems.forEach((item) => {
if (item.features?.orderType === 'Rücklage') {
this.setQuantityError(item, item.availability, item.quantity > item.availability?.inStock);
} else {
this.setQuantityError(item, item.availability, false);
}
});
}
// checkQuantityErrors(shoppingCartItems: ShoppingCartItemDTO[]) {
// shoppingCartItems.forEach((item) => {
// if (item.features?.orderType === 'Abholung') {
// this.setQuantityError(item, item.availability, item.quantity > item.availability?.inStock);
// } else {
// this.setQuantityError(item, item.availability, false);
// }
// });
// }
async updateBreadcrumb() {
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
@@ -485,7 +485,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
})
.toPromise();
this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
// this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
break;
case 'Abholung':
availability = await this.availabilityService
@@ -563,7 +563,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
},
})
.toPromise();
this.setQuantityError(shoppingCartItem, availability, false);
// this.setQuantityError(shoppingCartItem, availability, false);
} else if (availability) {
// Wenn das Ergebnis der Availability Abfrage keinen Preis zurückliefert (z.B. HFI Geschenkkarte), wird der Preis aus der
// Availability vor der Abfrage verwendet
@@ -594,16 +594,16 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
this.loadingOnQuantityChangeById$.next(undefined);
}
setQuantityError(item: ShoppingCartItemDTO, availability: AvailabilityDTO, error: boolean) {
const quantityErrors: { [key: string]: string } = this.quantityError$.value;
if (error) {
quantityErrors[item.product.catalogProductNumber] = `${availability.inStock} Exemplar(e) sofort lieferbar`;
this.quantityError$.next({ ...quantityErrors });
} else {
delete quantityErrors[item.product.catalogProductNumber];
this.quantityError$.next({ ...quantityErrors });
}
}
// setQuantityError(item: ShoppingCartItemDTO, availability: AvailabilityDTO, error: boolean) {
// const quantityErrors: { [key: string]: string } = this.quantityError$.value;
// if (error) {
// quantityErrors[item.product.catalogProductNumber] = `${availability.inStock} Exemplar(e) sofort lieferbar`;
// this.quantityError$.next({ ...quantityErrors });
// } else {
// delete quantityErrors[item.product.catalogProductNumber];
// this.quantityError$.next({ ...quantityErrors });
// }
// }
// Bei unbekannten Kunden und DIG Bestellung findet ein Vergleich der Preise statt
compareDeliveryAndCatalogPrice(availability: AvailabilityDTO, orderType: string, shoppingCartItemPrice: number) {

View File

@@ -1 +1 @@
<shell-breadcrumb [key]="breadcrumbKey$ | async" [includesTags]="['checkout']"></shell-breadcrumb> <router-outlet></router-outlet>
<shared-breadcrumb [key]="breadcrumbKey$ | async" [tags]="['checkout']"></shared-breadcrumb> <router-outlet></router-outlet>

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { map } from 'rxjs/operators';
@@ -8,8 +8,12 @@ import { map } from 'rxjs/operators';
styleUrls: ['page-checkout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageCheckoutComponent {
export class PageCheckoutComponent implements OnInit {
readonly breadcrumbKey$ = this.applicationService.activatedProcessId$.pipe(map((processId) => String(processId)));
constructor(private applicationService: ApplicationService) {}
ngOnInit() {
this.applicationService.setTitle('Warenkorb');
}
}

View File

@@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { CheckoutDummyModule } from './checkout-dummy/checkout-dummy.module';
import { CheckoutReviewModule } from './checkout-review/checkout-review.module';
import { CheckoutSummaryModule } from './checkout-summary/checkout-summary.module';
@@ -8,14 +8,7 @@ import { PageCheckoutRoutingModule } from './page-checkout-routing.module';
import { PageCheckoutComponent } from './page-checkout.component';
@NgModule({
imports: [
CommonModule,
CheckoutSummaryModule,
PageCheckoutRoutingModule,
CheckoutReviewModule,
CheckoutDummyModule,
ShellBreadcrumbModule,
],
imports: [CommonModule, CheckoutSummaryModule, PageCheckoutRoutingModule, CheckoutReviewModule, CheckoutDummyModule, BreadcrumbModule],
declarations: [PageCheckoutComponent],
exports: [],
})

View File

@@ -35,13 +35,10 @@ export class CustomerOrderEditComponent implements OnInit {
buyerNumber$ = this._activatedRoute.queryParams.pipe(map((params) => params.buyerNumber));
items$ = combineLatest([this.orderNumber$, this.compartmentCode$, this.archive$]).pipe(
switchMap(([orderNumber, compartmentCode, archive]) =>
// compartmentCode
// ? this._domainGoodsInService.getWarenausgabeItemByCompartment(compartmentCode, archive)
// : this._domainGoodsInService.getWarenausgabeItemByOrderNumber(orderNumber, archive)
this._domainGoodsInService.getOrderItemsByOrderNumber(orderNumber)
),
items$ = combineLatest([this.orderNumber$, this.compartmentCode$]).pipe(
switchMap(([orderNumber, compartmentCode]) => {
return this._domainGoodsInService.getOrderItemsByOrderNumber(compartmentCode ?? orderNumber);
}),
withLatestFrom(this.processingStatus$, this.buyerNumber$),
map(([response, processingStatus, buyerNumber]) => {
return response.result.filter(

View File

@@ -6,7 +6,7 @@ import { CustomerOrderSearchFilterComponent, OrderBranchIdInputComponent } from
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
import { CustomerOrderSearchStore } from './customer-order-search.store';
@@ -16,7 +16,7 @@ import { CustomerOrderSearchStore } from './customer-order-search.store';
UiIconModule,
RouterModule,
UiFilterNextModule,
ShellFilterOverlayModule,
SharedFilterOverlayModule,
UiSpinnerModule,
OrderBranchIdInputComponent,
],

View File

@@ -1,6 +1,7 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { BreadcrumbService } from '@core/breadcrumb';
import { EnvironmentService } from '@core/environment';
import { BranchDTO } from '@swagger/checkout';
@@ -30,7 +31,8 @@ export class CustomerOrderComponent implements OnInit {
private _activatedRoute: ActivatedRoute,
private _uiModal: UiModalService,
private _breadcrumb: BreadcrumbService,
private _environmentService: EnvironmentService
private _environmentService: EnvironmentService,
public auth: AuthService
) {}
ngOnInit(): void {
@@ -44,6 +46,8 @@ export class CustomerOrderComponent implements OnInit {
this.selectedBranch$ = this.application.activatedProcessId$.pipe(
switchMap((processId) => this.application.getSelectedBranch$(Number(processId)))
);
this.application.setTitle('Kundenbestellung');
}
async patchProcessData(selectedBranch: BranchDTO) {

View File

@@ -42,7 +42,7 @@ export class CreateB2BCustomerComponent extends AbstractCreateCustomer {
deviatingNameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
deviatingNameValidationFns: Record<string, ValidatorFn[]> = {
gender: [Validators.required],
gender: [Validators.required, Validators.min(1)],
firstName: [Validators.required],
lastName: [Validators.required],
};

View File

@@ -26,7 +26,7 @@ export class CreateGuestCustomerComponent extends AbstractCreateCustomer {
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required],
gender: [Validators.required, Validators.min(1)],
title: [],
};
@@ -45,7 +45,7 @@ export class CreateGuestCustomerComponent extends AbstractCreateCustomer {
deviatingNameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
deviatingNameValidationFns: Record<string, ValidatorFn[]> = {
gender: [Validators.required],
gender: [Validators.required, Validators.min(1)],
firstName: [Validators.required],
lastName: [Validators.required],
};

View File

@@ -36,7 +36,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required],
gender: [Validators.required, Validators.min(1)],
title: [],
};

View File

@@ -35,7 +35,7 @@ export class CreateStoreCustomerComponent extends AbstractCreateCustomer {
nameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
nameValidationFns: Record<string, ValidatorFn[]> = {
gender: [Validators.required],
gender: [Validators.required, Validators.min(1)],
firstName: [Validators.required],
lastName: [Validators.required],
};

View File

@@ -25,7 +25,7 @@ export class CreateWebshopCustomerComponent extends AbstractCreateCustomer {
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required],
gender: [Validators.required, Validators.min(1)],
title: [],
};

View File

@@ -29,7 +29,7 @@ export class UpdateP4MWebshopCustomerComponent extends AbstractCreateCustomer im
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required],
gender: [Validators.required, Validators.min(1)],
title: [],
};

View File

@@ -11,7 +11,7 @@ import { UiCommonModule } from '@ui/common';
import { CustomerResultCardComponent } from './search-results/customer-result-card/customer-result-card.component';
import { CustomerSearchFilterComponent } from './search-filter/search-filter.component';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiScrollContainerModule } from '@ui/scroll-container';
import { UiSpinnerModule } from '@ui/spinner';
@@ -23,7 +23,7 @@ import { UiSpinnerModule } from '@ui/spinner';
UiCommonModule,
UiIconModule,
ReactiveFormsModule,
ShellFilterOverlayModule,
SharedFilterOverlayModule,
UiFilterNextModule,
UiScrollContainerModule,
UiSpinnerModule,

View File

@@ -1,4 +1,4 @@
<shell-breadcrumb [key]="activatedProcessId$ | async" [includesTags]="['customer']"></shell-breadcrumb>
<shared-breadcrumb [key]="activatedProcessId$ | async" tags="customer"></shared-breadcrumb>
<div class="content-container">
<router-outlet></router-outlet>

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { map } from 'rxjs/operators';
@@ -9,8 +9,12 @@ import { map } from 'rxjs/operators';
providers: [],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageCustomerComponent {
export class PageCustomerComponent implements OnInit {
activatedProcessId$ = this.application.activatedProcessId$.pipe(map((p) => String(p)));
constructor(public application: ApplicationService) {}
ngOnInit() {
this.application.setTitle('Kundensuche');
}
}

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
import { PageCustomerComponent } from './page-customer.component';
import { PageCustomerRoutingModule } from './page-customer-routing.module';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { CustomerSearchModule } from './customer-search/customer-search.module';
import { CustomerDetailsModule } from './customer-details/customer-details.module';
import { UiInputModule } from '@ui/input';
@@ -14,7 +14,7 @@ import { CustomerModalModuleModule } from './modals';
imports: [
CommonModule,
PageCustomerRoutingModule,
ShellBreadcrumbModule,
BreadcrumbModule,
CustomerSearchModule,
CustomerDetailsModule,
UiInputModule,

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { DomainDashboardService } from '@domain/isa';
import { of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@@ -14,7 +15,12 @@ export class DashboardComponent implements OnInit {
catchError(() => of([]))
);
constructor(private readonly _domainIsaDashboardService: DomainDashboardService) {}
constructor(private readonly _domainIsaDashboardService: DomainDashboardService, private _app: ApplicationService) {}
ngOnInit(): void {}
ngOnInit(): void {
this._app.setSection('customer');
this._app.activateProcess(undefined);
this._app.setTitle('Dashboard');
}
}

View File

@@ -6,11 +6,11 @@ import { UiIconModule } from '@ui/icon';
import { GoodsInSearchFilterComponent } from './goods-in-search-filter';
import { RouterModule } from '@angular/router';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [CommonModule, RouterModule, UiIconModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
imports: [CommonModule, RouterModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
exports: [GoodsInSearchComponent],
declarations: [GoodsInSearchComponent, GoodsInSearchFilterComponent],
})

View File

@@ -1,3 +1,3 @@
<shell-breadcrumb [key]="goodsInKey"></shell-breadcrumb>
<shared-breadcrumb [key]="goodsInKey"></shared-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -1,4 +1,5 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
@Component({
@@ -10,7 +11,9 @@ import { Config } from '@core/config';
export class GoodsInComponent implements OnInit {
goodsInKey = this._config.get('process.ids.goodsIn');
constructor(private readonly _config: Config) {}
constructor(private readonly _config: Config, private _app: ApplicationService) {}
ngOnInit() {}
ngOnInit() {
this._app.setTitle('Abholfach');
}
}

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { GoodsInRoutingModule } from './good-in-routing.module';
import { GoodsInComponent } from './goods-in.component';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { CoreCommandModule } from '@core/command';
import {
AcceptedActionHandler,
@@ -42,11 +42,12 @@ import {
CreateReturnItemActionHandler,
PrintPriceDiffQrCodeLabelActionHandler,
} from '@domain/oms';
@NgModule({
declarations: [GoodsInComponent],
imports: [
GoodsInRoutingModule,
ShellBreadcrumbModule,
BreadcrumbModule,
CoreCommandModule.forChild([
AcceptedActionHandler,
ArrivedActionHandler,

View File

@@ -6,11 +6,11 @@ import { GoodsOutSearchFilterComponent } from './goods-out-search-filter';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [CommonModule, UiIconModule, RouterModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
imports: [CommonModule, UiIconModule, RouterModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
exports: [GoodsOutSearchComponent],
declarations: [GoodsOutSearchComponent, GoodsOutSearchFilterComponent],
})

View File

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

View File

@@ -1,5 +1,6 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { map } from 'rxjs/operators';
@Component({
@@ -8,8 +9,12 @@ import { map } from 'rxjs/operators';
styleUrls: ['goods-out.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsOutComponent {
export class GoodsOutComponent implements OnInit {
processId$ = this._activatedRoute.data.pipe(map((data) => String(data.processId)));
constructor(private _activatedRoute: ActivatedRoute) {}
constructor(private _activatedRoute: ActivatedRoute, private _app: ApplicationService) {}
ngOnInit() {
this._app.setTitle('Warenausgabe');
}
}

View File

@@ -41,7 +41,7 @@ import {
PrintPriceDiffQrCodeLabelActionHandler,
} from '@domain/oms';
import { CoreCommandModule } from '@core/command';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { GoodsInRoutingModule } from './good-out-routing.module';
@NgModule({
@@ -49,7 +49,7 @@ import { GoodsInRoutingModule } from './good-out-routing.module';
imports: [
CommonModule,
GoodsInRoutingModule,
ShellBreadcrumbModule,
BreadcrumbModule,
CoreCommandModule.forChild([
AcceptedActionHandler,
ArrivedActionHandler,

View File

@@ -24,7 +24,7 @@
</div>
</div>
<div class="package-details-list-item__product-details-2">
<div class="package-details-list-item__product-details-2" *ngIf="showStockInfos$ | async">
<div class="package-details-list-item__inventory-quantity py-2">
<div class="grow">
<ng-container *ngIf="showStockInfoTooltip$ | async">
@@ -52,7 +52,7 @@
Filialbestand
</div>
<div class="w-16">
<span class="filialbestand isa-label bg-accent-green px-2 font-bold">{{ inStock$ | async }} x</span>
<span class="filialbestand isa-label bg-accent-green px-2 font-bold">{{ inStock$ | async | max: 0 }} x</span>
</div>
</div>
<div class="package-details-list-item__package-quantity">

View File

@@ -1,6 +1,7 @@
import { fakeAsync } from '@angular/core/testing';
import { ProductImagePipe } from '@cdn/product-image';
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { MathPipesModule } from '@shared/pipes/math';
import { ArrivalStatus } from '@swagger/wws';
import { UiCommonModule } from '@ui/common';
import { UiTooltipModule } from '@ui/tooltip';
@@ -16,7 +17,7 @@ describe('PackageDetailsListItemComponent', () => {
const createComponent = createComponentFactory({
component: PackageDetailsListItemComponent,
imports: [UiCommonModule, UiTooltipModule],
imports: [UiCommonModule, UiTooltipModule, MathPipesModule],
declarations: [MockPipe(ProductImagePipe, (value) => `base/karma/assets/unit-test.svg#${value}`)],
});

View File

@@ -26,6 +26,8 @@ export class PackageDetailsListItemComponent implements OnChanges {
showStockInfoTooltip$ = this._store.arrivalStatus$.pipe(map((arrivalStatus) => arrivalStatus === 0));
showStockInfos$ = this._store.arrivalStatus$.pipe(map((arrivalStatus) => arrivalStatus !== 8));
constructor(private _store: PackageDetailsListStore) {}
ngOnChanges({ item }: SimpleChanges): void {

View File

@@ -5,7 +5,7 @@
class="mb-[2px]"
[item]="item"
*cdkVirtualFor="let item of items$ | async; trackBy: trackByItemId; let first = first"
[light]="!first"
[light]="!first || arrivalStatus == 8"
></page-package-details-list-item>
<div class="h-[100px]" *ngIf="hasActions"></div>
</cdk-virtual-scroll-viewport>

View File

@@ -36,13 +36,6 @@ describe('PackageDetailsListComponent', () => {
expect(spectator.component.items).toEqual(items);
});
it('should call store.setItems and loadStockInfos when items are set', () => {
const items = [{ id: '1' }, { id: '2' }] as PackageItemDTO[];
const setItemsSpy = spyOn(packageDetailsListStore, 'setItems');
spectator.component.items = items;
expect(setItemsSpy).toHaveBeenCalledWith(items);
});
it('should not call patchState and loadStockInfos when items are set to the same value', () => {
const items = [{ id: '1' }, { id: '2' }] as PackageItemDTO[];
spyOnProperty(spectator.component, 'items', 'get').and.returnValue(items);

View File

@@ -30,7 +30,6 @@ export class PackageDetailsListComponent implements OnChanges {
}
set items(value: PackageItemDTO[]) {
if (isEqual(this.items, value)) return;
this._store.setItems(value ?? []);
}
@Input()
@@ -48,10 +47,14 @@ export class PackageDetailsListComponent implements OnChanges {
constructor(private _store: PackageDetailsListStore) {}
ngOnChanges({ height }: SimpleChanges) {
ngOnChanges({ height, arrivalStatus, items }: SimpleChanges) {
if (height) {
this.checkViewportSize();
}
if (arrivalStatus && items) {
this._store.setItems(items.currentValue ?? [], arrivalStatus.currentValue !== 8);
}
}
checkViewportSize() {

View File

@@ -1,16 +1,25 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PackageInspectionPipesModule } from '../../pipes/package-inspection-pipes.module';
import { PackageDetailsListComponent } from './package-details-list.component';
import { PackageDetailsListItemComponent } from './package-details-list-item.component';
import { ProductImageModule } from '@cdn/product-image';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { UiTooltipModule } from '@ui/tooltip';
import { UiCommonModule } from '@ui/common';
import { PackageInspectionPipesModule } from '@shared/pipes/package-inspection';
import { MathPipesModule } from '@shared/pipes/math';
@NgModule({
imports: [CommonModule, ProductImageModule, PackageInspectionPipesModule, ScrollingModule, UiTooltipModule, UiCommonModule],
imports: [
CommonModule,
ProductImageModule,
PackageInspectionPipesModule,
ScrollingModule,
UiTooltipModule,
UiCommonModule,
MathPipesModule,
],
exports: [PackageDetailsListComponent, PackageDetailsListItemComponent],
declarations: [PackageDetailsListComponent, PackageDetailsListItemComponent],
})

View File

@@ -124,10 +124,13 @@ export class PackageDetailsListStore extends ComponentStore<PackageDetailsListSt
console.error(error);
};
setItems(items: PackageItemDTO[]) {
setItems(items: PackageItemDTO[], loadStockInfos: boolean) {
this.patchState({ items });
if (items.length === 0) return;
this.loadStockInfos(items);
if (loadStockInfos) {
this.loadStockInfos(items);
}
}
setArrivalStatus(arrivalStatus: ArrivalStatus) {

View File

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

View File

@@ -1,3 +0,0 @@
.page-package-list {
@apply bg-customer -mx-4 mt-10 border-t border-b-2 border-solid border-customer;
}

View File

@@ -21,7 +21,7 @@
korrekt?
</p>
</div>
<div class="bg-white" *ngSwitchCase="'Fehlt'">
<div class="bg-white" *ngSwitchCa se="'Fehlt'">
<p class="text-center text-xl py-10">
Prüfen Sie bitte stichprobenartig den Filialbestand <br />
des dargestellten Artikels. Ist der angezeigte Filialbestand <br />
@@ -39,10 +39,18 @@
{{ packageDetails.package.deliveryNoteNumber }}
</div>
<div class="col-span-3">
Filialstopp
<span class="font-bold ml-2">
{{ packageDetails.package.area }}
</span>
<ng-container *ngIf="packageDetails.package.arrivalStatus !== 8; else irrlauferTmplt">
Filialstopp
<span class="font-bold ml-2">
{{ packageDetails.package.area }}
</span>
</ng-container>
<ng-template #irrlauferTmplt>
Filiale
<span class="font-bold ml-2">
{{ packageDetails.package.misrouted | split: ':' | at: 1 }}
</span>
</ng-template>
</div>
<div class="text-right">
<ng-container *ngIf="(packageDetails.package.arrivalStatus | arrivalStatus) === 'Fehlt'">

View File

@@ -2,12 +2,14 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PackageDetailsComponent } from './package-details.component';
import { PackageInspectionPipesModule } from '../pipes';
import { PackageDetailsListModule } from '../components/package-details-list';
import { UiIconModule } from '@ui/icon';
import { UiTooltipModule } from '@ui/tooltip';
import { UiCommonModule } from '@ui/common';
import { ElementLifecycleModule } from '@shared/directives/element-lifecycle';
import { PackageInspectionPipesModule } from '@shared/pipes/package-inspection';
import { StringPipesModule } from '@shared/pipes/string';
import { ArrayPipesModule } from '@shared/pipes/array';
@NgModule({
imports: [
@@ -18,6 +20,8 @@ import { ElementLifecycleModule } from '@shared/directives/element-lifecycle';
UiTooltipModule,
UiCommonModule,
ElementLifecycleModule,
StringPipesModule,
ArrayPipesModule,
],
exports: [PackageDetailsComponent],
declarations: [PackageDetailsComponent],

View File

@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
@@ -13,10 +14,12 @@ export class PackageInspectionComponent implements OnInit {
return this._config.get('process.ids.packageInspection');
}
constructor(private _config: Config, private _breadcrumb: BreadcrumbService) {}
constructor(private _config: Config, private _breadcrumb: BreadcrumbService, private _app: ApplicationService) {}
ngOnInit(): void {
this.createBreadcrumbIfNotExists();
this._app.setTitle('Packstück Prüfung');
}
async createBreadcrumbIfNotExists(): Promise<void> {

View File

@@ -4,16 +4,15 @@ import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { CacheService } from '@core/cache';
import { Config } from '@core/config';
import { provideComponentStore } from '@ngrx/component-store';
import { ShellFilterOverlayComponent } from '@shell/filter-overlay';
import { ArrivalStatus, ListResponseArgsOfPackageDTO2 } from '@swagger/wws';
import { UiFilter, UiFilterComponent } from '@ui/filter';
import { isNumber, isString } from 'lodash';
import { UiFilter } from '@ui/filter';
import moment from 'moment';
import { combineLatest, Subject, Subscription } from 'rxjs';
import { filter, first, map } from 'rxjs/operators';
import { PackageListComponent } from '../components/package-list';
import { PackageResultCacheData } from './package-result-cache-data';
import { PackageResultComponentStore } from './package-result.component.store';
import { SharedFilterOverlayComponent } from '@shared/components/filter-overlay';
import { PackageListComponent } from '@shared/components/package-inspection/package-list';
@Component({
selector: 'page-package-result',
@@ -31,8 +30,8 @@ export class PackageResultComponent implements OnInit, AfterViewInit, OnDestroy
hint$ = new Subject<string>();
@ViewChild(ShellFilterOverlayComponent)
filterOverlay: ShellFilterOverlayComponent;
@ViewChild(SharedFilterOverlayComponent)
filterOverlay: SharedFilterOverlayComponent;
/**
* Zeigt die liste an, wenn entweder keine packages geladen werden oder wenn packages geladen wurden

View File

@@ -2,14 +2,14 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PackageResultComponent } from './package-result.component';
import { PackageListModule } from '../components/package-list';
import { UiIconModule } from '@ui/icon';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiFilterNextModule } from '@ui/filter';
import { UiSpinnerModule } from '@ui/spinner';
import { PackageListModule } from '@shared/components/package-inspection/package-list';
@NgModule({
imports: [CommonModule, PackageListModule, UiIconModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
imports: [CommonModule, PackageListModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
exports: [PackageResultComponent],
declarations: [PackageResultComponent],
})

View File

@@ -1,3 +0,0 @@
export * from './arrival-status-color-class.pipe';
export * from './arrival-status.pipe';
export * from './package-inspection-pipes.module';

View File

@@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { DomainRemissionService } from '@domain/remission';
import { ReturnDTO } from '@swagger/remi';
import { ReceiptDTO, ReturnDTO } from '@swagger/remi';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { UiSearchboxNextComponent } from '@ui/searchbox';
import { Subject } from 'rxjs';
@@ -52,10 +52,9 @@ export class CreateRemissionComponent implements OnInit {
const returnGroup = this._activatedRoute.snapshot.params?.returnGroup;
const supplier = await this.getSupplier();
const returnDTO = await this.createReturn(supplier.id, returnGroup);
const receipt = await this.createReceipt({ returnDTO, receiptNumber });
if (receipt) {
await this.navigateToFinishShippingDocument(returnDTO.id, receipt.id);
const returnReceipt = await this.createReturnAndReceipt(supplier.id, returnGroup, receiptNumber);
if (returnReceipt) {
await this.navigateToFinishShippingDocument(returnReceipt);
} else {
return undefined;
}
@@ -71,16 +70,25 @@ export class CreateRemissionComponent implements OnInit {
return suppliers.find((s) => s.id === this.supplierId);
}
async createReturn(supplierId: number, returnGroup: string) {
return await this._domainRemissionService.createReturn(supplierId, returnGroup);
async createReturnAndReceipt(
supplierId: number,
returnGroup: string,
receiptNumber: string
): Promise<{ returnDto: ReturnDTO; receiptDto: ReceiptDTO }> {
try {
const returnDto = await this._domainRemissionService.createReturn(supplierId, returnGroup);
const receiptDto = await this._domainRemissionService.createReceipt(returnDto, receiptNumber);
return {
returnDto,
receiptDto,
};
} catch (error) {
this._modal.error('Fehler beim Erstellen der Remission', error);
}
}
async createReceipt({ returnDTO, receiptNumber }: { returnDTO: ReturnDTO; receiptNumber?: string }) {
return await this._domainRemissionService.createReceipt(returnDTO, receiptNumber);
}
async navigateToFinishShippingDocument(returnId: number, receiptId: number) {
await this._router.navigate(['/filiale', 'remission', returnId, 'finish-shipping-document', receiptId], {
async navigateToFinishShippingDocument({ returnDto, receiptDto }: { returnDto: ReturnDTO; receiptDto: ReceiptDTO }) {
await this._router.navigate(['/filiale', 'remission', returnDto.id, 'finish-shipping-document', receiptDto.id], {
queryParams: { ...this._activatedRoute.snapshot.queryParams, supplier: this.supplierId, source: this.source },
});
}

View File

@@ -2,10 +2,11 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { DomainRemissionService } from '@domain/remission';
import { DialogModel, UiDialogModalComponent, UiModalService } from '@ui/modal';
import { UiSearchboxNextComponent } from '@ui/searchbox';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { first, takeUntil } from 'rxjs/operators';
@Component({
selector: 'page-finish-shipping-document',
@@ -35,12 +36,17 @@ export class FinishShippingDocumentComponent implements OnInit, OnDestroy {
return Number(this._activatedRoute?.snapshot?.params?.returnId);
}
get receiptId(): number {
return Number(this._activatedRoute?.snapshot?.params?.receiptId);
}
constructor(
private _activatedRoute: ActivatedRoute,
private _modal: UiModalService,
private _router: Router,
private _breadcrumb: BreadcrumbService,
private _config: Config
private _config: Config,
private _remissionService: DomainRemissionService
) {}
ngOnInit() {
@@ -53,6 +59,7 @@ export class FinishShippingDocumentComponent implements OnInit, OnDestroy {
}
search(query: string) {
this.hint$.next('');
if (!query) {
this.hint$.next('Ungültige Eingabe');
return;
@@ -77,6 +84,7 @@ export class FinishShippingDocumentComponent implements OnInit, OnDestroy {
modal.afterClosed$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result?.data === 'correct') {
await this.createReceiptAndAssignPackageNumber(query);
await this.navigateToRemissionList(query);
} else if (result?.data === 'rescan') {
this.searchboxComponent.clear();
@@ -90,14 +98,28 @@ export class FinishShippingDocumentComponent implements OnInit, OnDestroy {
});
}
async createReceiptAndAssignPackageNumber(packageNumber: string) {
const returnDTO = await this._remissionService.getReturn(this.returnId).pipe(first()).toPromise();
// const receipt = await this._remissionService.createReceipt(returnDTO, this.receiptId);
try {
return await this._remissionService.createReceiptAndAssignPackage({
returnId: returnDTO.id,
receiptId: this.receiptId,
packageNumber,
});
} catch (error) {
this._modal.error('Fehler beim Speichern der Wannennummer', error);
}
}
addBreadcrumbIfNotExists() {
const returnId = +this._activatedRoute.snapshot.params?.returnId;
const receiptId = +this._activatedRoute.snapshot.params?.receiptId;
const receiptNumber = this._activatedRoute.snapshot.params?.receiptNumber;
this._breadcrumb.addBreadcrumbIfNotExists({
key: this._config.get('process.ids.remission'),
name: 'Wannennummer scannen',
path: `/filiale/remission/${returnId}/finish-shipping-document/${receiptId}`,
path: `/filiale/remission/${returnId}/finish-shipping-document/${receiptNumber}`,
params: this._activatedRoute.snapshot.queryParams,
section: 'branch',
tags: ['remission', 'finish-shipping-document'],

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/core';
import { Component, ChangeDetectionStrategy, Input, OnDestroy, Host } from '@angular/core';
import { DomainRemissionService, RemissionListItem } from '@domain/remission';
import { RemissionPlacementType } from '@isa/remission';
import {
@@ -8,12 +8,13 @@ import {
ReturnItemDTO,
ReturnSuggestionDTO,
} from '@swagger/remi';
import { DialogModel, UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { mapFromReturnItemDTO, mapFromReturnSuggestionDTO } from 'apps/domain/remission/src/lib/mappings';
import { BehaviorSubject, Subject } from 'rxjs';
import { first, takeUntil } from 'rxjs/operators';
import { AddProductToShippingDocumentModalComponent } from '../../modals/add-product-to-shipping-document-modal/add-product-to-shipping-document-modal.component';
import { RemissionListComponentStore } from '../remission-list.component-store';
import { RemissionListComponent } from '../remission-list.component';
@Component({
selector: 'page-remission-list-item',
@@ -52,12 +53,13 @@ export class RemissionListItemComponent implements OnDestroy {
return !!this.returnDto && (returnItem?.descendantOf?.enabled || this.item?.dto?.impediment);
}
loading$ = new BehaviorSubject<boolean>(false);
loading$ = this._listComponent.remittingItem$.asObservable();
constructor(
private _modal: UiModalService,
private _remissionService: DomainRemissionService,
private _store: RemissionListComponentStore
private _store: RemissionListComponentStore,
@Host() private _listComponent: RemissionListComponent
) {}
ngOnDestroy() {
@@ -90,32 +92,14 @@ export class RemissionListItemComponent implements OnDestroy {
}
remit() {
const modal = this._modal.open({
content: UiDialogModalComponent,
title: 'Remittieren',
data: {
content: `Sie sind gerade dabei alle ${this.item.remissionQuantity} Exemplare von einem ${
this.item.placementType && this.item.placementType === 'Stapel' ? 'Stapel' : 'Leistungsplatz'
} zu\nremittieren. Sind wirklich alle Exemplare in ${
this.item.placementType && this.item.placementType === 'Stapel' ? 'dem Stapel' : 'der Leistung'
}?`,
handleCommand: false,
actions: [
{ label: 'Ja', selected: true, command: 'remit' },
{ label: 'Abbrechen', command: 'close' },
],
} as DialogModel,
});
modal.afterClosed$.pipe(takeUntil(this._onDestroy$)).subscribe((result) => {
if (result?.data === 'remit') {
this.addReturnItemOrSuggestion({ quantity: this.item.remissionQuantity });
}
});
this.addReturnItemOrSuggestion({ quantity: this.item.remissionQuantity });
}
async removeReturnItem() {
this.loading$.next(true);
if (this._listComponent.remittingItem$.value) {
return;
}
this._listComponent.remittingItem$.next(true);
try {
await this._remissionService.removeReturnItemFromList({ itemId: this.item?.dto?.id }).toPromise();
this._store.removeItem(this.item);
@@ -127,7 +111,7 @@ export class RemissionListItemComponent implements OnDestroy {
});
this.reload();
}
this.loading$.next(false);
this._listComponent.remittingItem$.next(false);
}
async addReturnItemOrSuggestion({
@@ -141,7 +125,10 @@ export class RemissionListItemComponent implements OnDestroy {
impedimentComment?: string;
remainingQuantity?: number;
}) {
this.loading$.next(true);
if (this._listComponent.remittingItem$.value) {
return;
}
this._listComponent.remittingItem$.next(true);
try {
let response: ValueTupleOfReceiptItemDTOAndReturnItemDTO | ValueTupleOfReceiptItemDTOAndReturnSuggestionDTO;
@@ -205,11 +192,14 @@ export class RemissionListItemComponent implements OnDestroy {
});
this.reload();
}
this.loading$.next(false);
this._listComponent.remittingItem$.next(false);
}
async returnImpediment() {
this.loading$.next(true);
if (this._listComponent.remittingItem$.value) {
return;
}
this._listComponent.remittingItem$.next(true);
let updatedDto: ReturnItemDTO | ReturnSuggestionDTO;
@@ -239,7 +229,7 @@ export class RemissionListItemComponent implements OnDestroy {
});
this.reload();
}
this.loading$.next(false);
this._listComponent.remittingItem$.next(false);
}
reload() {

View File

@@ -125,6 +125,8 @@ export class RemissionListComponent implements OnInit, OnDestroy {
showScrollArrow$ = new BehaviorSubject<boolean>(false);
remittingItem$ = new BehaviorSubject<boolean>(false);
constructor(
private readonly _remissionListStore: RemissionListComponentStore,
private readonly _remissionStore: RemissionComponentStore,

View File

@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { RemissionListComponent } from './remission-list.component';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiIconModule } from '@ui/icon';
import { RemissionFilterModule } from './remission-filter/remission-filter.module';
import { RemissionListItemModule } from './remission-list-item';
@@ -14,7 +14,7 @@ import { RemissionListItemLoadingModule } from './remission-list-item-loading/re
@NgModule({
imports: [
CommonModule,
ShellFilterOverlayModule,
SharedFilterOverlayModule,
UiIconModule,
RemissionFilterModule,
RemissionListItemModule,

View File

@@ -23,10 +23,18 @@ const routes: Routes = [
path: 'list',
component: RemissionListComponent,
},
{
path: ':returnId/list',
component: RemissionListComponent,
},
{
path: ':returnId/:packageNumber/list',
component: RemissionListComponent,
},
{
path: ':returnId/shipping-document',
component: ShippingDocumentDetailsComponent,
},
{
path: ':returnId/:packageNumber/shipping-document',
component: ShippingDocumentDetailsComponent,

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