Compare commits

...

34 Commits

Author SHA1 Message Date
Lorenz Hilpert
4b10dd96d9 Merge branch 'hotfix/abholfach-zubuchen-und-cover-items' 2024-11-19 13:50:36 +01:00
Lorenz Hilpert
1126e4f0c1 Import tapResponse alongside ComponentStore in pickup-shelf-details.store.ts 2024-11-19 13:50:15 +01:00
Lorenz Hilpert
e9f24a88d6 Refactor PickupShelfInDetailsComponent to use signals for customer number and update fetch logic 2024-11-19 13:48:33 +01:00
Lorenz Hilpert
d48680c59e Merge branch 'release/3.3' 2024-11-12 17:04:15 +01:00
Lorenz Hilpert
1d472ce3df (cherry picked from commit 4c027922283bc4113253310c9d47e7c8944427a3) 2024-10-07 19:04:31 +02:00
Nino Righi
1d19779dac Merged PR 1805: #4687 Hotfix Organisation Name Update Order
#4687 Hotfix Organisation Name Update Order
2024-10-07 14:33:04 +00:00
Nino
90e671d285 #4687 Changes to View, Added Organisation Input Field 2024-10-07 12:07:28 +02:00
Lorenz Hilpert
33fb44f20a Upgrade Version to 3.3 2024-09-24 16:51:36 +02:00
Lorenz Hilpert
8723f7aa7e Merge tag '3.2' into develop
Finish Release 3.2 3.2
2024-09-24 16:47:21 +02:00
Lorenz Hilpert
03815586f7 Merge branch 'release/3.2' 2024-09-24 16:47:03 +02:00
Nino Righi
86a11ff07a Merged PR 1791: #4715 Disable option for Staff to Add new Billing Adresses and only show the...
#4715 Disable option for Staff to Add new Billing Adresses and only show the very first attached to their accounts
2024-09-24 14:25:48 +00:00
Nino Righi
41be8533dc Merged PR 1790: #4776 Suchbegriff in der Artikelsuche soll bei Klick auf Filter bestehen blei...
#4776 Suchbegriff in der Artikelsuche soll bei Klick auf Filter bestehen bleiben (Trefferliste)
2024-09-24 13:42:17 +00:00
Nino Righi
186afbc828 Merged PR 1789: #4687 WA, Abholfach, HSC - First and Lastname Update on Buyer just on Order
#4687 WA, Abholfach, HSC - First and Lastname Update on Buyer just on Order
2024-09-24 13:39:57 +00:00
Nino Righi
c3561339a9 Merged PR 1788: #4794 Adjusted Route Error Handling
#4794 Adjusted Route Error Handling
2024-09-24 13:01:43 +00:00
Nino Righi
5312073184 Merged PR 1787: #4710 Initially Select All Possible Items Inside Purchase Options Modal
#4710 Initially Select All Possible Items Inside Purchase Options Modal
2024-09-24 12:45:49 +00:00
Lorenz Hilpert
4dfe3bfa11 Merge tag 'KameraOffline' into develop
Kamera kann nur geöffnet werden wenn man Online ist. KameraOffline
2024-09-24 14:39:19 +02:00
Lorenz Hilpert
9b7a1b1c21 Merge branch 'hotfix/skip-open-camera-when-offline' 2024-09-24 14:39:11 +02:00
Lorenz Hilpert
a290d3b249 Kamera öffnet sich nur wenn man Online ist 2024-09-24 14:37:48 +02:00
Lorenz Hilpert
ad348af551 Merge branch 'master' into release/3.2 2024-09-17 10:56:13 +02:00
Lorenz Hilpert
f1bdba5d10 Merge tag 'online-offline-verhalten' into develop
Merge Online Offline Verhalten online-offline-verhalten
2024-09-16 16:46:38 +02:00
Lorenz Hilpert
c4134e7f99 Merge branch 'hotfix/online-offline-network' 2024-09-16 16:46:28 +02:00
Lorenz Hilpert
b7a16f5d30 IPad 6 Fehlermeldung word wrap 2024-09-16 14:39:00 +02:00
Lorenz Hilpert
4105709286 Added Styles instead of classes for word wrapping 2024-09-16 14:23:11 +02:00
Lorenz Hilpert
0c3b322fbd Ipad 4 Error Anzeige 2024-09-16 13:31:00 +02:00
Lorenz Hilpert
12096754c7 Set Max Screen Width Error 2024-09-16 12:28:25 +02:00
Lorenz Hilpert
453d921a99 Nachricht angepasst 2024-09-16 11:54:52 +02:00
Lorenz Hilpert
bad05fd098 Offline und Online Banner
Initialisierung Wartet auf Netzwerk
2024-09-13 17:30:08 +02:00
Lorenz Hilpert
363daf1e35 console.log entfernt 2024-09-13 16:08:21 +02:00
Lorenz Hilpert
e0cb0974cf Initialisierung gibt ein Feedback an den Benutzer aus. Feedback wenn Benutzer offline ist. 2024-09-13 16:05:54 +02:00
Lorenz Hilpert
c3d9274766 Merge branch 'hotfix/pwa-camera-dialog-size' 2024-09-13 11:03:04 +02:00
Lorenz Hilpert
bc16b841fb Kamera Overlay Größe
(cherry picked from commit d5dc4e053d)
2024-09-13 11:02:30 +02:00
Lorenz Hilpert
d5dc4e053d Kamera Overlay Größe 2024-09-12 18:03:07 +02:00
Lorenz Hilpert
3c6833988c Change Variables in pipeline Minor from 1 to 2 2024-09-04 17:58:00 +02:00
Lorenz Hilpert
28fb4ebb48 Merge tag '3.1' into develop 2024-09-04 17:56:06 +02:00
27 changed files with 487 additions and 165 deletions

View File

@@ -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 {

View File

@@ -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) => {

View File

@@ -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);

View File

@@ -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[]>,
),
),
);
}
}

View File

@@ -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';

View File

@@ -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>

View File

@@ -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

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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,
// });
}
}

View 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$;

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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);
})
}),
);
}
}

View File

@@ -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>

View File

@@ -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'])

View File

@@ -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) => {

View File

@@ -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() {

View File

@@ -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,

View File

@@ -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">

View File

@@ -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;

View File

@@ -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,
})
}),
);
}
});

View File

@@ -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
View File

@@ -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",

View File

@@ -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",