mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merge branch 'develop' into release/2.2
This commit is contained in:
@@ -4,7 +4,7 @@ import { Config } from '@core/config';
|
||||
import { isNullOrUndefined } from '@utils/common';
|
||||
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
|
||||
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { asapScheduler, BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -15,6 +15,8 @@ export class AuthService {
|
||||
return this._initialized.asObservable();
|
||||
}
|
||||
|
||||
private _authConfig: AuthConfig;
|
||||
|
||||
constructor(private _config: Config, private readonly _oAuthService: OAuthService) {
|
||||
this._oAuthService.events?.subscribe((event) => {
|
||||
if (event.type === 'token_received') {
|
||||
@@ -28,13 +30,14 @@ export class AuthService {
|
||||
throw new Error('AuthService is already initialized');
|
||||
}
|
||||
|
||||
const authConfig: AuthConfig = this._config.get('@core/auth');
|
||||
this._authConfig = this._config.get('@core/auth');
|
||||
|
||||
authConfig.redirectUri = window.location.origin;
|
||||
authConfig.silentRefreshRedirectUri = window.location.origin + '/silent-refresh.html';
|
||||
authConfig.useSilentRefresh = true;
|
||||
this._authConfig.redirectUri = window.location.origin;
|
||||
|
||||
this._oAuthService.configure(authConfig);
|
||||
this._authConfig.silentRefreshRedirectUri = window.location.origin + '/silent-refresh.html';
|
||||
this._authConfig.useSilentRefresh = true;
|
||||
|
||||
this._oAuthService.configure(this._authConfig);
|
||||
this._oAuthService.tokenValidationHandler = new JwksValidationHandler();
|
||||
|
||||
this._oAuthService.setupAutomaticSilentRefresh();
|
||||
@@ -91,6 +94,9 @@ export class AuthService {
|
||||
|
||||
async logout() {
|
||||
await this._oAuthService.revokeTokenAndLogout();
|
||||
asapScheduler.schedule(() => {
|
||||
window.location.reload();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
hasRole(role: string | string[]) {
|
||||
@@ -105,11 +111,15 @@ export class AuthService {
|
||||
return roles.every((r) => userRoles.includes(r));
|
||||
}
|
||||
|
||||
async silentRefresh() {
|
||||
async refresh() {
|
||||
try {
|
||||
await this._oAuthService.silentRefresh();
|
||||
if (this._authConfig.responseType.includes('code') && this._authConfig.scope.includes('offline_access')) {
|
||||
await this._oAuthService.refreshToken();
|
||||
} else {
|
||||
await this._oAuthService.silentRefresh();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Silent refresh failed', error);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable, InjectionToken } from '@angular/core';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { SignalrHub, SignalRHubOptions } from '@core/signalr';
|
||||
import { BehaviorSubject, merge, of } from 'rxjs';
|
||||
import { filter, map, shareReplay, tap, withLatestFrom } from 'rxjs/operators';
|
||||
@@ -9,7 +10,16 @@ export const NOTIFICATIONS_HUB_OPTIONS = new InjectionToken<SignalRHubOptions>('
|
||||
@Injectable()
|
||||
export class NotificationsHub extends SignalrHub {
|
||||
updateNotification$ = new BehaviorSubject<MessageBoardItemDTO>(undefined);
|
||||
constructor(@Inject(NOTIFICATIONS_HUB_OPTIONS) options: SignalRHubOptions) {
|
||||
|
||||
get branchNo() {
|
||||
return String(this._auth.getClaimByKey('branch_no') || this._auth.getClaimByKey('sub'));
|
||||
}
|
||||
|
||||
get sessionStoragesessionStorageKey() {
|
||||
return `NOTIFICATIONS_BOARD_${this.branchNo}`;
|
||||
}
|
||||
|
||||
constructor(@Inject(NOTIFICATIONS_HUB_OPTIONS) options: SignalRHubOptions, private _auth: AuthService) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
@@ -31,12 +41,12 @@ export class NotificationsHub extends SignalrHub {
|
||||
|
||||
private _storeNotifactions(data: EnvelopeDTO<MessageBoardItemDTO[]>) {
|
||||
if (data) {
|
||||
localStorage.setItem('NOTIFICATIONS_BOARD', JSON.stringify(data));
|
||||
sessionStorage.setItem(this.sessionStoragesessionStorageKey, JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
private _getNotifications(): EnvelopeDTO<MessageBoardItemDTO[]> {
|
||||
const stringData = localStorage.getItem('NOTIFICATIONS_BOARD');
|
||||
const stringData = sessionStorage.getItem(this.sessionStoragesessionStorageKey);
|
||||
if (stringData) {
|
||||
return JSON.parse(stringData);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export class AppComponent implements OnInit {
|
||||
if (silentRefreshInterval > 0) {
|
||||
interval(silentRefreshInterval).subscribe(() => {
|
||||
if (this._authService.isAuthenticated()) {
|
||||
this._authService.silentRefresh();
|
||||
this._authService.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -135,7 +135,7 @@ export class AppComponent implements OnInit {
|
||||
onVisibilityChange(event: Event) {
|
||||
// refresh token when app is in background
|
||||
if (this._document.hidden && this._authService.isAuthenticated()) {
|
||||
this._authService.silentRefresh();
|
||||
this._authService.refresh();
|
||||
} else if (!this._authService.isAuthenticated()) {
|
||||
return this._modal
|
||||
.open({
|
||||
@@ -145,7 +145,7 @@ export class AppComponent implements OnInit {
|
||||
})
|
||||
.afterClosed$.pipe(
|
||||
tap(() => {
|
||||
this._authService.login();
|
||||
this._authService.logout();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export class HttpErrorInterceptor implements HttpInterceptor {
|
||||
})
|
||||
.afterClosed$.pipe(
|
||||
tap(() => {
|
||||
this._auth.login();
|
||||
this._auth.logout();
|
||||
}),
|
||||
mergeMap(() => throwError(error))
|
||||
);
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
"responseType": "id_token token",
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
|
||||
"clientId": "isa-client",
|
||||
"responseType": "code",
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi",
|
||||
"showDebugInformation": true
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
"responseType": "id_token token",
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
|
||||
"clientId": "isa-client",
|
||||
"responseType": "code",
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi",
|
||||
"showDebugInformation": true
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
parent.postMessage(location.hash, location.origin);
|
||||
var checks = [/[\?|&|#]code=/, /[\?|&|#]error=/, /[\?|&|#]token=/, /[\?|&|#]id_token=/];
|
||||
|
||||
function isResponse(str) {
|
||||
if (!str) return false;
|
||||
for (var i = 0; i < checks.length; i++) {
|
||||
if (str.match(checks[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var message = isResponse(location.hash) ? location.hash : '#' + location.search;
|
||||
|
||||
if (window.parent && window.parent !== window) {
|
||||
// if loaded as an iframe during silent refresh
|
||||
window.parent.postMessage(message, location.origin);
|
||||
} else if (window.opener && window.opener !== window) {
|
||||
// if loaded as a popup during initial login
|
||||
window.opener.postMessage(message, location.origin);
|
||||
} else {
|
||||
// last resort for a popup which has been through redirects and can't use window.opener
|
||||
localStorage.setItem('auth_hash', message);
|
||||
localStorage.removeItem('auth_hash');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -69,20 +69,15 @@ export class ReorderModalComponent extends ComponentStore<GoodsInListReorderModa
|
||||
)
|
||||
);
|
||||
|
||||
readonly currentBranch$ = combineLatest([
|
||||
this._applicationService.getSelectedBranch$(),
|
||||
this.domainAvailabilityService.getDefaultBranch(),
|
||||
]).pipe(map(([selectedBranch, defaultBranch]) => selectedBranch ?? defaultBranch));
|
||||
|
||||
readonly storeAvailabilities$ = combineLatest([this.orderItem$, this.currentBranch$]).pipe(
|
||||
switchMap(([item, branch]) =>
|
||||
readonly storeAvailabilities$ = this.orderItem$.pipe(
|
||||
switchMap((item) =>
|
||||
this.domainAvailabilityService
|
||||
.getPickUpAvailabilities([
|
||||
{
|
||||
qty: item.quantity,
|
||||
ean: item.product.ean,
|
||||
itemId: item.product?.catalogProductNumber,
|
||||
shopId: branch.id,
|
||||
shopId: item?.targetBranchId,
|
||||
price: item.retailPrice,
|
||||
},
|
||||
])
|
||||
|
||||
@@ -33,13 +33,30 @@
|
||||
<ui-select-bullet [ngModel]="selected" (ngModelChange)="setSelected($event)"></ui-select-bullet>
|
||||
</div>
|
||||
|
||||
<div class="item-stock">
|
||||
<ui-icon icon="home" size="1em"></ui-icon>
|
||||
<span *ngIf="inStock$ | async; let stock" [class.skeleton]="stock.inStock === undefined" class="min-w-[1rem] text-right inline-block">{{
|
||||
stock?.inStock
|
||||
}}</span>
|
||||
x
|
||||
<div class="item-stock z-dropdown" [uiOverlayTrigger]="tooltip" [overlayTriggerDisabled]="!(stockTooltipText$ | async)">
|
||||
<ng-container *ngIf="isOrderBranch$ | async">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<ui-icon icon="home" size="1em"></ui-icon>
|
||||
<span
|
||||
*ngIf="inStock$ | async; let stock"
|
||||
[class.skeleton]="stock.inStock === undefined"
|
||||
class="min-w-[1rem] text-right inline-block"
|
||||
>{{ stock?.inStock }}</span
|
||||
>
|
||||
<span>x</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!(isOrderBranch$ | async)">
|
||||
<div class="flex flex-row items-center justify-between z-dropdown">
|
||||
<ui-icon class="block" icon="home" size="1em"></ui-icon>
|
||||
<span class="min-w-[1rem] text-center inline-block">-</span>
|
||||
<span>x</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
<!-- <div class="item-stock"><ui-icon icon="home" size="1em"></ui-icon> {{ item?.stockInfos | stockInfos }} x</div> -->
|
||||
|
||||
<div class="item-ssc" [class.xs]="item?.catalogAvailability?.sscText?.length >= 60">
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ItemDTO } from '@swagger/cat';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { debounceTime, switchMap, tap } from 'rxjs/operators';
|
||||
import { debounceTime, switchMap, map, tap, shareReplay } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
|
||||
export interface SearchResultItemComponentState {
|
||||
@@ -83,6 +83,30 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
switchMap((processId) => this.applicationService.getSelectedBranch$(processId))
|
||||
);
|
||||
|
||||
isOrderBranch$ = combineLatest([this.defaultBranch$, this.selectedBranchId$]).pipe(
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
const branch = selectedBranch ?? defaultBranch;
|
||||
return branch.branchType !== 4;
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
stockTooltipText$ = combineLatest([this.defaultBranch$, this.selectedBranchId$]).pipe(
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
if (defaultBranch?.branchType === 4) {
|
||||
if (!selectedBranch) {
|
||||
return 'Wählen Sie eine Filiale aus, um den Bestand zu sehen.';
|
||||
}
|
||||
} else {
|
||||
if (selectedBranch && defaultBranch.id !== selectedBranch?.id) {
|
||||
return 'Sie sehen den Bestand einer anderen Filiale.';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
inStock$ = combineLatest([this.item$, this.selectedBranchId$, this.defaultBranch$]).pipe(
|
||||
debounceTime(100),
|
||||
switchMap(([item, branch, defaultBranch]) =>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DomainCatalogModule } from '@domain/catalog';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
import { UiOrderByFilterModule } from 'apps/ui/filter/src/lib/next/order-by-filter/order-by-filter.module';
|
||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
||||
@@ -28,6 +29,7 @@ import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe
|
||||
UiSpinnerModule,
|
||||
UiOrderByFilterModule,
|
||||
ScrollingModule,
|
||||
UiTooltipModule,
|
||||
],
|
||||
exports: [ArticleSearchResultsComponent, SearchResultItemComponent],
|
||||
declarations: [
|
||||
|
||||
@@ -35,3 +35,7 @@ p {
|
||||
.select-option {
|
||||
@apply mt-4 mb-4 border-2 border-solid border-brand text-brand text-cta-l font-bold bg-white rounded-full py-3 px-6;
|
||||
}
|
||||
|
||||
.select-option:disabled {
|
||||
@apply bg-disabled-branch border-disabled-branch text-white;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</p>
|
||||
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
|
||||
<ui-branch-dropdown
|
||||
class="min-h-[38px]"
|
||||
[branches]="branches$ | async"
|
||||
[selected]="selected$ | async"
|
||||
(selectBranch)="selectBranch($event)"
|
||||
@@ -18,7 +19,12 @@
|
||||
>
|
||||
<div class="grow"></div>
|
||||
<div>
|
||||
<button [disabled]="availability.price?.value?.value < 0" type="button" class="select-option" (click)="select()">
|
||||
<button
|
||||
[disabled]="availability.price?.value?.value < 0 || !(selected$ | async)"
|
||||
type="button"
|
||||
class="select-option"
|
||||
(click)="select()"
|
||||
>
|
||||
Auswählen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,14 @@ import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
})
|
||||
export class PickUpOptionComponent {
|
||||
branches$: Observable<BranchDTO[]> = this._purchasingOptionsModalStore.selectAvailableBranches;
|
||||
selected$: Observable<string> = this._purchasingOptionsModalStore.selectBranch.pipe(map((branch) => branch.name));
|
||||
selected$: Observable<string> = this._purchasingOptionsModalStore.selectBranch.pipe(
|
||||
map((branch) => {
|
||||
// Determins if branch is targetBranch
|
||||
if (branch.branchType === 1) {
|
||||
return branch.name;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
readonly item$ = this._purchasingOptionsModalStore.selectItem;
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<shared-goods-in-out-order-details (actionHandled)="actionHandled($event)" [itemsSelectable]="true">
|
||||
<shared-goods-in-out-order-details (actionHandled)="actionHandled($event)" [itemsSelectable]="true" [fetchPartial]="true">
|
||||
<shared-goods-in-out-order-details-header [order]="order$ | async" (editClick)="navigateToEditPage($event)">
|
||||
</shared-goods-in-out-order-details-header>
|
||||
<shared-goods-in-out-order-details-item
|
||||
*ngFor="let item of items$ | async"
|
||||
[orderItem]="item"
|
||||
[order]="order$ | async"
|
||||
[selected]="true"
|
||||
></shared-goods-in-out-order-details-item>
|
||||
<shared-goods-in-out-order-details-tags></shared-goods-in-out-order-details-tags>
|
||||
</shared-goods-in-out-order-details>
|
||||
|
||||
@@ -162,15 +162,11 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
switchMap(([options, results, filter, branch]) => {
|
||||
const queryToken = filter?.getQueryToken() ?? {};
|
||||
|
||||
let cachedResultCount: number;
|
||||
|
||||
const cached = options?.siletReload && this._cache.get(filter?.getQueryToken());
|
||||
if (cached) {
|
||||
const cachedResults = this._cache.get(queryToken);
|
||||
if (cachedResults?.results?.length > 0) {
|
||||
this.patchState(cachedResults);
|
||||
cachedResultCount = cachedResults.results.length;
|
||||
|
||||
this._searchResultFromCacheSubject.next({ hits: cachedResults.hits, results: cachedResults.results });
|
||||
}
|
||||
}
|
||||
@@ -180,7 +176,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
queryToken.take = 50;
|
||||
} else if (options.siletReload) {
|
||||
queryToken.skip = 0;
|
||||
queryToken.take = cachedResultCount || results.length || 50;
|
||||
queryToken.take = 50;
|
||||
} else {
|
||||
queryToken.skip = results.length;
|
||||
queryToken.take = 50;
|
||||
|
||||
@@ -45,6 +45,18 @@ export class SharedGoodsInOutOrderDetailsComponent extends SharedGoodsInOutOrder
|
||||
}
|
||||
}
|
||||
|
||||
// Von außen auf true setzen um die select-bullets auch ohne die Action FETCHED_PARTIAL anzuzeigen
|
||||
// Benutzt in Kundenbestellungen #3771
|
||||
@Input()
|
||||
get fetchPartial() {
|
||||
return this.get((s) => s.fetchPartial);
|
||||
}
|
||||
set fetchPartial(fetchPartial: boolean) {
|
||||
if (this.fetchPartial !== fetchPartial) {
|
||||
this.patchState({ fetchPartial: fetchPartial });
|
||||
}
|
||||
}
|
||||
|
||||
get receipts() {
|
||||
return this.get((s) => s.receipts);
|
||||
}
|
||||
@@ -216,7 +228,9 @@ export class SharedGoodsInOutOrderDetailsComponent extends SharedGoodsInOutOrder
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
this.patchState({ fetchPartial: false });
|
||||
if (action.command.includes('FETCHED_PARTIAL')) {
|
||||
this.patchState({ fetchPartial: false });
|
||||
}
|
||||
this.selectedeOrderItemSubsetIds = [];
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
cdkOverlayOrigin
|
||||
#trigger="cdkOverlayOrigin"
|
||||
>
|
||||
<span>{{ selected }}</span>
|
||||
<span>{{ selected ?? 'Filiale auswählen' }}</span>
|
||||
<div class="arrow-icon">
|
||||
<ui-icon size="15px" icon="arrow_head"></ui-icon>
|
||||
</div>
|
||||
|
||||
@@ -24,8 +24,7 @@ export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
|
||||
component: UiOverlayTrigger;
|
||||
|
||||
@Input()
|
||||
@HostBinding('disabled')
|
||||
disabled: boolean;
|
||||
overlayTriggerDisabled: boolean;
|
||||
|
||||
private overlayRef: OverlayRef;
|
||||
private viewRef: EmbeddedViewRef<any>;
|
||||
@@ -59,8 +58,15 @@ export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
@HostListener('click')
|
||||
toggle() {
|
||||
@HostListener('click', ['$event'])
|
||||
toggle(event: Event) {
|
||||
if (this.overlayTriggerDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (this.viewRef) {
|
||||
this.close();
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user