mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Compare commits
34 Commits
3.1
...
Abholfach-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b10dd96d9 | ||
|
|
1126e4f0c1 | ||
|
|
e9f24a88d6 | ||
|
|
d48680c59e | ||
|
|
1d472ce3df | ||
|
|
1d19779dac | ||
|
|
90e671d285 | ||
|
|
33fb44f20a | ||
|
|
8723f7aa7e | ||
|
|
03815586f7 | ||
|
|
86a11ff07a | ||
|
|
41be8533dc | ||
|
|
186afbc828 | ||
|
|
c3561339a9 | ||
|
|
5312073184 | ||
|
|
4dfe3bfa11 | ||
|
|
9b7a1b1c21 | ||
|
|
a290d3b249 | ||
|
|
ad348af551 | ||
|
|
f1bdba5d10 | ||
|
|
c4134e7f99 | ||
|
|
b7a16f5d30 | ||
|
|
4105709286 | ||
|
|
0c3b322fbd | ||
|
|
12096754c7 | ||
|
|
453d921a99 | ||
|
|
bad05fd098 | ||
|
|
363daf1e35 | ||
|
|
e0cb0974cf | ||
|
|
c3d9274766 | ||
|
|
bc16b841fb | ||
|
|
d5dc4e053d | ||
|
|
3c6833988c | ||
|
|
28fb4ebb48 |
@@ -4,12 +4,11 @@
|
||||
|
||||
.scanner-container {
|
||||
width: 100vw;
|
||||
max-width: 95vw;
|
||||
max-height: calc(95vh - 120px);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.close-scanner {
|
||||
@apply block px-6 py-4 bg-white text-brand border-2 border-solid border-brand rounded-full text-lg font-bold mx-auto mt-4;
|
||||
@apply absolute bottom-12 left-[50%] -translate-x-[50%] block px-6 py-4 bg-white text-brand border-2 border-solid border-brand rounded-full text-lg font-bold mx-auto mt-4;
|
||||
}
|
||||
|
||||
@screen desktop {
|
||||
|
||||
@@ -9,12 +9,20 @@ import { Config } from '@core/config';
|
||||
import { ComponentPortal } from '@angular/cdk/portal';
|
||||
import { ScanditOverlayComponent } from './scandit-overlay.component';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { injectNetworkStatus$ } from 'apps/isa-app/src/app/services/network-status.service';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Injectable()
|
||||
export class ScanditScanAdapter implements ScanAdapter {
|
||||
readonly name = 'Scandit';
|
||||
|
||||
constructor(private readonly _config: Config, private _overlay: Overlay, private _environmentService: EnvironmentService) {}
|
||||
private $networkStatus = toSignal(injectNetworkStatus$());
|
||||
|
||||
constructor(
|
||||
private readonly _config: Config,
|
||||
private _overlay: Overlay,
|
||||
private _environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async init(): Promise<boolean> {
|
||||
if (this._environmentService.isTablet()) {
|
||||
@@ -30,6 +38,11 @@ export class ScanditScanAdapter implements ScanAdapter {
|
||||
|
||||
scan(): Observable<string> {
|
||||
return new Observable((observer) => {
|
||||
if (this.$networkStatus() === 'offline') {
|
||||
observer.error(new Error('No network connection'));
|
||||
return;
|
||||
}
|
||||
|
||||
const overlay = this.createOverlay();
|
||||
|
||||
const portal = this.createPortal();
|
||||
@@ -49,7 +62,7 @@ export class ScanditScanAdapter implements ScanAdapter {
|
||||
sub.add(
|
||||
overlay.backdropClick().subscribe(() => {
|
||||
complete();
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
ref.instance.onScan((code) => {
|
||||
|
||||
@@ -17,7 +17,10 @@ export class AuthService {
|
||||
|
||||
private _authConfig: AuthConfig;
|
||||
|
||||
constructor(private _config: Config, private readonly _oAuthService: OAuthService) {
|
||||
constructor(
|
||||
private _config: Config,
|
||||
private readonly _oAuthService: OAuthService,
|
||||
) {
|
||||
this._oAuthService.events?.subscribe((event) => {
|
||||
if (event.type === 'token_received') {
|
||||
console.log('SSO Token Expiration:', new Date(this._oAuthService.getAccessTokenExpiration()));
|
||||
@@ -45,6 +48,8 @@ export class AuthService {
|
||||
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
|
||||
} catch (error) {
|
||||
this.login();
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
this._initialized.next(true);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
BranchService,
|
||||
BuyerDTO,
|
||||
ChangeStockStatusCodeValues,
|
||||
HistoryDTO,
|
||||
NotificationChannel,
|
||||
@@ -29,7 +30,7 @@ export class DomainOmsService {
|
||||
private branchService: BranchService,
|
||||
private vatService: VATService,
|
||||
private stockStatusCodeService: StockStatusCodeService,
|
||||
private _orderCheckoutService: OrderCheckoutService
|
||||
private _orderCheckoutService: OrderCheckoutService,
|
||||
) {}
|
||||
|
||||
getOrderItemsByCustomerNumber(customerNumber: string, skip: number): Observable<OrderListItemDTO[]> {
|
||||
@@ -54,7 +55,7 @@ export class DomainOmsService {
|
||||
return this.receiptService
|
||||
.ReceiptGetReceiptsByOrderItemSubset({
|
||||
payload: {
|
||||
receiptType: (65 as unknown) as any,
|
||||
receiptType: 65 as unknown as any,
|
||||
ids: orderItemSubsetIds,
|
||||
eagerLoading: 1,
|
||||
},
|
||||
@@ -76,7 +77,7 @@ export class DomainOmsService {
|
||||
getStockStatusCodes({ supplierId, eagerLoading = 0 }: { supplierId: number; eagerLoading?: number }) {
|
||||
return this.stockStatusCodeService.StockStatusCodeGetStockStatusCodes({ supplierId, eagerLoading }).pipe(
|
||||
map((response) => response.result),
|
||||
shareReplay()
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,7 +121,7 @@ export class DomainOmsService {
|
||||
orderId: number,
|
||||
orderItemId: number,
|
||||
orderItemSubsetId: number,
|
||||
data: StatusValues
|
||||
data: StatusValues,
|
||||
): Observable<ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO> {
|
||||
return this.orderService
|
||||
.OrderChangeStatus({
|
||||
@@ -184,7 +185,7 @@ export class DomainOmsService {
|
||||
selected: order.notificationChannels,
|
||||
email: order.buyer?.communicationDetails?.email,
|
||||
mobile: order.buyer?.communicationDetails?.mobile,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,12 +206,47 @@ export class DomainOmsService {
|
||||
delete communicationDetails.mobile;
|
||||
}
|
||||
|
||||
return this.updateOrder({ orderId, notificationChannels: changes.selected, communicationDetails });
|
||||
}
|
||||
|
||||
updateOrder({
|
||||
orderId,
|
||||
notificationChannels,
|
||||
communicationDetails,
|
||||
firstName,
|
||||
lastName,
|
||||
organisation,
|
||||
}: {
|
||||
orderId: number;
|
||||
notificationChannels?: NotificationChannel;
|
||||
communicationDetails?: { email?: string; mobile?: string };
|
||||
lastName?: string;
|
||||
firstName?: string;
|
||||
organisation?: string;
|
||||
}) {
|
||||
const buyer: BuyerDTO = {};
|
||||
|
||||
if (!!communicationDetails) {
|
||||
buyer.communicationDetails = { ...communicationDetails };
|
||||
}
|
||||
|
||||
if (!!lastName || !!firstName) {
|
||||
buyer.lastName = lastName;
|
||||
buyer.firstName = firstName;
|
||||
}
|
||||
|
||||
if (!!organisation) {
|
||||
buyer.organisation = {
|
||||
name: organisation,
|
||||
};
|
||||
}
|
||||
|
||||
return this.orderService
|
||||
.OrderPatchOrder({
|
||||
orderId: orderId,
|
||||
order: {
|
||||
notificationChannels: changes.selected,
|
||||
buyer: { communicationDetails },
|
||||
notificationChannels,
|
||||
buyer,
|
||||
},
|
||||
})
|
||||
.pipe(map((res) => res.result));
|
||||
@@ -242,11 +278,14 @@ export class DomainOmsService {
|
||||
map((res) =>
|
||||
res.result
|
||||
.sort((a, b) => new Date(b.completed).getTime() - new Date(a.completed).getTime())
|
||||
.reduce((data, result) => {
|
||||
(data[result.name] = data[result.name] || []).push(new Date(result.completed));
|
||||
return data;
|
||||
}, {} as Record<string, Date[]>)
|
||||
)
|
||||
.reduce(
|
||||
(data, result) => {
|
||||
(data[result.name] = data[result.name] || []).push(new Date(result.completed));
|
||||
return data;
|
||||
},
|
||||
{} as Record<string, Date[]>,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { inject, isDevMode, NgModule } from '@angular/core';
|
||||
import { isDevMode, NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import {
|
||||
CanActivateCartGuard,
|
||||
@@ -8,8 +8,6 @@ import {
|
||||
CanActivateCustomerOrdersWithProcessIdGuard,
|
||||
CanActivateCustomerWithProcessIdGuard,
|
||||
CanActivateGoodsInGuard,
|
||||
CanActivateGoodsOutGuard,
|
||||
CanActivateGoodsOutWithProcessIdGuard,
|
||||
CanActivateProductGuard,
|
||||
CanActivateProductWithProcessIdGuard,
|
||||
CanActivateRemissionGuard,
|
||||
@@ -22,7 +20,6 @@ import { MainComponent } from './main.component';
|
||||
import { PreviewComponent } from './preview';
|
||||
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ProcessIdGuard } from './guards/process-id.guard';
|
||||
import { ActivateProcessIdGuard, ActivateProcessIdWithConfigKeyGuard } from './guards/activate-process-id.guard';
|
||||
|
||||
|
||||
@@ -1 +1,28 @@
|
||||
@if ($offlineBannerVisible()) {
|
||||
<div [@fadeInOut] class="bg-brand text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
|
||||
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
|
||||
<div>
|
||||
<ng-icon name="matWifiOff"></ng-icon>
|
||||
</div>
|
||||
|
||||
<div>Sie sind offline, keine Verbindung zum Netzwerk.</div>
|
||||
</h3>
|
||||
<p>Bereits geladene Ihnalte werden angezeigt, Interaktionen sind aktuell nicht möglich.</p>
|
||||
</div>
|
||||
}
|
||||
@if ($onlineBannerVisible()) {
|
||||
<div [@fadeInOut] class="bg-green-500 text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
|
||||
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
|
||||
<div>
|
||||
<ng-icon name="matWifi"></ng-icon>
|
||||
</div>
|
||||
|
||||
<div>Sie sind wieder online.</div>
|
||||
</h3>
|
||||
<button class="fixed top-2 right-4 text-3xl w-12 h-12" type="button" (click)="$onlineBannerVisible.set(false)">
|
||||
<ng-icon name="matClose"></ng-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Component, HostListener, Inject, OnInit, Renderer2 } from '@angular/core';
|
||||
import { Component, effect, HostListener, Inject, OnInit, Renderer2, signal, untracked } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { ApplicationService } from '@core/application';
|
||||
@@ -12,17 +12,60 @@ import { IsaLogProvider } from './providers';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { injectOnline$ } from './services/network-status.service';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
animations: [
|
||||
trigger('fadeInOut', [
|
||||
transition(':enter', [
|
||||
// :enter wird ausgelöst, wenn das Element zum DOM hinzugefügt wird
|
||||
style({ opacity: 0, transform: 'translateY(-100%)' }),
|
||||
animate('300ms', style({ opacity: 1, transform: 'translateY(0)' })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
// :leave wird ausgelöst, wenn das Element aus dem DOM entfernt wird
|
||||
animate('300ms', style({ opacity: 0, transform: 'translateY(-100%)' })),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
$online = toSignal(injectOnline$());
|
||||
|
||||
$offlineBannerVisible = signal(false);
|
||||
|
||||
$onlineBannerVisible = signal(false);
|
||||
|
||||
private onlineBannerDismissTimeout: any;
|
||||
|
||||
onlineEffects = effect(() => {
|
||||
const online = this.$online();
|
||||
const offlineBannerVisible = this.$offlineBannerVisible();
|
||||
|
||||
untracked(() => {
|
||||
this.$offlineBannerVisible.set(!online);
|
||||
|
||||
if (!online) {
|
||||
this.$onlineBannerVisible.set(false);
|
||||
clearTimeout(this.onlineBannerDismissTimeout);
|
||||
}
|
||||
|
||||
if (offlineBannerVisible && online) {
|
||||
this.$onlineBannerVisible.set(true);
|
||||
this.onlineBannerDismissTimeout = setTimeout(() => this.$onlineBannerVisible.set(false), 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
private _checkForUpdates: number = this._config.get('checkForUpdates');
|
||||
|
||||
get checkForUpdates(): number {
|
||||
return this._checkForUpdates;
|
||||
return this._checkForUpdates ?? 60 * 60 * 1000; // default 1 hour
|
||||
}
|
||||
|
||||
// For Unit Testing
|
||||
|
||||
@@ -37,6 +37,10 @@ import { NativeContainerService } from 'native-container';
|
||||
import { ShellModule } from '@shared/shell';
|
||||
import { MainComponent } from './main.component';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { NgIconsModule } from '@ng-icons/core';
|
||||
import { matClose, matWifi, matWifiOff } from '@ng-icons/material-icons/baseline';
|
||||
import { NetworkStatusService } from './services/network-status.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
@@ -46,26 +50,68 @@ export function _appInitializerFactory(
|
||||
auth: AuthService,
|
||||
injector: Injector,
|
||||
scanAdapter: ScanAdapterService,
|
||||
nativeContainer: NativeContainerService
|
||||
nativeContainer: NativeContainerService,
|
||||
networkStatus: NetworkStatusService,
|
||||
) {
|
||||
return async () => {
|
||||
const statusElement = document.querySelector('#init-status');
|
||||
statusElement.innerHTML = 'Konfigurationen werden geladen...';
|
||||
await config.init();
|
||||
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
|
||||
await auth.init();
|
||||
const laoderElement = document.querySelector('#init-loader');
|
||||
|
||||
if (auth.isAuthenticated()) {
|
||||
statusElement.innerHTML = 'App wird initialisiert...';
|
||||
const state = injector.get(RootStateService);
|
||||
await state.init();
|
||||
try {
|
||||
let online = false;
|
||||
|
||||
while (!online) {
|
||||
online = await firstValueFrom(networkStatus.online$);
|
||||
|
||||
if (!online) {
|
||||
statusElement.innerHTML =
|
||||
'<b>Warte auf Netzwerkverbindung (WLAN)</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br>Sobald eine Netzwerkverbindung besteht, wird die App automatisch neu geladen.';
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
|
||||
statusElement.innerHTML = 'Konfigurationen werden geladen...';
|
||||
await config.init();
|
||||
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
|
||||
await auth.init();
|
||||
|
||||
if (auth.isAuthenticated()) {
|
||||
statusElement.innerHTML = 'App wird initialisiert...';
|
||||
const state = injector.get(RootStateService);
|
||||
await state.init();
|
||||
}
|
||||
|
||||
statusElement.innerHTML = 'Native Container wird initialisiert...';
|
||||
await nativeContainer.init();
|
||||
|
||||
statusElement.innerHTML = 'Scanner wird initialisiert...';
|
||||
await scanAdapter.init();
|
||||
} catch (error) {
|
||||
laoderElement.remove();
|
||||
statusElement.classList.add('text-xl');
|
||||
statusElement.innerHTML = '<b>Fehler bei der Initialisierung</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br><br>';
|
||||
|
||||
const reload = document.createElement('button');
|
||||
reload.classList.add('bg-brand', 'text-white', 'p-2', 'rounded', 'cursor-pointer');
|
||||
reload.innerHTML = 'App neu laden';
|
||||
reload.onclick = () => window.location.reload();
|
||||
statusElement.appendChild(reload);
|
||||
|
||||
const preLabel = document.createElement('div');
|
||||
preLabel.classList.add('mt-12');
|
||||
preLabel.innerHTML = 'Fehlermeldung:';
|
||||
|
||||
statusElement.appendChild(preLabel);
|
||||
|
||||
const pre = document.createElement('pre');
|
||||
pre.classList.add('mt-4', 'text-wrap');
|
||||
pre.innerHTML = error.message;
|
||||
|
||||
statusElement.appendChild(pre);
|
||||
|
||||
console.error('Error during app initialization', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
statusElement.innerHTML = 'Native Container wird initialisiert...';
|
||||
await nativeContainer.init();
|
||||
|
||||
statusElement.innerHTML = 'Scanner wird initialisiert...';
|
||||
await scanAdapter.init();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,13 +153,14 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
ScanditScanAdapterModule.forRoot(),
|
||||
PlatformModule,
|
||||
IconModule.forRoot(),
|
||||
NgIconsModule.withIcons({ matWifiOff, matClose, matWifi }),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: _appInitializerFactory,
|
||||
multi: true,
|
||||
deps: [Config, AuthService, Injector, ScanAdapterService, NativeContainerService],
|
||||
deps: [Config, AuthService, Injector, ScanAdapterService, NativeContainerService, NetworkStatusService],
|
||||
},
|
||||
{
|
||||
provide: NOTIFICATIONS_HUB_OPTIONS,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { ScanAdapterService } from '@adapter/scan';
|
||||
import { AuthService as IsaAuthService } from '@swagger/isa';
|
||||
@@ -9,11 +9,12 @@ import { EnvironmentService } from '@core/environment';
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class IsAuthenticatedGuard {
|
||||
constructor(
|
||||
private _router: Router,
|
||||
private _authService: AuthService,
|
||||
private _scanService: ScanAdapterService,
|
||||
private _isaAuthService: IsaAuthService,
|
||||
private _modal: UiModalService,
|
||||
private _environmentService: EnvironmentService
|
||||
private _environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
|
||||
@@ -2,30 +2,31 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
|
||||
import { NEVER, Observable, throwError } from 'rxjs';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { catchError, mergeMap, tap } from 'rxjs/operators';
|
||||
import { catchError, filter, mergeMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { IsaLogProvider } from '../providers';
|
||||
import { LogLevel } from '@core/logger';
|
||||
import { injectOnline$ } from '../services/network-status.service';
|
||||
|
||||
@Injectable()
|
||||
export class HttpErrorInterceptor implements HttpInterceptor {
|
||||
constructor(private _modal: UiModalService, private _auth: AuthService, private _isaLogProvider: IsaLogProvider) {}
|
||||
readonly offline$ = injectOnline$().pipe(filter((online) => !online));
|
||||
|
||||
constructor(
|
||||
private _modal: UiModalService,
|
||||
private _auth: AuthService,
|
||||
private _isaLogProvider: IsaLogProvider,
|
||||
) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(req).pipe(catchError((error: HttpErrorResponse, caught: any) => this.handleError(error)));
|
||||
return next.handle(req).pipe(
|
||||
takeUntil(this.offline$),
|
||||
catchError((error: HttpErrorResponse, caught: any) => this.handleError(error)),
|
||||
);
|
||||
}
|
||||
|
||||
handleError(error: HttpErrorResponse): Observable<any> {
|
||||
if (error.status === 0) {
|
||||
return this._modal
|
||||
.open({
|
||||
content: UiMessageModalComponent,
|
||||
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
|
||||
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
|
||||
})
|
||||
.afterClosed$.pipe(mergeMap(() => throwError(error)));
|
||||
} else if (error.status === 401) {
|
||||
console.log('401', error);
|
||||
if (error.status === 401) {
|
||||
return this._modal
|
||||
.open({
|
||||
content: UiMessageModalComponent,
|
||||
@@ -36,11 +37,13 @@ export class HttpErrorInterceptor implements HttpInterceptor {
|
||||
tap(() => {
|
||||
this._auth.login();
|
||||
}),
|
||||
mergeMap(() => NEVER)
|
||||
mergeMap(() => NEVER),
|
||||
);
|
||||
}
|
||||
|
||||
this._isaLogProvider.log(LogLevel.ERROR, 'Http Error', error);
|
||||
if (!error.url.endsWith('/isa/logging')) {
|
||||
this._isaLogProvider.log(LogLevel.ERROR, 'Http Error', error);
|
||||
}
|
||||
|
||||
return throwError(error);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ import { LogLevel } from '@core/logger';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class IsaErrorHandler implements ErrorHandler {
|
||||
constructor(private _modal: UiModalService, private _authService: AuthService, private _isaLogProvider: IsaLogProvider) {}
|
||||
constructor(
|
||||
private _modal: UiModalService,
|
||||
private _authService: AuthService,
|
||||
private _isaLogProvider: IsaLogProvider,
|
||||
) {}
|
||||
|
||||
async handleError(error: any): Promise<void> {
|
||||
console.error(error);
|
||||
@@ -37,13 +41,13 @@ export class IsaErrorHandler implements ErrorHandler {
|
||||
|
||||
this._isaLogProvider.log(LogLevel.ERROR, 'Client Error', error);
|
||||
|
||||
this._modal.open({
|
||||
content: UiErrorModalComponent,
|
||||
title:
|
||||
!navigator.onLine || (error instanceof HttpErrorResponse && error?.status === 0)
|
||||
? 'Sie sind offline, keine Verbindung zum Netzwerk'
|
||||
: 'Unbekannter Fehler',
|
||||
data: error,
|
||||
});
|
||||
// this._modal.open({
|
||||
// content: UiErrorModalComponent,
|
||||
// title:
|
||||
// !navigator.onLine || (error instanceof HttpErrorResponse && error?.status === 0)
|
||||
// ? 'Sie sind offline, keine Verbindung zum Netzwerk'
|
||||
// : 'Unbekannter Fehler',
|
||||
// data: error,
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
25
apps/isa-app/src/app/services/network-status.service.ts
Normal file
25
apps/isa-app/src/app/services/network-status.service.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class NetworkStatusService {
|
||||
online$ = new Observable<boolean>((subscriber) => {
|
||||
const handler = () => subscriber.next(navigator.onLine);
|
||||
|
||||
window.addEventListener('online', handler);
|
||||
window.addEventListener('offline', handler);
|
||||
|
||||
handler();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', handler);
|
||||
window.removeEventListener('offline', handler);
|
||||
};
|
||||
});
|
||||
|
||||
status$ = this.online$.pipe(map((online) => (online ? 'online' : 'offline')));
|
||||
}
|
||||
|
||||
export const injectNetworkStatus$ = () => inject(NetworkStatusService).status$;
|
||||
|
||||
export const injectOnline$ = () => inject(NetworkStatusService).online$;
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -15,7 +15,7 @@
|
||||
<app-root>
|
||||
<div class="grid place-items-center h-screen">
|
||||
<div class="grid grid-flow-row gap-4 items-center justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<div id="init-loader" class="flex flex-col items-center">
|
||||
<img class="animate-spin" src="/assets/images/spinner.svg" alt="spinner animation" />
|
||||
</div>
|
||||
<div id="init-status" class="text-center">App wird geladen</div>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
}
|
||||
|
||||
.page-price-update-item__item-card {
|
||||
@apply grid grid-flow-col;
|
||||
grid-template-columns: 63px auto minmax(230px, auto);
|
||||
@apply grid grid-flow-col gap-2;
|
||||
grid-template-columns: 63px 1fr 14rem;
|
||||
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
|
||||
}
|
||||
|
||||
@@ -20,5 +20,11 @@
|
||||
|
||||
.page-price-update-item__item-addition {
|
||||
@apply grid grid-flow-row justify-items-end;
|
||||
grid-template-rows: 27px 27px 41px 52px auto;
|
||||
grid-template-rows: auto auto 2.56rem 3.25rem auto;
|
||||
}
|
||||
|
||||
.page-price-update-item__item-product-group-details,
|
||||
.page-price-update-item__item-title {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search({filter, clear: true})"
|
||||
(search)="search({ filter, clear: true })"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
@@ -20,7 +20,7 @@
|
||||
class="page-search-results__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
[routerLink]="filterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
[queryParams]="filterQueryParams"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
@@ -32,8 +32,7 @@
|
||||
class="page-search-results__items-count inline-flex flex-row items-center pr-5 text-p3"
|
||||
[class.mb-4]="primaryOutletActive$ | async"
|
||||
>
|
||||
{{ hits ??
|
||||
0 }}
|
||||
{{ hits ?? 0 }}
|
||||
Titel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
selectedItems$ = combineLatest([this.results$, this.selectedItemIds$]).pipe(
|
||||
map(([items, selectedItemIds]) => {
|
||||
return items?.filter((item) => selectedItemIds?.find((selectedItemId) => item.id === selectedItemId));
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
getProcessId(): number {
|
||||
@@ -84,7 +84,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
map(([filter, defaultFilter]) => {
|
||||
const filterQueryParams = filter?.getQueryParams();
|
||||
return !isEqual(this.resetQueryParamsQueryAndOrderBy(filterQueryParams), Filter.create(defaultFilter).getQueryParams());
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
get filterRoute() {
|
||||
@@ -95,6 +95,10 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}).path;
|
||||
}
|
||||
|
||||
get filterQueryParams() {
|
||||
return this.cleanupQueryParams(this.searchService?.filter?.getQueryParams());
|
||||
}
|
||||
|
||||
get primaryOutletActive$() {
|
||||
return this._environment.matchDesktop$.pipe(map((matches) => matches && this.route.outlet === 'primary'));
|
||||
}
|
||||
@@ -116,7 +120,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _router: Router
|
||||
private _router: Router,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -125,8 +129,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
.pipe(
|
||||
debounceTime(0),
|
||||
switchMap(([processId, queryParams]) =>
|
||||
this.application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, queryParams, selectedBranch })))
|
||||
)
|
||||
this.application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, queryParams, selectedBranch }))),
|
||||
),
|
||||
)
|
||||
.subscribe(async ({ processId, queryParams, selectedBranch }) => {
|
||||
const processChanged = processId !== this.searchService.processId;
|
||||
@@ -138,7 +142,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
this.cacheCurrentData(
|
||||
this.searchService.processId,
|
||||
this.searchService.filter.getQueryParams(),
|
||||
this.searchService?.selectedBranch?.id
|
||||
this.searchService?.selectedBranch?.id,
|
||||
);
|
||||
this.updateBreadcrumbs(this.searchService.processId, this.searchService.filter.getQueryParams());
|
||||
}
|
||||
@@ -190,7 +194,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
if (this.route?.outlet === 'primary') {
|
||||
await this.removeDetailsBreadcrumb(processId);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
@@ -244,7 +248,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
@@ -258,7 +262,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -337,7 +341,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
async updateBreadcrumbs(
|
||||
processId: number = this.searchService.processId,
|
||||
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams()
|
||||
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams(),
|
||||
) {
|
||||
const selected_item_ids = this.searchService?.selectedItemIds?.toString();
|
||||
|
||||
@@ -559,7 +563,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
this.unselectAll();
|
||||
}
|
||||
this.loading$.next(false);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[order]="order$ | async"
|
||||
[selected]="true"
|
||||
(historyClick)="navigateToHistoryPage($event)"
|
||||
(specialCommentChanged)="updateCustomerOrderResults()"
|
||||
(specialCommentChanged)="onSpecialCommentChange()"
|
||||
></page-customer-order-details-item>
|
||||
<page-customer-order-details-tags *ngIf="showTagsComponent$ | async"></page-customer-order-details-tags>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
OnInit,
|
||||
QueryList,
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRoute, NavigationError, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { OrderItemsContext } from '@domain/oms';
|
||||
@@ -84,7 +84,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
const first = items?.find((_) => true);
|
||||
const hasArrivedAction = first?.actions?.some((a) => a.command?.includes('ARRIVED'));
|
||||
return hasArrivedAction && !fetching;
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
actionsDisabled$ = combineLatest([
|
||||
@@ -94,7 +94,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
]).pipe(map(([disabled, partial, orderItems]) => disabled || (partial && orderItems.length > 1)));
|
||||
|
||||
addToPreviousCompartmentActionDisabled$ = combineLatest([this.compartmentInfo$, this.changeActionDisabled$, this.fetching$]).pipe(
|
||||
map(([compartmentInfo, changeActionDisabled, fetching]) => (!!compartmentInfo || changeActionDisabled) && fetching)
|
||||
map(([compartmentInfo, changeActionDisabled, fetching]) => (!!compartmentInfo || changeActionDisabled) && fetching),
|
||||
);
|
||||
|
||||
constructor(
|
||||
@@ -105,7 +105,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
private _router: Router,
|
||||
private _uiModal: UiModalService,
|
||||
private _environment: EnvironmentService,
|
||||
private _commandService: CommandService
|
||||
private _commandService: CommandService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -113,7 +113,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
this._activatedRoute.queryParams.subscribe((params) => {
|
||||
const buyerNumber: string = decodeURIComponent(params.buyerNumber ?? '');
|
||||
this._store.patchState({ buyerNumber });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
@@ -136,7 +136,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
}
|
||||
|
||||
await this.removeDetailsCrumbs();
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
@@ -153,7 +153,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
this._store.patchState({ orderId });
|
||||
this._store.loadOrder();
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.removeBreadcrumbs();
|
||||
@@ -172,7 +172,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
this.items$.subscribe((_) => {
|
||||
this.customerOrderDetailsTags?.writeValue(this.compartmentInfo);
|
||||
this.customerOrderDetailsTags?.registerOnChange((compartmentInfo) => (this._store.compartmentInfo = compartmentInfo));
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
|
||||
getItemQuantityMap(): Map<number, number> {
|
||||
return new Map(
|
||||
this.customerOrderDetailsItemComponents.toArray().map((component) => [component.orderItem.orderItemSubsetId, component.quantity])
|
||||
this.customerOrderDetailsItemComponents.toArray().map((component) => [component.orderItem.orderItemSubsetId, component.quantity]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
|
||||
async handleAction(
|
||||
action: KeyValueDTOOfStringAndString,
|
||||
{ compartmentCode, compartmentInfo }: { compartmentCode?: string; compartmentInfo?: string } = {}
|
||||
{ compartmentCode, compartmentInfo }: { compartmentCode?: string; compartmentInfo?: string } = {},
|
||||
) {
|
||||
if (action.command.includes('FETCHED_PARTIAL')) {
|
||||
this._store.patchState({ fetchPartial: true });
|
||||
@@ -312,7 +312,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
(itemComponent) =>
|
||||
new Promise<ReceiptDTO[]>((resolve) => {
|
||||
itemComponent.loadReceipts((r) => resolve(r));
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
receipts = await Promise.all(receiptsPromise).then((r) => r.reduce((acc, val) => acc.concat(val), []));
|
||||
@@ -352,10 +352,14 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
await this.actionHandled({ orderItemsContext: commandData, command: action.command, navigation: navigateTo });
|
||||
this._store.updateOrderItems(commandData.items);
|
||||
} catch (error) {
|
||||
this._uiModal.open({
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
if (error instanceof NavigationError) {
|
||||
await this.handleRouterMatchNoRoutesError(); // Refresh Result list but stay on same page
|
||||
} else {
|
||||
this._uiModal.open({
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -374,24 +378,36 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
|
||||
if (handler.navigation === 'main') {
|
||||
await this._navigationService.getCustomerOrdersBasePath(this.processId).navigate();
|
||||
} else {
|
||||
const item: OrderItemListItemDTO = handler.orderItemsContext.items.find((_) => true);
|
||||
await this._router.navigate(this.getDetailsPath(item), {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, buyerNumber: item.buyerNumber },
|
||||
});
|
||||
await this.updateCustomerOrderResults();
|
||||
const item: OrderItemListItemDTO = handler?.orderItemsContext?.items?.find((_) => true);
|
||||
if (!!item) {
|
||||
await this._router.navigate(this.getDetailsPath(item), {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, buyerNumber: item.buyerNumber },
|
||||
});
|
||||
}
|
||||
if (this.isDesktop) {
|
||||
await this.refreshResults();
|
||||
}
|
||||
await this.removeDetailsCrumbs();
|
||||
this._store.loadItems();
|
||||
}
|
||||
}
|
||||
|
||||
async updateCustomerOrderResults() {
|
||||
async handleRouterMatchNoRoutesError() {
|
||||
await this.refreshResults();
|
||||
}
|
||||
|
||||
async onSpecialCommentChange() {
|
||||
if (this.isDesktop) {
|
||||
await this._router.navigate([], {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, updateResults: true },
|
||||
});
|
||||
await this.refreshResults();
|
||||
}
|
||||
}
|
||||
|
||||
async refreshResults() {
|
||||
await this._router.navigate([], {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, updateResults: true },
|
||||
});
|
||||
}
|
||||
|
||||
async arrivedActionNavigation(): Promise<'main'> {
|
||||
const detailsCrumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$('customer-order', ['customer-order', 'details'])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, Host } from '@angular/core';
|
||||
import { CustomerSearchStore } from '../../store';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { debounceTime, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { Observable, Subject, combineLatest } from 'rxjs';
|
||||
import { AssignedPayerDTO, CustomerDTO, ListResponseArgsOfAssignedPayerDTO } from '@swagger/crm';
|
||||
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
|
||||
@@ -27,8 +27,10 @@ interface DetailsMainViewBillingAddressesComponentState {
|
||||
standalone: true,
|
||||
imports: [NgIf, NgFor, AsyncPipe, CustomerPipesModule, RouterLink],
|
||||
})
|
||||
export class DetailsMainViewBillingAddressesComponent extends ComponentStore<DetailsMainViewBillingAddressesComponentState>
|
||||
implements OnInit, OnDestroy {
|
||||
export class DetailsMainViewBillingAddressesComponent
|
||||
extends ComponentStore<DetailsMainViewBillingAddressesComponentState>
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
assignedPayers$ = this.select((state) => state.assignedPayers);
|
||||
|
||||
selectedPayer$ = this.select((state) => state.selectedPayer);
|
||||
@@ -36,7 +38,7 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
isNotBusinessKonto$ = this._store.isBusinessKonto$.pipe(map((isBusinessKonto) => !isBusinessKonto));
|
||||
|
||||
showCustomerAddress$ = combineLatest([this._store.isBusinessKonto$, this._store.isMitarbeiter$, this._store.isKundenkarte$]).pipe(
|
||||
map(([isBusinessKonto, isMitarbeiter, isKundenkarte]) => isBusinessKonto || isMitarbeiter || isKundenkarte)
|
||||
map(([isBusinessKonto, isMitarbeiter, isKundenkarte]) => isBusinessKonto || isMitarbeiter || isKundenkarte),
|
||||
);
|
||||
|
||||
get showCustomerAddress() {
|
||||
@@ -47,12 +49,8 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
this._store.isOnlinekonto$,
|
||||
this._store.isOnlineKontoMitKundenkarte$,
|
||||
this._store.isKundenkarte$,
|
||||
this._store.isMitarbeiter$,
|
||||
]).pipe(
|
||||
map(
|
||||
([isOnlinekonto, isOnlineKontoMitKundenkarte, isKundenkarte, isMitarbeiter]) =>
|
||||
isOnlinekonto || isOnlineKontoMitKundenkarte || isKundenkarte || isMitarbeiter
|
||||
)
|
||||
map(([isOnlinekonto, isOnlineKontoMitKundenkarte, isKundenkarte]) => isOnlinekonto || isOnlineKontoMitKundenkarte || isKundenkarte),
|
||||
);
|
||||
|
||||
canEditAddress$ = combineLatest([this._store.isKundenkarte$]).pipe(map(([isKundenkarte]) => isKundenkarte));
|
||||
@@ -62,13 +60,13 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
editRoute$ = combineLatest([this._store.processId$, this._store.customerId$, this._store.isBusinessKonto$]).pipe(
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b }))
|
||||
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b })),
|
||||
);
|
||||
|
||||
addBillingAddressRoute$ = combineLatest([this.canAddNewAddress$, this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([canAddNewAddress, processId, customerId]) =>
|
||||
canAddNewAddress ? this._navigation.addBillingAddressRoute({ processId, customerId }) : undefined
|
||||
)
|
||||
canAddNewAddress ? this._navigation.addBillingAddressRoute({ processId, customerId }) : undefined,
|
||||
),
|
||||
);
|
||||
|
||||
constructor(
|
||||
@@ -76,7 +74,7 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
private _store: CustomerSearchStore,
|
||||
private _customerService: CrmCustomerService,
|
||||
private _modal: UiModalService,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
private _navigation: CustomerSearchNavigation,
|
||||
) {
|
||||
super({
|
||||
assignedPayers: [],
|
||||
@@ -89,12 +87,15 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._store.customerId$.pipe(takeUntil(this._onDestroy$)).subscribe((customerId) => {
|
||||
this.resetStore();
|
||||
if (customerId) {
|
||||
this.loadAssignedPayers(customerId);
|
||||
}
|
||||
});
|
||||
combineLatest([this._store.customerId$, this._store.isMitarbeiter$])
|
||||
.pipe(takeUntil(this._onDestroy$), debounceTime(250))
|
||||
.subscribe(([customerId, isMitarbeiter]) => {
|
||||
this.resetStore();
|
||||
// #4715 Hier erfolgt ein Check auf Mitarbeiter, da Mitarbeiter keine zusätzlichen Rechnungsadressen haben sollen
|
||||
if (customerId && !isMitarbeiter) {
|
||||
this.loadAssignedPayers(customerId);
|
||||
}
|
||||
});
|
||||
|
||||
combineLatest([this.selectedPayer$, this._store.customer$])
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
@@ -151,9 +152,9 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
switchMap((customerId) =>
|
||||
this._customerService
|
||||
.getAssignedPayers({ customerId })
|
||||
.pipe(tapResponse(this.handleLoadAssignedPayersResponse, this.handleLoadAssignedPayersError))
|
||||
)
|
||||
)
|
||||
.pipe(tapResponse(this.handleLoadAssignedPayersResponse, this.handleLoadAssignedPayersError)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
handleLoadAssignedPayersResponse = (response: ListResponseArgsOfAssignedPayerDTO) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, inject, OnInit, AfterViewInit, ViewChild } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, inject, OnInit, AfterViewInit, ViewChild, effect } from '@angular/core';
|
||||
import { PickupShelfDetailsBaseComponent } from '../../pickup-shelf-details-base.component';
|
||||
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
|
||||
import { PickUpShelfDetailsHeaderComponent } from '../../shared/pickup-shelf-details-header/pickup-shelf-details-header.component';
|
||||
@@ -11,7 +11,7 @@ import { OnInitDirective } from '@shared/directives/element-lifecycle';
|
||||
import { PickupShelfInNavigationService } from '@shared/services';
|
||||
import { BehaviorSubject, asapScheduler, combineLatest } from 'rxjs';
|
||||
import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
||||
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString } from '@swagger/oms';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
@@ -52,6 +52,8 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
|
||||
|
||||
order$ = this.store.order$;
|
||||
|
||||
$customerNumber = toSignal(this.store.customerNumber$);
|
||||
|
||||
orderItems$ = this.store.orderItems$.pipe(shareReplay(1));
|
||||
|
||||
noOrderItemsFound$ = this.store.noOrderItemsFound$;
|
||||
@@ -105,6 +107,14 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
) {
|
||||
super();
|
||||
|
||||
effect(() => {
|
||||
const customerNumber = this.$customerNumber();
|
||||
|
||||
if (customerNumber) {
|
||||
this.store.fetchCoverOrderItems(customerNumber);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -124,11 +134,11 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
|
||||
});
|
||||
|
||||
// Fix #4696 - Always Fetch Cover Order Items
|
||||
this._activatedRoute.params.pipe(distinctUntilChanged(isEqual), takeUntilDestroyed(this.destroyRef)).subscribe((_) => {
|
||||
if (!this.store.coverOrderItems || this.store.coverOrderItems.length === 0) {
|
||||
this.store.fetchCoverOrderItems();
|
||||
}
|
||||
});
|
||||
// this._activatedRoute.params.pipe(distinctUntilChanged(isEqual), takeUntilDestroyed(this.destroyRef)).subscribe((_) => {
|
||||
// if (!this.store.coverOrderItems || this.store.coverOrderItems.length === 0) {
|
||||
// this.store.fetchCoverOrderItems();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
|
||||
import { PickupShelfDetailsState } from './pickup-shelf-details.state';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import {
|
||||
@@ -702,19 +703,10 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
|
||||
return this.select(Selectors.selectLatestSmsNotificationDate2(orderItemSubsetId));
|
||||
};
|
||||
|
||||
delayWhenCustomerNumberNotExists = delayWhen(() =>
|
||||
this.customerNumber$.pipe(
|
||||
filter((cn) => !!cn),
|
||||
take(1),
|
||||
),
|
||||
);
|
||||
|
||||
fetchCoverOrderItems = this.effect((trigger$: Observable<void>) =>
|
||||
fetchCoverOrderItems = this.effect((trigger$: Observable<string>) =>
|
||||
trigger$.pipe(
|
||||
this.delayWhenCustomerNumberNotExists,
|
||||
tap(() => this.beforeFetchCoverOrderItems()),
|
||||
withLatestFrom(this.customerNumber$),
|
||||
switchMap(([_, customerNumber]) =>
|
||||
switchMap((customerNumber) =>
|
||||
this._pickupShelfIOService
|
||||
.getOrderItemsByCustomerNumber({
|
||||
customerNumber,
|
||||
|
||||
@@ -29,6 +29,20 @@
|
||||
<input uiInput formControlName="buyerNumber" />
|
||||
</ui-form-control>
|
||||
|
||||
<ng-container *ngIf="showNameFields">
|
||||
<ui-form-control label="Name" variant="inline" [statusLabel]="canEditNameFields ? '' : 'Nicht Änderbar'">
|
||||
<input uiInput formControlName="firstName" />
|
||||
</ui-form-control>
|
||||
|
||||
<ui-form-control label="Vorname" variant="inline" [statusLabel]="canEditNameFields ? '' : 'Nicht Änderbar'">
|
||||
<input uiInput formControlName="lastName" />
|
||||
</ui-form-control>
|
||||
|
||||
<ui-form-control *ngIf="isB2B" label="Firmenname" variant="inline" [statusLabel]="canEditNameFields ? '' : 'Nicht Änderbar'">
|
||||
<input uiInput formControlName="organisation" />
|
||||
</ui-form-control>
|
||||
</ng-container>
|
||||
|
||||
<div formArrayName="items">
|
||||
<div *ngFor="let item of itemsControl.controls; index as i" [formGroupName]="i">
|
||||
<div class="item-header-wrapper">
|
||||
|
||||
@@ -82,6 +82,20 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
return Array.from(ProcessingStatusNameMap.keys());
|
||||
}
|
||||
|
||||
get showNameFields(): boolean {
|
||||
// orderType 1 === Abholung / Rücklage
|
||||
return this.items[0]?.orderType === 1; // Felder nur bei "Rücklage" oder "Abholung" anzeigen #4687
|
||||
}
|
||||
|
||||
get canEditNameFields(): boolean {
|
||||
return this.items[0]?.processingStatus === 16; // Felder nur im Status bestellt bearbeitbar #4687
|
||||
}
|
||||
|
||||
// #4687 Ungenauer B2B-Check, da Customer Features nicht am OrderItemListItemDTO hängen
|
||||
get isB2B(): boolean {
|
||||
return !!this.items[0]?.organisation;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private fb: UntypedFormBuilder,
|
||||
private processingStatusPipe: ProcessingStatusPipe,
|
||||
@@ -89,7 +103,7 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
private omsService: DomainOmsService,
|
||||
private dateAdapter: DateAdapter,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private _modal: UiModalService
|
||||
private _modal: UiModalService,
|
||||
) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -123,6 +137,9 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
orderDate: fb.control({ value: this.datePipe.transform(items[0].orderDate), disabled: true }),
|
||||
clientChannel: fb.control({ value: (await this.getOrderSource()) ?? items[0].features?.orderSource, disabled: true }),
|
||||
buyerNumber: fb.control({ value: items[0].buyerNumber, disabled: true }),
|
||||
firstName: fb.control({ value: items[0].firstName, disabled: !this.canEditNameFields }),
|
||||
lastName: fb.control({ value: items[0].lastName, disabled: !this.canEditNameFields }),
|
||||
organisation: fb.control({ value: items[0].organisation, disabled: !this.canEditNameFields }),
|
||||
items: fb.array([]),
|
||||
notificationChannel: this.notificationsGroup,
|
||||
});
|
||||
@@ -169,7 +186,7 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
if (!value) {
|
||||
fbItem.get('compartmentInfo').reset('');
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.itemsControl.push(fbItem);
|
||||
@@ -246,10 +263,10 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
notificationChannels.length === 2
|
||||
? ['email', 'sms']
|
||||
: notificationChannels[0] === 1
|
||||
? ['email']
|
||||
: notificationChannels[0] === 2
|
||||
? ['sms']
|
||||
: [],
|
||||
? ['email']
|
||||
: notificationChannels[0] === 2
|
||||
? ['sms']
|
||||
: [],
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
@@ -270,6 +287,9 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
try {
|
||||
const control = this.control.getRawValue();
|
||||
const orderId = control.orderId;
|
||||
const firstName = control.firstName;
|
||||
const lastName = control.lastName;
|
||||
const organisation = control.organisation;
|
||||
|
||||
if (this.notificationsGroup.dirty) {
|
||||
try {
|
||||
@@ -305,6 +325,19 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.firstOrLastNameOrOrganisationChanged()) {
|
||||
await this.omsService.updateOrder({ orderId, firstName, lastName, organisation }).pipe(first()).toPromise();
|
||||
}
|
||||
} catch (error) {
|
||||
this._modal.open({
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
title: 'Fehler beim Aktualisieren der Bestellung - Vorname und Name konnten nicht übernommen werden',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.isOrderItemDirty(formGroup)) {
|
||||
await this.omsService
|
||||
@@ -350,7 +383,7 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
},
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise()
|
||||
.toPromise(),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -382,6 +415,10 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
}
|
||||
}
|
||||
|
||||
firstOrLastNameOrOrganisationChanged() {
|
||||
return this.control.get('firstName').dirty || this.control.get('lastName').dirty || this.control.get('organisation').dirty;
|
||||
}
|
||||
|
||||
getFormGroupByOrderItemSubsetId(orderItemSubsetId: number): UntypedFormGroup {
|
||||
const arr = this.control.get('items') as UntypedFormArray;
|
||||
return arr.controls.find((c) => (c as UntypedFormGroup).controls.orderItemSubsetId.value === orderItemSubsetId) as UntypedFormGroup;
|
||||
|
||||
@@ -135,7 +135,10 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
|
||||
return this._service.getVats$();
|
||||
}
|
||||
|
||||
constructor(private _service: PurchaseOptionsService, private _catalogService: DomainCatalogService) {
|
||||
constructor(
|
||||
private _service: PurchaseOptionsService,
|
||||
private _catalogService: DomainCatalogService,
|
||||
) {
|
||||
super({
|
||||
defaultBranch: undefined,
|
||||
inStoreBranch: undefined,
|
||||
@@ -184,6 +187,7 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
|
||||
|
||||
await this._loadAvailabilities();
|
||||
await this._loadCanAdd();
|
||||
this.selectSelectableItems();
|
||||
}
|
||||
|
||||
// #region Private funtions for loading and setting Branches and Availabilities
|
||||
@@ -407,7 +411,7 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
|
||||
this.patchState({ availabilities });
|
||||
} else {
|
||||
let availabilities = this.availabilities.filter(
|
||||
(a) => !(a.itemId === availability.itemId && a.purchaseOption === availability.purchaseOption)
|
||||
(a) => !(a.itemId === availability.itemId && a.purchaseOption === availability.purchaseOption),
|
||||
);
|
||||
this.patchState({ availabilities });
|
||||
}
|
||||
@@ -440,7 +444,7 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
|
||||
availability: inStoreAvailability.data,
|
||||
quantity: item.quantity ?? 1,
|
||||
type: this.type,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -456,7 +460,7 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
|
||||
availability: deliveryAvailability.data,
|
||||
quantity: item.quantity ?? 1,
|
||||
type: this.type,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -469,7 +473,7 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
|
||||
availability: pickupAvailability.data,
|
||||
quantity: item.quantity ?? 1,
|
||||
type: this.type,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -482,7 +486,7 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
|
||||
availability: downloadAvailability.data,
|
||||
quantity: item.quantity ?? 1,
|
||||
type: this.type,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ variables:
|
||||
value: '3'
|
||||
# Minor Version einstellen
|
||||
- name: 'Minor'
|
||||
value: '1'
|
||||
value: '3'
|
||||
- name: 'Patch'
|
||||
value: "$[counter(format('{0}.{1}', variables['Major'], variables['Minor']),0)]"
|
||||
- name: 'BuildUniqueID'
|
||||
|
||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -20,6 +20,8 @@
|
||||
"@angular/router": "^17.3.10",
|
||||
"@angular/service-worker": "^17.3.10",
|
||||
"@microsoft/signalr": "^7.0.0",
|
||||
"@ng-icons/core": "^27.5.2",
|
||||
"@ng-icons/material-icons": "^29.5.0",
|
||||
"@ngrx/component-store": "^17.2.0",
|
||||
"@ngrx/effects": "^17.2.0",
|
||||
"@ngrx/entity": "^17.2.0",
|
||||
@@ -3403,6 +3405,22 @@
|
||||
"ws": "^7.4.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@ng-icons/core": {
|
||||
"version": "27.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@ng-icons/core/-/core-27.5.2.tgz",
|
||||
"integrity": "sha512-LDfhrfxJ5yM8NkpSlz9ix+RRuecFHZYjfCgXH0dVDuk5gx+40Dxi/v9vL8oRpUP4oL9spR7Ndry2ENVZY/h4Tw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ng-icons/material-icons": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@ng-icons/material-icons/-/material-icons-29.5.0.tgz",
|
||||
"integrity": "sha512-iPkyDJ/fy9i4m4DUp2krHjMiG9XOiYozonHKJg5O/RlzjsQE4+aTyp+Imm8VAXDC35KgTO0aeeo6rltiGypWqg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ngneat/spectator": {
|
||||
"version": "15.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@ngneat/spectator/-/spectator-15.0.1.tgz",
|
||||
@@ -18342,6 +18360,22 @@
|
||||
"ws": "^7.4.5"
|
||||
}
|
||||
},
|
||||
"@ng-icons/core": {
|
||||
"version": "27.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@ng-icons/core/-/core-27.5.2.tgz",
|
||||
"integrity": "sha512-LDfhrfxJ5yM8NkpSlz9ix+RRuecFHZYjfCgXH0dVDuk5gx+40Dxi/v9vL8oRpUP4oL9spR7Ndry2ENVZY/h4Tw==",
|
||||
"requires": {
|
||||
"tslib": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"@ng-icons/material-icons": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@ng-icons/material-icons/-/material-icons-29.5.0.tgz",
|
||||
"integrity": "sha512-iPkyDJ/fy9i4m4DUp2krHjMiG9XOiYozonHKJg5O/RlzjsQE4+aTyp+Imm8VAXDC35KgTO0aeeo6rltiGypWqg==",
|
||||
"requires": {
|
||||
"tslib": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"@ngneat/spectator": {
|
||||
"version": "15.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@ngneat/spectator/-/spectator-15.0.1.tgz",
|
||||
|
||||
@@ -71,6 +71,8 @@
|
||||
"@angular/router": "^17.3.10",
|
||||
"@angular/service-worker": "^17.3.10",
|
||||
"@microsoft/signalr": "^7.0.0",
|
||||
"@ng-icons/core": "^27.5.2",
|
||||
"@ng-icons/material-icons": "^29.5.0",
|
||||
"@ngrx/component-store": "^17.2.0",
|
||||
"@ngrx/effects": "^17.2.0",
|
||||
"@ngrx/entity": "^17.2.0",
|
||||
|
||||
Reference in New Issue
Block a user