mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
63 Commits
fix/3265-W
...
2.0.531
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12fb774b73 | ||
|
|
8cfb160989 | ||
|
|
6325167eda | ||
|
|
8925eae4c5 | ||
|
|
9282bcd779 | ||
|
|
5aa6499598 | ||
|
|
f766781928 | ||
|
|
02834b7102 | ||
|
|
a9e3430505 | ||
|
|
4b9a23001a | ||
|
|
8de7ec9124 | ||
|
|
19a0a3c7c3 | ||
|
|
42a7d6e4b7 | ||
|
|
66818b1647 | ||
|
|
bc8ba9adc8 | ||
|
|
4ae5759361 | ||
|
|
b5cfcf8036 | ||
|
|
061982cf2c | ||
|
|
0e1422c2c4 | ||
|
|
3e534029a0 | ||
|
|
8d9ee9fe5c | ||
|
|
675aa04564 | ||
|
|
88c8885a81 | ||
|
|
151760aef9 | ||
|
|
6c89969b60 | ||
|
|
0fd5e66c33 | ||
|
|
c8aa526e4d | ||
|
|
f2c492c6ea | ||
|
|
11cf845235 | ||
|
|
ae6fbc7c64 | ||
|
|
71eda539f6 | ||
|
|
f43b948ac9 | ||
|
|
1b77020b6a | ||
|
|
1f62040560 | ||
|
|
cc5c3167b1 | ||
|
|
b9b79b949f | ||
|
|
a0d729fe6d | ||
|
|
f618dd3865 | ||
|
|
3fd3f972db | ||
|
|
2311655e5e | ||
|
|
c589836097 | ||
|
|
dbc641cfce | ||
|
|
f13bc58925 | ||
|
|
94d5892cf1 | ||
|
|
8e32b15f26 | ||
|
|
fe5f0ef2eb | ||
|
|
daa27d5f2d | ||
|
|
bb7626609e | ||
|
|
9ed58b685b | ||
|
|
4eb81ad30a | ||
|
|
a1f2cb57b3 | ||
|
|
62b8e387ca | ||
|
|
07498db711 | ||
|
|
2adc8c6f5d | ||
|
|
f15a43f303 | ||
|
|
e35aea5a7e | ||
|
|
0e1ed9d8cc | ||
|
|
f62ef06e51 | ||
|
|
30f4d4588f | ||
|
|
e102396dab | ||
|
|
f60815ef63 | ||
|
|
7b11b53774 | ||
|
|
abff7715ee |
11
apps/core/cache/src/lib/cache.service.ts
vendored
11
apps/core/cache/src/lib/cache.service.ts
vendored
@@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CacheOptions } from './cache-options';
|
||||
import { Cached } from './cached';
|
||||
import { sha1 } from 'object-hash';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -60,7 +59,15 @@ export class CacheService {
|
||||
}
|
||||
|
||||
private getKey(token: Object) {
|
||||
return sha1(token);
|
||||
return this.hash(JSON.stringify(token));
|
||||
}
|
||||
|
||||
private hash(data: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
hash = data.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
return hash.toString(16);
|
||||
}
|
||||
|
||||
private serialize(data: Cached): string {
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
<div
|
||||
class="toast-main"
|
||||
[style.width]="width"
|
||||
[@slideAnimation]="{ value: animationState }"
|
||||
(@slideAnimation.done)="onSlideFinished($event)"
|
||||
>
|
||||
<div class="toast-main" [style.width]="width" [@slideAnimation]="{ value: animationState }" (@slideAnimation.done)="onSlideFinished()">
|
||||
<button class="absolute top-2 right-2 p-6 border-none bg-transparent" (click)="close()">
|
||||
<ui-icon icon="close" size="20px"></ui-icon>
|
||||
</button>
|
||||
|
||||
@@ -11,9 +11,9 @@ import { TOAST_CONFIG_TOKEN } from './tokens';
|
||||
animations: [toastAnimations.slideToast],
|
||||
})
|
||||
export class ToastComponent implements OnInit, OnDestroy {
|
||||
timeoutRef: NodeJS.Timeout;
|
||||
timeoutRef?: any;
|
||||
animationState: ToastAnimationState = 'default';
|
||||
width: string = '55.25rem';
|
||||
width = '55.25rem';
|
||||
|
||||
constructor(
|
||||
@Inject(TOAST_CONFIG_TOKEN) public readonly data: Toast,
|
||||
@@ -40,7 +40,7 @@ export class ToastComponent implements OnInit, OnDestroy {
|
||||
this.animationState = 'closing';
|
||||
}
|
||||
|
||||
onSlideFinished(event: AnimationEvent) {
|
||||
onSlideFinished() {
|
||||
if (this.animationState === 'closing') {
|
||||
this._ref.close();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ItemDTO, SearchService } from '@swagger/cat';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, StoreCheckoutService, SupplierDTO } from '@swagger/checkout';
|
||||
import { combineLatest, Observable, of } from 'rxjs';
|
||||
import {
|
||||
@@ -37,7 +37,7 @@ export class DomainAvailabilityService {
|
||||
@memorize()
|
||||
getTakeAwaySupplier(): Observable<SupplierDTO> {
|
||||
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
|
||||
map(({ result }) => result.find((supplier) => supplier?.supplierNumber === 'F')),
|
||||
map(({ result }) => result?.find((supplier) => supplier?.supplierNumber === 'F')),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
@@ -125,8 +125,13 @@ export class DomainAvailabilityService {
|
||||
|
||||
getTakeAwayAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
|
||||
return this.getCurrentStock().pipe(
|
||||
switchMap((s) => this._stock.StockInStock({ articleIds: [item.itemId], stockId: s.id })),
|
||||
withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
|
||||
switchMap((s) =>
|
||||
combineLatest([
|
||||
this._stock.StockInStock({ articleIds: [item.itemId], stockId: s.id }),
|
||||
this.getTakeAwaySupplier(),
|
||||
this.getCurrentBranch(),
|
||||
])
|
||||
),
|
||||
map(([response, supplier, branch]) => {
|
||||
const price = item?.price;
|
||||
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// start:ng42.barrel
|
||||
export * from './info-feed-item';
|
||||
export * from './info-feed';
|
||||
export * from './kpi-feed-item';
|
||||
export * from './kpi-feed';
|
||||
export * from './products-feed';
|
||||
|
||||
4
apps/domain/isa/src/lib/defs/info-feed-item.ts
Normal file
4
apps/domain/isa/src/lib/defs/info-feed-item.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface InfoFeedItem {
|
||||
heading: string;
|
||||
text: string;
|
||||
}
|
||||
7
apps/domain/isa/src/lib/defs/info-feed.ts
Normal file
7
apps/domain/isa/src/lib/defs/info-feed.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { FeedDTO } from '@swagger/isa';
|
||||
import { InfoFeedItem } from './info-feed-item';
|
||||
|
||||
export interface InfoFeed extends FeedDTO {
|
||||
type: 'info';
|
||||
items: InfoFeedItem[];
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
content: ReorderModalComponent,
|
||||
title: 'Artikel nachbestellen',
|
||||
data: {
|
||||
item: orderItem,
|
||||
item: { ...orderItem, quantity: data.itemQuantity?.get(orderItem.orderItemSubsetId) ?? orderItem.quantity },
|
||||
showReasons: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -55,7 +55,7 @@ export class DomainGoodsService {
|
||||
return this.abholfachService.AbholfachWareneingang({
|
||||
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
|
||||
input: {
|
||||
qs: customerNumber,
|
||||
customer_name: customerNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService) {}
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const process = await this._applicationService
|
||||
@@ -25,16 +26,42 @@ export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
|
||||
id: +route.params.processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes)}`,
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
}
|
||||
|
||||
await this.removeBreadcrumbWithSameProcessId(route);
|
||||
this._applicationService.activateProcess(+route.params.processId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
|
||||
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
|
||||
const crumbs = await this._breadcrumbService
|
||||
.getBreadcrumbByKey$(+route.params.processId)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
// Entferne alle Crumbs die nichts mit der Kundensuche zu tun haben
|
||||
if (crumbs.length > 1) {
|
||||
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'customer') === undefined);
|
||||
for (const crumb of crumbsToRemove) {
|
||||
await this._breadcrumbService.removeBreadcrumb(crumb.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
|
||||
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
|
||||
}
|
||||
|
||||
findMissingNumber(processNumbers: number[]) {
|
||||
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
|
||||
if (!processNumbers.find((number) => number === missingNumber)) {
|
||||
return missingNumber;
|
||||
}
|
||||
}
|
||||
return Math.max(...processNumbers) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,117 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateCustomerGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _router: Router
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
let lastActivatedProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
if (!lastActivatedProcessId) {
|
||||
lastActivatedProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: lastActivatedProcessId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
});
|
||||
|
||||
const lastActivatedCartCheckoutProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
const lastActivatedGoodsOutProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
|
||||
|
||||
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
|
||||
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
|
||||
await this.fromCartCheckoutProcess(processes, lastActivatedCartCheckoutProcessId);
|
||||
return false;
|
||||
} else if (!!lastActivatedGoodsOutProcessId && lastActivatedGoodsOutProcessId === activatedProcessId) {
|
||||
await this.fromGoodsOutProcess(processes, lastActivatedGoodsOutProcessId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lastActivatedProcessId) {
|
||||
await this.fromCartProcess(processes);
|
||||
return false;
|
||||
} else {
|
||||
await this._router.navigate(['/kunde', String(lastActivatedProcessId), 'customer']);
|
||||
}
|
||||
await this._router.navigate(['/kunde', lastActivatedProcessId, 'customer']);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Kundensuche
|
||||
async fromCartProcess(processes: ApplicationProcess[]) {
|
||||
const newProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: newProcessId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this._router.navigate(['/kunde', String(newProcessId), 'customer']);
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Footer Kundensuche
|
||||
async fromCartCheckoutProcess(processes: ApplicationProcess[], processId: number) {
|
||||
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
|
||||
this._checkoutService.removeProcess({ processId });
|
||||
|
||||
// Ändere type cart-checkout zu cart
|
||||
this._applicationService.patchProcess(processId, {
|
||||
id: processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
data: {},
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._router.navigate(['/kunde', String(processId), 'customer']);
|
||||
}
|
||||
|
||||
// Bei offener Warenausgabe und Klick auf Footer Kundensuche
|
||||
async fromGoodsOutProcess(processes: ApplicationProcess[], processId: number) {
|
||||
const buyer = await this._checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
const customerFeatures = await this._checkoutService.getCustomerFeatures({ processId }).pipe(first()).toPromise();
|
||||
const name = buyer
|
||||
? customerFeatures?.b2b
|
||||
? buyer.organisation?.name
|
||||
? buyer.organisation?.name
|
||||
: buyer.lastName
|
||||
: buyer.lastName
|
||||
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`;
|
||||
|
||||
// Ändere type goods-out zu cart
|
||||
this._applicationService.patchProcess(processId, {
|
||||
id: processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name,
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._router.navigate(['/kunde', String(processId), 'customer']);
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
|
||||
}
|
||||
|
||||
findMissingNumber(processNumbers: number[]) {
|
||||
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
|
||||
if (!processNumbers.find((number) => number === missingNumber)) {
|
||||
return missingNumber;
|
||||
}
|
||||
}
|
||||
return Math.max(...processNumbers) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateGoodsOutWithProcessIdGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService) {}
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const process = await this._applicationService
|
||||
@@ -14,19 +15,36 @@ export class CanActivateGoodsOutWithProcessIdGuard implements CanActivate {
|
||||
.toPromise();
|
||||
|
||||
if (!process) {
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
// const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
await this._applicationService.createProcess({
|
||||
id: +route.params.processId,
|
||||
type: 'goods-out',
|
||||
section: 'customer',
|
||||
name: `Warenausgabe ${this.processNumber(processes)}`,
|
||||
name: `Warenausgabe`,
|
||||
});
|
||||
}
|
||||
|
||||
await this.removeBreadcrumbWithSameProcessId(route);
|
||||
this._applicationService.activateProcess(+route.params.processId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
|
||||
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
|
||||
const crumbs = await this._breadcrumbService
|
||||
.getBreadcrumbByKey$(+route.params.processId)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
// Entferne alle Crumbs die nichts mit der Warenausgabe zu tun haben
|
||||
if (crumbs.length > 1) {
|
||||
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'goods-out') === undefined);
|
||||
for (const crumb of crumbsToRemove) {
|
||||
await this._breadcrumbService.removeBreadcrumb(crumb.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
|
||||
|
||||
@@ -1,44 +1,164 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { Config } from '@core/config';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateGoodsOutGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _router: Router
|
||||
) {}
|
||||
|
||||
// !!! Ticket #3272 Code soll vorerst bestehen bleiben. Prozess Warenausgabe soll wieder Vorgang heißen (wie aktuell im Produktiv), bis zum neuen Navigationskonzept
|
||||
// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
// const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
|
||||
// let lastActivatedProcessId = (
|
||||
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
|
||||
// )?.id;
|
||||
|
||||
// const lastActivatedCartProcessId = (
|
||||
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
|
||||
// )?.id;
|
||||
|
||||
// const lastActivatedCartCheckoutProcessId = (
|
||||
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
|
||||
// )?.id;
|
||||
|
||||
// const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
|
||||
|
||||
// // Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
|
||||
// if (!!lastActivatedCartProcessId && lastActivatedCartProcessId === activatedProcessId) {
|
||||
// await this.fromCartProcess(processes, route, lastActivatedCartProcessId);
|
||||
// return false;
|
||||
// } else if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
|
||||
// await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (!lastActivatedProcessId) {
|
||||
// await this.fromGoodsOutProcess(processes, route);
|
||||
// return false;
|
||||
// } else {
|
||||
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Bei offener Warenausgabe und Klick auf Footer Warenausgabe
|
||||
// async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
|
||||
// const newProcessId = Date.now();
|
||||
// await this._applicationService.createProcess({
|
||||
// id: newProcessId,
|
||||
// type: 'goods-out',
|
||||
// section: 'customer',
|
||||
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
|
||||
// });
|
||||
|
||||
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
|
||||
// }
|
||||
|
||||
// // Bei offener Artikelsuche/Kundensuche und Klick auf Footer Warenausgabe
|
||||
// async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
|
||||
// // Ändere type cart zu goods-out
|
||||
// this._applicationService.patchProcess(processId, {
|
||||
// id: processId,
|
||||
// type: 'goods-out',
|
||||
// section: 'customer',
|
||||
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
|
||||
// });
|
||||
|
||||
// // Navigation
|
||||
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
|
||||
// }
|
||||
|
||||
// // Bei offener Bestellbestätigung, Artikelsuche/Kundensuche und Klick auf Footer Warenausgabe
|
||||
// async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
|
||||
// // Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
|
||||
// this._checkoutService.removeProcess({ processId });
|
||||
|
||||
// // Ändere type cart-checkout zu goods-out
|
||||
// this._applicationService.patchProcess(processId, {
|
||||
// id: processId,
|
||||
// type: 'goods-out',
|
||||
// section: 'customer',
|
||||
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
|
||||
// data: {},
|
||||
// });
|
||||
|
||||
// // Navigation
|
||||
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
|
||||
// }
|
||||
|
||||
// !!! Ticket #3272 Code soll vorerst bestehen bleiben. Prozess Warenausgabe soll wieder Vorgang heißen (wie aktuell im Produktiv), bis zum neuen Navigationskonzept
|
||||
// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const processesIds = await this._applicationService
|
||||
.getProcesses$('customer')
|
||||
.pipe(
|
||||
first(),
|
||||
map((p) => {
|
||||
return p.filter((process) => process.type === 'goods-out').map((process) => +process.name.replace('Warenausgabe', '').trim());
|
||||
})
|
||||
)
|
||||
.toPromise();
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
|
||||
let lastActivatedProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
console.log(processesIds);
|
||||
const lastActivatedCartCheckoutProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
// if (!lastActivatedProcessId) {
|
||||
lastActivatedProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: lastActivatedProcessId,
|
||||
type: 'goods-out',
|
||||
section: 'customer',
|
||||
name: `Warenausgabe ${Math.max(...processesIds, 0) + 1}`,
|
||||
});
|
||||
// }
|
||||
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
|
||||
|
||||
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
|
||||
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
|
||||
await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lastActivatedProcessId) {
|
||||
await this.fromGoodsOutProcess(processes, route);
|
||||
return false;
|
||||
} else {
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
||||
}
|
||||
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bei offener Warenausgabe und Klick auf Footer Warenausgabe
|
||||
async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
|
||||
const newProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: newProcessId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Footer Warenausgabe
|
||||
async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
|
||||
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
|
||||
this._checkoutService.removeProcess({ processId });
|
||||
|
||||
// Ändere type cart-checkout zu goods-out
|
||||
this._applicationService.patchProcess(processId, {
|
||||
id: processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
data: {},
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
|
||||
}
|
||||
|
||||
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
|
||||
url.push(...route.url.map((segment) => segment.path));
|
||||
if (route.firstChild) {
|
||||
@@ -46,4 +166,18 @@ export class CanActivateGoodsOutGuard implements CanActivate {
|
||||
}
|
||||
return url.filter((segment) => !!segment);
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
|
||||
}
|
||||
|
||||
findMissingNumber(processNumbers: number[]) {
|
||||
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
|
||||
if (!processNumbers.find((number) => number === missingNumber)) {
|
||||
return missingNumber;
|
||||
}
|
||||
}
|
||||
return Math.max(...processNumbers) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateProductWithProcessIdGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService) {}
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const process = await this._applicationService
|
||||
@@ -25,16 +26,45 @@ export class CanActivateProductWithProcessIdGuard implements CanActivate {
|
||||
id: +route.params.processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes)}`,
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
}
|
||||
|
||||
await this.removeBreadcrumbWithSameProcessId(route);
|
||||
this._applicationService.activateProcess(+route.params.processId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
|
||||
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
|
||||
const crumbs = await this._breadcrumbService
|
||||
.getBreadcrumbByKey$(+route.params.processId)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
// Entferne alle Crumbs die nichts mit der Artikelsuche zu tun haben
|
||||
if (crumbs.length > 1) {
|
||||
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'catalog') === undefined);
|
||||
for (const crumb of crumbsToRemove) {
|
||||
await this._breadcrumbService.removeBreadcrumb(crumb.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
|
||||
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
|
||||
}
|
||||
|
||||
findMissingNumber(processNumbers: number[]) {
|
||||
// Ticket #3272 Bei Klick auf "+" bzw. neuen Prozess hinzufügen soll der neue Tab immer die höchste Nummer haben (wie aktuell im Produktiv)
|
||||
// ----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
|
||||
// if (!processNumbers.find((number) => number === missingNumber)) {
|
||||
// return missingNumber;
|
||||
// }
|
||||
// }
|
||||
return Math.max(...processNumbers) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateProductGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _router: Router
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
@@ -13,20 +18,90 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
if (!lastActivatedProcessId) {
|
||||
lastActivatedProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: lastActivatedProcessId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
});
|
||||
const lastActivatedCartCheckoutProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
const lastActivatedGoodsOutProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
|
||||
|
||||
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
|
||||
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
|
||||
await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
|
||||
return false;
|
||||
} else if (!!lastActivatedGoodsOutProcessId && lastActivatedGoodsOutProcessId === activatedProcessId) {
|
||||
await this.fromGoodsOutProcess(processes, route, lastActivatedGoodsOutProcessId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lastActivatedProcessId) {
|
||||
await this.fromCartProcess(processes, route);
|
||||
return false;
|
||||
} else {
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
||||
}
|
||||
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Artikelsuche
|
||||
async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
|
||||
const newProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: newProcessId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
|
||||
}
|
||||
|
||||
// Bei offener Warenausgabe und Klick auf Footer Artikelsuche
|
||||
async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
|
||||
const buyer = await this._checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
const customerFeatures = await this._checkoutService.getCustomerFeatures({ processId }).pipe(first()).toPromise();
|
||||
const name = buyer
|
||||
? customerFeatures?.b2b
|
||||
? buyer.organisation?.name
|
||||
? buyer.organisation?.name
|
||||
: buyer.lastName
|
||||
: buyer.lastName
|
||||
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`;
|
||||
|
||||
// Ändere type goods-out zu cart
|
||||
this._applicationService.patchProcess(processId, {
|
||||
id: processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name,
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Footer Artikelsuche
|
||||
async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
|
||||
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
|
||||
this._checkoutService.removeProcess({ processId });
|
||||
|
||||
// Ändere type cart-checkout zu cart
|
||||
this._applicationService.patchProcess(processId, {
|
||||
id: processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
data: {},
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
|
||||
}
|
||||
|
||||
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
|
||||
url.push(...route.url.map((segment) => segment.path));
|
||||
if (route.firstChild) {
|
||||
@@ -34,4 +109,18 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
}
|
||||
return url.filter((segment) => !!segment);
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
|
||||
}
|
||||
|
||||
findMissingNumber(processNumbers: number[]) {
|
||||
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
|
||||
if (!processNumbers.find((number) => number === missingNumber)) {
|
||||
return missingNumber;
|
||||
}
|
||||
}
|
||||
return Math.max(...processNumbers) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ErrorHandler, Injectable } from '@angular/core';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
|
||||
@Injectable()
|
||||
export class IsaErrorHandler implements ErrorHandler {
|
||||
constructor(private _modal: UiModalService, private _authService: AuthService) {}
|
||||
|
||||
handleError(error: any): void {
|
||||
async handleError(error: any): Promise<void> {
|
||||
console.error(error);
|
||||
|
||||
// Bei Klick auf Abbrechen auf der Login Seite erneut zur Login Seite weiterleiten
|
||||
@@ -16,6 +16,22 @@ export class IsaErrorHandler implements ErrorHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof HttpErrorResponse && error?.status === 401) {
|
||||
await this._modal
|
||||
.open({
|
||||
content: UiDialogModalComponent,
|
||||
title: 'Sitzung abgelaufen',
|
||||
data: {
|
||||
content: 'Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an',
|
||||
actions: [{ command: 'CLOSE', selected: true, label: 'Erneut anmelden' }],
|
||||
},
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
|
||||
this._authService.logout();
|
||||
return;
|
||||
}
|
||||
|
||||
this._modal.open({
|
||||
content: UiErrorModalComponent,
|
||||
title:
|
||||
|
||||
@@ -385,7 +385,7 @@ describe('ShellComponent', () => {
|
||||
spyOn(router, 'navigate');
|
||||
|
||||
await spectator.component.activateProcess(1);
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/kunde']);
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 1, 'product']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ export class ShellComponent {
|
||||
if (latestCrumb) {
|
||||
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
|
||||
} else {
|
||||
await this._router.navigate(['/kunde']);
|
||||
await this._router.navigate(['/kunde', activatedProcessId, 'product']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ hr {
|
||||
}
|
||||
|
||||
span.number {
|
||||
@apply text-right;
|
||||
@apply text-center;
|
||||
}
|
||||
|
||||
span {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<div class="product-details">
|
||||
<div class="product-image">
|
||||
<button class="image-button" (click)="showImages()">
|
||||
<img [src]="item.imageId | productImage: 195:315:true" alt="product image" />
|
||||
<ui-icon icon="search_add" size="22px"></ui-icon>
|
||||
<img (load)="loadImage()" [src]="item.imageId | productImage: 195:315:true" alt="product image" />
|
||||
<ui-icon *ngIf="imageLoaded$ | async" icon="search_add" size="22px"></ui-icon>
|
||||
</button>
|
||||
|
||||
<button (click)="showReviews()" class="recessions" *ngIf="item.reviews?.length > 0">
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div>
|
||||
<div class="format">
|
||||
<div class="format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
class="format-icon"
|
||||
@@ -84,18 +84,33 @@
|
||||
<div data-name="product-ean">{{ item.product?.ean }}</div>
|
||||
<div class="right">
|
||||
<div class="availability-icons">
|
||||
<div class="fetching medium" *ngIf="fetchingAvailabilities$ | async"></div>
|
||||
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"></div>
|
||||
<ng-template #showAvailabilityTakeAwayIcon>
|
||||
<ui-icon *ngIf="store.isTakeAwayAvailabilityAvailable$ | async" icon="shopping_bag" size="18px"></ui-icon>
|
||||
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
|
||||
<ui-icon class="truck" *ngIf="showDeliveryTruck$ | async" icon="truck" size="30px"></ui-icon>
|
||||
<ui-icon class="truck_b2b" *ngIf="showDeliveryB2BTruck$ | async" icon="truck_b2b" size="40px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<span *ngIf="store.isDownload$ | async" class="download-icon">
|
||||
<ui-icon icon="download" size="18px"></ui-icon>
|
||||
<span class="label">Download</span>
|
||||
</span>
|
||||
</ng-container>
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"></div>
|
||||
<ng-template #showAvailabilityPickUpIcon>
|
||||
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<div class="fetching xsmall" *ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"></div>
|
||||
<ng-template #showAvailabilityDeliveryIcon>
|
||||
<ui-icon *ngIf="showDeliveryTruck$ | async" class="truck" icon="truck" size="30px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="fetching xsmall"
|
||||
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityDeliveryB2BIcon>
|
||||
<ui-icon *ngIf="showDeliveryB2BTruck$ | async" class="truck_b2b" icon="truck_b2b" size="40px"></ui-icon>
|
||||
</ng-template>
|
||||
|
||||
<span *ngIf="store.isDownload$ | async" class="download-icon">
|
||||
<ui-icon icon="download" size="18px"></ui-icon>
|
||||
<span class="label">Download</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -108,6 +108,10 @@
|
||||
animation: load 0.75s linear infinite;
|
||||
}
|
||||
|
||||
.xsmall {
|
||||
@apply w-6;
|
||||
}
|
||||
|
||||
.small {
|
||||
@apply w-16;
|
||||
}
|
||||
@@ -117,7 +121,7 @@
|
||||
}
|
||||
|
||||
.availability-icons {
|
||||
@apply flex flex-row justify-end text-dark-cerulean mt-4;
|
||||
@apply flex flex-row items-center justify-end text-dark-cerulean mt-4;
|
||||
|
||||
ui-icon {
|
||||
@apply mx-1;
|
||||
@@ -168,7 +172,7 @@
|
||||
}
|
||||
|
||||
.product-text {
|
||||
@apply flex flex-col whitespace-pre-line mb-px-100;
|
||||
@apply flex flex-col whitespace-pre-line mb-px-100 break-words;
|
||||
|
||||
h3 {
|
||||
@apply my-4;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { UiModalService } from '@ui/modal';
|
||||
import { ModalReviewsComponent } from '@modal/reviews';
|
||||
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal';
|
||||
import { PurchasingOptions } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal/purchasing-options-modal.store';
|
||||
import { combineLatest, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { filter, first, map, shareReplay } from 'rxjs/operators';
|
||||
import { ArticleDetailsStore } from './article-details.store';
|
||||
import { ModalImagesComponent } from 'apps/modal/images/src/public-api';
|
||||
@@ -32,6 +32,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
private readonly subscriptions = new Subscription();
|
||||
showRecommendations: boolean;
|
||||
|
||||
imageLoaded$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
fetchingAvailabilities$ = combineLatest([
|
||||
this.store.fetchingDeliveryAvailability$,
|
||||
this.store.fetchingDeliveryB2BAvailability$,
|
||||
@@ -268,4 +270,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
const element = this.elementRef.nativeElement.closest('.main-wrapper');
|
||||
element?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
loadImage() {
|
||||
this.imageLoaded$.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
|
||||
</div>
|
||||
|
||||
<div class="item-format">
|
||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
|
||||
@@ -176,7 +176,15 @@
|
||||
</strong>
|
||||
<span class="shipping-cost-info">ohne Versandkosten</span>
|
||||
</div>
|
||||
<button class="cta-primary" (click)="order()" [disabled]="showOrderButtonSpinner">
|
||||
<button
|
||||
class="cta-primary"
|
||||
(click)="order()"
|
||||
[disabled]="
|
||||
showOrderButtonSpinner ||
|
||||
((primaryCtaLabel$ | async) === 'Bestellen' && !(checkNotificationChannelControl$ | async)) ||
|
||||
control.invalid
|
||||
"
|
||||
>
|
||||
<ui-spinner [show]="showOrderButtonSpinner">
|
||||
{{ primaryCtaLabel$ | async }}
|
||||
</ui-spinner>
|
||||
|
||||
@@ -70,7 +70,7 @@ button {
|
||||
@apply bg-brand text-white font-bold text-lg outline-none border-brand border-solid border-2 rounded-full px-6 py-3;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-customer border-none;
|
||||
@apply bg-inactive-customer border-solid border-inactive-customer cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface CheckoutReviewComponentState {
|
||||
export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewComponentState> implements OnInit {
|
||||
private _orderCompleted = new Subject<void>();
|
||||
|
||||
checkNotificationChannelControl$ = new BehaviorSubject<boolean>(true);
|
||||
|
||||
get shoppingCart() {
|
||||
return this.get((s) => s.shoppingCart);
|
||||
}
|
||||
@@ -192,7 +194,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
|
||||
communicationDetails$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getBuyerCommunicationDetails({ processId }))
|
||||
switchMap((processId) => this.domainCheckoutService.getBuyerCommunicationDetails({ processId })),
|
||||
map((communicationDetails) => communicationDetails ?? { email: undefined, mobile: undefined })
|
||||
);
|
||||
|
||||
notificationChannelLoading$ = new Subject<boolean>();
|
||||
@@ -317,26 +320,51 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
const communicationDetails = await this.communicationDetails$.pipe(first()).toPromise();
|
||||
this.control = fb.group({
|
||||
notificationChannel: new FormGroup({
|
||||
selected: new FormControl(notificationChannel),
|
||||
selected: new FormControl((notificationChannel & 3) === 3 || communicationDetails.email ? 1 : notificationChannel),
|
||||
email: new FormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
|
||||
mobile: new FormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async onNotificationChange(notificationChannels: NotificationChannel[]) {
|
||||
async onNotificationChange(notificationChannels?: NotificationChannel[]) {
|
||||
this.notificationChannelLoading$.next(true);
|
||||
|
||||
try {
|
||||
const control = this.control?.getRawValue();
|
||||
const notificationChannel = notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel;
|
||||
const notificationChannel = notificationChannels
|
||||
? (notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel)
|
||||
: control?.notificationChannel?.selected || 0;
|
||||
const processId = await this.applicationService.activatedProcessId$.pipe(first()).toPromise();
|
||||
const email = control?.notificationChannel?.email;
|
||||
const mobile = control?.notificationChannel?.mobile;
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email, mobile });
|
||||
|
||||
// Check if E-Mail and Mobilnumber is available if E-Mail or SMS checkbox is active
|
||||
if (notificationChannel === 3 && (!email || !mobile)) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else if (notificationChannel === 2 && !mobile) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else if (notificationChannel === 1 && !email) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else {
|
||||
this.checkNotificationChannelControl$.next(true);
|
||||
}
|
||||
|
||||
// NotificationChannel nur speichern, wenn Haken und Value gesetzt
|
||||
let setNotificationChannel = 0;
|
||||
if ((notificationChannel & 1) === 1 && email) {
|
||||
setNotificationChannel += 1;
|
||||
}
|
||||
if ((notificationChannel & 2) === 2 && mobile) {
|
||||
setNotificationChannel += 2;
|
||||
}
|
||||
|
||||
if (notificationChannel > 0) {
|
||||
this.setCommunicationDetails({ processId, notificationChannel, email, mobile });
|
||||
}
|
||||
this.domainCheckoutService.setNotificationChannels({
|
||||
processId,
|
||||
notificationChannels: (notificationChannel as NotificationChannel) || 0,
|
||||
notificationChannels: (setNotificationChannel as NotificationChannel) || 0,
|
||||
});
|
||||
} catch (error) {
|
||||
this.uiModal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim setzen des Benachrichtigungskanals' });
|
||||
@@ -345,6 +373,29 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
this.notificationChannelLoading$.next(false);
|
||||
}
|
||||
|
||||
setCommunicationDetails({
|
||||
processId,
|
||||
notificationChannel,
|
||||
email,
|
||||
mobile,
|
||||
}: {
|
||||
processId: number;
|
||||
notificationChannel: number;
|
||||
email: string;
|
||||
mobile: string;
|
||||
}) {
|
||||
const emailValid = this.control?.get('notificationChannel')?.get('email')?.valid;
|
||||
const mobileValid = this.control?.get('notificationChannel')?.get('mobile')?.valid;
|
||||
|
||||
if (notificationChannel === 3 && emailValid && mobileValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email, mobile });
|
||||
} else if (notificationChannel === 1 && emailValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email });
|
||||
} else if (notificationChannel === 2 && mobileValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, mobile });
|
||||
}
|
||||
}
|
||||
|
||||
openDummyModal(data?: any) {
|
||||
this.uiModal.open({
|
||||
content: CheckoutDummyComponent,
|
||||
@@ -761,6 +812,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
||||
} else {
|
||||
try {
|
||||
this.showOrderButtonSpinner = true;
|
||||
// Ticket #3287 Um nur E-Mail und SMS Benachrichtigungen zu setzen und um alle anderen Benachrichtigungskanäle wie z.B. Brief zu deaktivieren
|
||||
await this.onNotificationChange();
|
||||
const orders = await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
|
||||
const orderIds = orders.map((order) => order.id).join(',');
|
||||
this._orderCompleted.next();
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
}}</a>
|
||||
</div>
|
||||
|
||||
<div class="item-format">
|
||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
|
||||
@@ -70,10 +70,12 @@
|
||||
|
||||
<div class="product-details">
|
||||
<div class="info-row">
|
||||
<img class="order-icon" [src]="'/assets/images/Icon_' + order?.product?.format + '.svg'" alt="book-icon" />
|
||||
<span class="format">{{ order.product?.format }}</span>
|
||||
<ng-container *ngIf="order?.product?.format && order?.product?.formatDetail">
|
||||
<img class="order-icon" [src]="'/assets/images/Icon_' + order?.product?.format + '.svg'" alt="book-icon" />
|
||||
<span class="format">{{ order.product?.format }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="order?.product?.contributors">
|
||||
<span class="separator">|</span>
|
||||
<span class="separator" *ngIf="order?.product?.format && order?.product?.formatDetail">|</span>
|
||||
<span class="contributors">{{ order?.product?.contributors }}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -140,6 +140,7 @@ export class CheckoutSummaryComponent implements OnDestroy {
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
// Wenn es keine Bestellabschluss Prozesse mehr gibt, werden alle Orders aus dem Store removed um die Performance zu verbessern
|
||||
if (!checkoutProcess) {
|
||||
this.domainCheckoutService.removeAllOrders();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="item-format">
|
||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
|
||||
@@ -94,17 +94,16 @@ export class PurchasingOptionsListItemComponent {
|
||||
})
|
||||
);
|
||||
|
||||
price$ = this.fetchingAvailabilities$.pipe(
|
||||
filter((fetching) => !fetching),
|
||||
price$ = combineLatest([this.fetchingAvailabilities$, this._store.selectedFilterOption$]).pipe(
|
||||
filter(([fetching]) => !fetching),
|
||||
withLatestFrom(
|
||||
this._store.selectedFilterOption$,
|
||||
this.takeAwayAvailabilities$,
|
||||
this.pickUpAvailabilities$,
|
||||
this.deliveryAvailabilities$,
|
||||
this.deliveryDigAvailabilities$,
|
||||
this.deliveryB2bAvailabilities$
|
||||
),
|
||||
map(([_, option, takeAway, pickUp, delivery, deliveryDig, deliveryB2b]) => {
|
||||
map(([[_, option], takeAway, pickUp, delivery, deliveryDig, deliveryB2b]) => {
|
||||
let availability;
|
||||
switch (option) {
|
||||
case 'take-away':
|
||||
@@ -114,7 +113,12 @@ export class PurchasingOptionsListItemComponent {
|
||||
availability = pickUp;
|
||||
break;
|
||||
case 'delivery':
|
||||
availability = deliveryDig || delivery || deliveryB2b;
|
||||
if (deliveryDig || delivery) {
|
||||
availability = deliveryDig || delivery;
|
||||
} else {
|
||||
availability = deliveryB2b;
|
||||
option = 'b2b-delivery';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return this.item.availability?.price;
|
||||
|
||||
@@ -169,6 +169,7 @@ export class PurchasingOptionsListModalComponent implements OnInit {
|
||||
const deliveryDigAvailabilities = await this._store.deliveryDigAvailabilities$.pipe(first()).toPromise();
|
||||
const selectedTakeAwayBranch = await this._store.selectedTakeAwayBranch$.pipe(first()).toPromise();
|
||||
const selectedPickUpBranch = await this._store.selectedPickUpBranch$.pipe(first()).toPromise();
|
||||
let option = this._store.selectedFilterOption;
|
||||
|
||||
for (const item of items) {
|
||||
let availability;
|
||||
@@ -190,11 +191,12 @@ export class PurchasingOptionsListModalComponent implements OnInit {
|
||||
availability = deliveryDigAvailabilities[item.product.catalogProductNumber];
|
||||
} else if (deliveryB2bAvailabilities[item.product.catalogProductNumber]) {
|
||||
availability = deliveryB2bAvailabilities[item.product.catalogProductNumber];
|
||||
option = 'b2b-delivery';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const price = this._availability.getPriceForAvailability(this._store.selectedFilterOption, item.availability, availability);
|
||||
const price = this._availability.getPriceForAvailability(option, item.availability, availability);
|
||||
|
||||
// Negative Preise und nicht vorhandene Availability ignorieren
|
||||
if (price?.value?.value < 0 || !availability) {
|
||||
|
||||
@@ -39,7 +39,15 @@
|
||||
<h6 class="title">{{ item?.product?.contributors }} - {{ item?.product?.name }}</h6>
|
||||
<strong class="can-add-error" *ngIf="canAddError$ | async; let canAddError">{{ canAddError }}</strong>
|
||||
<div class="grow"></div>
|
||||
<div class="format">{{ item?.product?.formatDetail }}</div>
|
||||
<div class="format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail }}
|
||||
</div>
|
||||
|
||||
<div class="price">
|
||||
{{ price$ | async | currency: item?.catalogAvailability?.price?.value?.currency || 'EUR':'code' }}
|
||||
</div>
|
||||
|
||||
@@ -70,6 +70,14 @@ img.thumbnail {
|
||||
}
|
||||
}
|
||||
|
||||
.format {
|
||||
@apply flex flex-row items-center whitespace-nowrap;
|
||||
|
||||
img {
|
||||
@apply mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.quantity {
|
||||
@apply self-end flex flex-col justify-end;
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { SearchComponentStoreService } from '@store/search-component-store';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { NEVER, Observable, Subject } from 'rxjs';
|
||||
import { filter, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest, NEVER, Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, filter, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-search-main',
|
||||
@@ -34,9 +35,16 @@ export class CustomerSearchMainComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
this.processId$ = this._activatedRoute.parent.parent.data.pipe(map((d) => +d.processId));
|
||||
this._activatedRoute.url
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.processId$))
|
||||
.subscribe(([_, processId]) => this.removeBreadcrumbs(processId));
|
||||
combineLatest([this.processId$, this._activatedRoute.queryParams])
|
||||
.pipe(takeUntil(this._onDestroy$), debounceTime(50))
|
||||
.subscribe(([processId, queryParams]) => {
|
||||
const currentQueryParams = this._store.filter?.getQueryParams();
|
||||
if (!isEqual(currentQueryParams, queryParams)) {
|
||||
this._store.resetFilter(queryParams);
|
||||
}
|
||||
this.removeBreadcrumbs(processId);
|
||||
this.addOrUpdateBreadcrumb(processId, queryParams);
|
||||
});
|
||||
|
||||
this.filter$
|
||||
.pipe(
|
||||
@@ -58,6 +66,17 @@ export class CustomerSearchMainComponent implements OnInit, OnDestroy {
|
||||
this._breadcrumb.removeBreadcrumbsByKeyAndTags(processId, ['customer', 'details']);
|
||||
}
|
||||
|
||||
addOrUpdateBreadcrumb(processId: number, queryParams: Record<string, string>) {
|
||||
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name: 'Kundensuche',
|
||||
path: `/kunde/${processId}/customer/search`,
|
||||
params: queryParams,
|
||||
section: 'customer',
|
||||
tags: ['customer', 'filter', 'main'],
|
||||
});
|
||||
}
|
||||
|
||||
updateCustomerCreateQueryParams(value: string) {
|
||||
if (this.isValidEmail(value)) {
|
||||
this.customerCreateQueryParams = { email: value };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<ng-container *ngFor="let feed of feeds$ | async">
|
||||
<page-kpi-card *ngIf="feed?.type === 'kpi'" [feed]="feed"></page-kpi-card>
|
||||
<page-products-card class="tablet:col-span-2" *ngIf="feed?.type === 'products'" [feed]="feed"></page-products-card>
|
||||
<page-info-card class="tablet:col-span-2" *ngIf="feed?.type === 'info'" [feed]="feed"></page-info-card>
|
||||
</ng-container>
|
||||
|
||||
@@ -5,11 +5,12 @@ import { UiProgressModule } from '@ui/progress';
|
||||
import { UiSliderModule } from '@ui/slider';
|
||||
import { DashboardRoutingModule } from './dashboard-routing-module';
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { InfoCardComponent } from './info-card/info-card.component';
|
||||
import { KpiCardComponent } from './kpi-card/kpi-card.component';
|
||||
import { ProductsCardComponent } from './products-card/product-card.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [DashboardComponent, KpiCardComponent, ProductsCardComponent],
|
||||
declarations: [DashboardComponent, KpiCardComponent, ProductsCardComponent, InfoCardComponent],
|
||||
imports: [CommonModule, DashboardRoutingModule, UiProgressModule, UiSliderModule, ProductImageModule],
|
||||
exports: [DashboardComponent],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="info">
|
||||
<div class="title">{{ feed?.label }}</div>
|
||||
<div class="text-2xl font-bold mt-2">{{ feed?.headline }}</div>
|
||||
<div class="desc" [innerHtml]="feed?.desc"></div>
|
||||
</div>
|
||||
|
||||
<div class="info-item" *ngFor="let item of feed?.items">
|
||||
<div class="text-2xl font-bold">{{ item?.heading }}</div>
|
||||
<div class="desc" [innerHtml]="item?.text"></div>
|
||||
</div>
|
||||
|
||||
<img *ngIf="feed?.emphasize === 10" src="assets/images/recommendation_tag.png" class="emphasize-tag" />
|
||||
@@ -0,0 +1,25 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-2 bg-white rounded p-4 pb-10 shadow relative;
|
||||
}
|
||||
|
||||
.info {
|
||||
.title {
|
||||
@apply font-bold uppercase;
|
||||
color: var(--page-dashboard-card-title-color);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-info-card {
|
||||
.info-item {
|
||||
.desc p {
|
||||
@apply mt-4;
|
||||
}
|
||||
}
|
||||
|
||||
.desc p {
|
||||
@apply mt-4;
|
||||
}
|
||||
}
|
||||
.emphasize-tag {
|
||||
@apply absolute top-0 right-0 mr-8 -mt-1 w-12 z-popover;
|
||||
}
|
||||
15
apps/page/dashboard/src/lib/info-card/info-card.component.ts
Normal file
15
apps/page/dashboard/src/lib/info-card/info-card.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { InfoFeed } from '@domain/isa';
|
||||
|
||||
@Component({
|
||||
selector: 'page-info-card',
|
||||
templateUrl: 'info-card.component.html',
|
||||
styleUrls: ['info-card.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class InfoCardComponent {
|
||||
@Input()
|
||||
feed: InfoFeed;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
:host {
|
||||
@apply grid grid-flow-col gap-4 bg-white rounded p-4 shadow;
|
||||
@apply grid grid-flow-col gap-4 bg-white rounded p-4 shadow relative;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,3 +12,5 @@
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
</ui-slider>
|
||||
|
||||
<img *ngIf="feed?.emphasize === 10" src="assets/images/recommendation_tag.png" class="emphasize-tag" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-4 bg-white rounded p-4 shadow;
|
||||
@apply grid grid-flow-row gap-4 bg-white rounded p-4 shadow relative;
|
||||
|
||||
@screen tablet {
|
||||
@apply grid-flow-col grid-cols-2;
|
||||
@@ -33,3 +33,7 @@ ui-slider {
|
||||
::ng-deep page-products-card .desc ul li {
|
||||
@apply list-disc ml-8;
|
||||
}
|
||||
|
||||
.emphasize-tag {
|
||||
@apply absolute top-0 right-0 mr-8 -mt-1 w-12 z-popover;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ export class GoodsInCleanupListComponent implements OnInit, OnDestroy {
|
||||
|
||||
byProcessingStatusFn = (item: OrderItemListItemDTO) => item.processingStatus;
|
||||
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) => item.compartmentCode;
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) =>
|
||||
!!item.compartmentInfo ? `${item.compartmentCode}_${item.compartmentInfo}` : item.compartmentCode;
|
||||
|
||||
changeActionLoader$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
|
||||
@@ -47,7 +47,8 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
|
||||
|
||||
byProcessingStatusFn = (item: OrderItemListItemDTO) => item.processingStatus;
|
||||
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) => item.compartmentCode;
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) =>
|
||||
!!item.compartmentInfo ? `${item.compartmentCode}_${item.compartmentInfo}` : item.compartmentCode;
|
||||
|
||||
constructor(
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
@@ -154,6 +155,7 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (!response?.dialog) {
|
||||
this._toast.create({
|
||||
title: 'Abholfachremission',
|
||||
text: response?.message,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ export class GoodsInReservationComponent implements OnInit, OnDestroy {
|
||||
|
||||
byProcessingStatusFn = (item: OrderItemListItemDTO) => item.processingStatus;
|
||||
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) => item.compartmentCode;
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) =>
|
||||
!!item.compartmentInfo ? `${item.compartmentCode}_${item.compartmentInfo}` : item.compartmentCode;
|
||||
|
||||
constructor(
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
|
||||
@@ -3,10 +3,10 @@ import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { Config } from '@core/config';
|
||||
import { UiFilterAutocompleteProvider } from '@ui/filter';
|
||||
import { UiFilter, UiFilterAutocompleteProvider } from '@ui/filter';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Subject } from 'rxjs';
|
||||
import { filter, first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { combineLatest, Subject } from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { GoodsInSearchStore } from './goods-in-search.store';
|
||||
import { GoodsInSearchMainAutocompleteProvider } from './providers/goods-in-search-main-autocomplete.provider';
|
||||
@@ -35,14 +35,8 @@ export class GoodsInSearchComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
showFilterOverlay = false;
|
||||
|
||||
initialFilter$ = this._goodsInSearchStore.filter$.pipe(
|
||||
filter((filter) => !!filter),
|
||||
first()
|
||||
);
|
||||
|
||||
hasFilter$ = this._goodsInSearchStore.filter$.pipe(
|
||||
withLatestFrom(this.initialFilter$),
|
||||
map(([filter, initialFilter]) => !isEqual(filter?.getQueryParams(), initialFilter?.getQueryParams()))
|
||||
hasFilter$ = combineLatest([this._goodsInSearchStore.filter$, this._goodsInSearchStore.defaultSettings$]).pipe(
|
||||
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), UiFilter.create(defaultFilter).getQueryParams()))
|
||||
);
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { Config } from '@core/config';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { first, debounceTime } from 'rxjs/operators';
|
||||
import { GoodsInSearchStore } from '../goods-in-search.store';
|
||||
|
||||
@Component({
|
||||
@@ -37,9 +37,14 @@ export class GoodsInSearchMainComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
this._goodsInSearchStore.setQueryParams(this._activatedRoute.snapshot.queryParams);
|
||||
this._subscriptions.add(
|
||||
this._activatedRoute.queryParams.pipe(debounceTime(50)).subscribe((queryParams) => {
|
||||
this._goodsInSearchStore.setQueryParams(queryParams);
|
||||
|
||||
this.removeBreadcrumbs();
|
||||
this.removeBreadcrumbs();
|
||||
this.updateBreadcrumb(queryParams);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -76,7 +81,6 @@ export class GoodsInSearchMainComponent implements OnInit, OnDestroy {
|
||||
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'preview'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
resultCrumbs.forEach((crumb) => {
|
||||
this._breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
@@ -138,19 +142,20 @@ export class GoodsInSearchMainComponent implements OnInit, OnDestroy {
|
||||
this._goodsInSearchStore.search();
|
||||
}
|
||||
|
||||
async updateBreadcrumb() {
|
||||
async updateBreadcrumb(queryParams: Record<string, string>) {
|
||||
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this._config.get('process.ids.goodsIn'),
|
||||
name: 'Abholfach',
|
||||
path: '/filiale/goods/in',
|
||||
tags: ['goods-in', 'main', 'filter'],
|
||||
section: 'branch',
|
||||
params: this._goodsInSearchStore.filter?.getQueryParams(),
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
async updateQueryParams() {
|
||||
await this._router.navigate([], { queryParams: this._goodsInSearchStore.filter?.getQueryParams() });
|
||||
this.updateBreadcrumb();
|
||||
const queryParams = this._goodsInSearchStore.filter?.getQueryParams();
|
||||
await this._router.navigate([], { queryParams });
|
||||
this.updateBreadcrumb(queryParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ export class GoodsInSearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
byProcessingStatusFn = (item: OrderItemListItemDTO) => item.processingStatus;
|
||||
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) => item.compartmentCode;
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) =>
|
||||
!!item.compartmentInfo ? `${item.compartmentCode}_${item.compartmentInfo}` : item.compartmentCode;
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
|
||||
@@ -63,18 +63,17 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async applyFilter() {
|
||||
this._goodsOutSearchStore.clearResults();
|
||||
this._goodsOutSearchStore.setFilter(this.filter);
|
||||
this.message = undefined;
|
||||
|
||||
await this.updateQueryParams();
|
||||
|
||||
this._goodsOutSearchStore.searchResult$.pipe(takeUntil(this._onDestroy$), take(1)).subscribe((result) => {
|
||||
if (result.error) {
|
||||
if (result.results.error) {
|
||||
} else {
|
||||
if (result.hits > 0) {
|
||||
if (result.hits === 1) {
|
||||
const orderItem = result.result[0];
|
||||
if (result.results.hits > 0) {
|
||||
if (result.results.hits === 1) {
|
||||
const orderItem = result.results.result[0];
|
||||
this._router.navigate([this.getDetailsPath(orderItem)]);
|
||||
} else {
|
||||
this._router.navigate(['/kunde', this.processId, 'goods', 'out', 'results'], {
|
||||
@@ -91,7 +90,7 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
this._goodsOutSearchStore.search({});
|
||||
this._goodsOutSearchStore.search({ clear: true });
|
||||
}
|
||||
|
||||
async updateBreadcrumb() {
|
||||
|
||||
@@ -2,11 +2,10 @@ import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { Config } from '@core/config';
|
||||
import { UiFilterAutocompleteProvider } from '@ui/filter';
|
||||
import { UiFilter, UiFilterAutocompleteProvider } from '@ui/filter';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Subject } from 'rxjs';
|
||||
import { filter, first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { combineLatest, Subject } from 'rxjs';
|
||||
import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { GoodsOutSearchStore } from './goods-out-search.store';
|
||||
import { GoodsOutSearchMainAutocompleteProvider } from './providers/goods-out-search-main-autocomplete.provider';
|
||||
|
||||
@@ -34,14 +33,8 @@ export class GoodsOutSearchComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
showFilterOverlay = false;
|
||||
|
||||
initialFilter$ = this._goodsOutSearchStore.filter$.pipe(
|
||||
filter((filter) => !!filter),
|
||||
first()
|
||||
);
|
||||
|
||||
hasFilter$ = this._goodsOutSearchStore.filter$.pipe(
|
||||
withLatestFrom(this.initialFilter$),
|
||||
map(([filter, initialFilter]) => !isEqual(filter?.getQueryParams(), initialFilter?.getQueryParams()))
|
||||
hasFilter$ = combineLatest([this._goodsOutSearchStore.filter$, this._goodsOutSearchStore.defaultSettings$]).pipe(
|
||||
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), UiFilter.create(defaultFilter).getQueryParams()))
|
||||
);
|
||||
|
||||
processId$ = this._activatedRoute.data.pipe(map((data) => +data.processId));
|
||||
@@ -49,20 +42,18 @@ export class GoodsOutSearchComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private _goodsOutSearchStore: GoodsOutSearchStore,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private readonly _config: Config
|
||||
private _activatedRoute: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this._goodsOutSearchStore.loadSettings();
|
||||
|
||||
this.processId$.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._activatedRoute.queryParams)).subscribe(([processId, params]) => {
|
||||
if (params && Object.keys(params).length === 0) {
|
||||
this._goodsOutSearchStore.setQueryParams(params);
|
||||
this._goodsOutSearchStore.loadSettings();
|
||||
} else {
|
||||
// this._goodsOutSearchStore.resetFilter(params);
|
||||
}
|
||||
this.processId$.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._activatedRoute.queryParams)).subscribe(([processId]) => {
|
||||
// if (params && Object.keys(params).length === 0) {
|
||||
// console.log('params is empty');
|
||||
// this._goodsOutSearchStore.setQueryParams(params);
|
||||
// this._goodsOutSearchStore.loadSettings();
|
||||
// } else {
|
||||
// // this._goodsOutSearchStore.resetFilter(params);
|
||||
// }
|
||||
|
||||
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ListResponseArgsOfOrderItemListItemDTO, OrderItemListItemDTO, QuerySett
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { isResponseArgs } from '@utils/object';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { switchMap, mergeMap, withLatestFrom, filter, take, tap } from 'rxjs/operators';
|
||||
import { switchMap, filter, tap, first, map } from 'rxjs/operators';
|
||||
|
||||
export interface GoodsOutSearchState {
|
||||
defaultSettings?: QuerySettingsDTO;
|
||||
@@ -15,9 +15,9 @@ export interface GoodsOutSearchState {
|
||||
filter?: UiFilter;
|
||||
message?: string;
|
||||
fetching: boolean;
|
||||
silentFetching: boolean;
|
||||
hits: number;
|
||||
results: OrderItemListItemDTO[];
|
||||
searchOptions?: { take?: number; skip?: number };
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -52,23 +52,24 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
|
||||
|
||||
readonly fetching$ = this.select((s) => s.fetching);
|
||||
|
||||
get silentFetching() {
|
||||
return this.get((s) => s.silentFetching);
|
||||
}
|
||||
|
||||
get queryParams() {
|
||||
return this.get((s) => s.queryParams);
|
||||
}
|
||||
|
||||
readonly queryParams$ = this.select((s) => s.queryParams);
|
||||
|
||||
get searchOptions() {
|
||||
return this.get((s) => s.searchOptions);
|
||||
}
|
||||
set searchOptions(searchOptions: { take?: number; skip?: number }) {
|
||||
this.patchState({ searchOptions });
|
||||
}
|
||||
|
||||
private _searchResultSubject = new Subject<ListResponseArgsOfOrderItemListItemDTO>();
|
||||
private _searchResultSubject = new Subject<{ results: ListResponseArgsOfOrderItemListItemDTO; cached: boolean }>();
|
||||
|
||||
readonly searchResult$ = this._searchResultSubject.asObservable();
|
||||
|
||||
private _searchResultFromCacheSubject = new Subject<{ hits: number; results: OrderItemListItemDTO[] }>();
|
||||
|
||||
readonly searchResultFromCache$ = this._searchResultFromCacheSubject.asObservable();
|
||||
|
||||
private _searchResultClearedSubject = new Subject<void>();
|
||||
|
||||
readonly searchResultCleared = this._searchResultClearedSubject.asObservable();
|
||||
@@ -76,34 +77,24 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
|
||||
constructor(private _domainGoodsInService: DomainGoodsService, private _cache: CacheService) {
|
||||
super({
|
||||
fetching: false,
|
||||
silentFetching: false,
|
||||
hits: 0,
|
||||
results: [],
|
||||
});
|
||||
this.loadDefaultSettings();
|
||||
}
|
||||
|
||||
loadSettings = this.effect(($) =>
|
||||
$.pipe(
|
||||
switchMap(() =>
|
||||
this._domainGoodsInService.goodsOutQuerySettings().pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
this.setDefaultSettings(res.result);
|
||||
const filter = UiFilter.create(res.result);
|
||||
if (this.queryParams) {
|
||||
filter.fromQueryParams(this.queryParams);
|
||||
}
|
||||
this.setFilter(filter);
|
||||
},
|
||||
(err) => {
|
||||
console.error('GoodsInSearchStore.loadSettings()', err);
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
async loadDefaultSettings() {
|
||||
const defaultSettings = await this._domainGoodsInService
|
||||
.goodsOutQuerySettings()
|
||||
.pipe(map((res) => res?.result))
|
||||
.toPromise();
|
||||
|
||||
setDefaultSettings(defaultSettings: QuerySettingsDTO) {
|
||||
const filter = UiFilter.create(defaultSettings);
|
||||
if (this.queryParams) {
|
||||
filter.fromQueryParams(this.queryParams);
|
||||
}
|
||||
this.setFilter(filter);
|
||||
this.patchState({ defaultSettings });
|
||||
}
|
||||
|
||||
@@ -115,26 +106,16 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
|
||||
}
|
||||
}
|
||||
|
||||
resetFilter(defaultQueryParams?: Record<string, string>) {
|
||||
resetFilter(queryParams?: Record<string, string>) {
|
||||
const filter = UiFilter.create(this.defaultSettings);
|
||||
|
||||
if (!!defaultQueryParams) {
|
||||
filter?.fromQueryParams(defaultQueryParams);
|
||||
if (!!queryParams) {
|
||||
filter?.fromQueryParams(queryParams);
|
||||
}
|
||||
|
||||
this.patchState({ queryParams });
|
||||
this.setFilter(filter);
|
||||
}
|
||||
|
||||
clearResults() {
|
||||
this.patchState({
|
||||
fetching: false,
|
||||
hits: 0,
|
||||
message: undefined,
|
||||
results: [],
|
||||
});
|
||||
this._searchResultClearedSubject.next();
|
||||
}
|
||||
|
||||
setQueryParams(queryParams: Record<string, string>) {
|
||||
this.patchState({ queryParams });
|
||||
if (this.filter instanceof UiFilter) {
|
||||
@@ -143,73 +124,116 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
|
||||
}
|
||||
}
|
||||
|
||||
searchRequest(options?: { take?: number; skip?: number; reload?: boolean }) {
|
||||
return this.filter$.pipe(
|
||||
filter((f) => f instanceof UiFilter),
|
||||
take(1),
|
||||
mergeMap((filter) =>
|
||||
this._domainGoodsInService.searchWarenausgabe({
|
||||
...filter.getQueryToken(),
|
||||
skip: options.reload ? 0 : options?.skip ?? this.results.length,
|
||||
take: options.reload ? this.results.length : options?.take ?? 50,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
// searchRequest(options: { skip?: number; take?: number }) {
|
||||
// console.log('GoodsInSearchStore.searchRequest()', options);
|
||||
// return this.filter$.pipe(
|
||||
// filter((f) => f instanceof UiFilter),
|
||||
// take(1),
|
||||
// mergeMap((filter) => {
|
||||
// console.log('GoodsInSearchStore.searchRequest()', filter);
|
||||
// return this._domainGoodsInService.searchWarenausgabe({
|
||||
// ...filter.getQueryToken(),
|
||||
// skip: options.skip,
|
||||
// take: options.take,
|
||||
// });
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
search = this.effect((options$: Observable<{ siletReload?: boolean }>) =>
|
||||
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
|
||||
options$.pipe(
|
||||
tap((options) => {
|
||||
switchMap((options) => {
|
||||
return this.results$.pipe(
|
||||
map((results) => [options, results]),
|
||||
first()
|
||||
);
|
||||
}),
|
||||
switchMap(([options, results]) => {
|
||||
return this.filter$.pipe(
|
||||
filter((f) => f instanceof UiFilter),
|
||||
map((filter) => [options, results, filter]),
|
||||
first()
|
||||
);
|
||||
}),
|
||||
tap(([options, _, filter]: [{ clear?: boolean; siletReload?: boolean }, OrderItemListItemDTO[], UiFilter]) => {
|
||||
if (!options?.siletReload) {
|
||||
this.patchState({ fetching: true });
|
||||
} else {
|
||||
this.patchState({ silentFetching: true });
|
||||
}
|
||||
|
||||
if (options?.clear) {
|
||||
this._searchResultClearedSubject.next();
|
||||
this._cache.delete(filter?.getQueryToken());
|
||||
}
|
||||
}),
|
||||
withLatestFrom(this.results$),
|
||||
switchMap(([_options, _results]) => {
|
||||
const queryToken = this.filter?.getQueryToken();
|
||||
switchMap(([options, results, filter]) => {
|
||||
const queryToken = filter?.getQueryToken() ?? {};
|
||||
|
||||
if (queryToken && this._cache.get(queryToken)) {
|
||||
const cached = this._cache.get(queryToken);
|
||||
let cachedResultCount: number;
|
||||
|
||||
this.patchState(cached);
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
return this.searchRequest({ ...this.searchOptions, reload: _options.siletReload }).pipe(
|
||||
if (options.clear) {
|
||||
queryToken.skip = 0;
|
||||
queryToken.take = 50;
|
||||
} else if (options.siletReload) {
|
||||
queryToken.skip = 0;
|
||||
queryToken.take = cachedResultCount || results.length || 50;
|
||||
} else {
|
||||
queryToken.skip = results.length;
|
||||
queryToken.take = 50;
|
||||
}
|
||||
|
||||
return this._domainGoodsInService.searchWarenausgabe(queryToken).pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
let results: OrderItemListItemDTO[] = [];
|
||||
if (_options.siletReload) {
|
||||
results = res.result;
|
||||
let _results: OrderItemListItemDTO[] = [];
|
||||
if (options.siletReload) {
|
||||
_results = res.result;
|
||||
} else if (options.clear) {
|
||||
_results = res.result;
|
||||
} else {
|
||||
results = [...(_results ?? []), ...(res.result ?? [])];
|
||||
_results = [...results, ...(res.result ?? [])];
|
||||
}
|
||||
|
||||
this.patchState({
|
||||
hits: res.hits,
|
||||
results,
|
||||
results: _results,
|
||||
fetching: false,
|
||||
silentFetching: false,
|
||||
});
|
||||
|
||||
this._cache.set(filter?.getQueryToken(), {
|
||||
hits: res.hits,
|
||||
results: _results,
|
||||
fetching: false,
|
||||
});
|
||||
|
||||
if (queryToken) {
|
||||
this._cache.set(queryToken, {
|
||||
hits: res.hits,
|
||||
results,
|
||||
fetching: false,
|
||||
});
|
||||
}
|
||||
|
||||
this._searchResultSubject.next(res);
|
||||
this._searchResultSubject.next({ results: res, cached });
|
||||
},
|
||||
(err: Error) => {
|
||||
if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
|
||||
this._searchResultSubject.next(err.error);
|
||||
this._searchResultSubject.next({ results: err.error, cached });
|
||||
} else {
|
||||
this._searchResultSubject.next({
|
||||
error: true,
|
||||
message: err.message,
|
||||
results: {
|
||||
error: true,
|
||||
message: err.message,
|
||||
},
|
||||
cached,
|
||||
});
|
||||
}
|
||||
this.patchState({ fetching: false });
|
||||
this.patchState({ fetching: false, silentFetching: false });
|
||||
console.error('GoodsInSearchStore.search()', err);
|
||||
}
|
||||
)
|
||||
@@ -217,38 +241,4 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
reload = this.effect(($) =>
|
||||
$.pipe(
|
||||
tap((_) => this.patchState({ fetching: true })),
|
||||
withLatestFrom(this.results$),
|
||||
switchMap(([_, results]) =>
|
||||
this.searchRequest({ take: results?.length ?? 0, skip: 0 }).pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
this.patchState({
|
||||
hits: res.hits,
|
||||
results: res.result,
|
||||
fetching: false,
|
||||
});
|
||||
|
||||
this._searchResultSubject.next(res);
|
||||
},
|
||||
(err: Error) => {
|
||||
if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
|
||||
this._searchResultSubject.next(err.error);
|
||||
} else {
|
||||
this._searchResultSubject.next({
|
||||
error: true,
|
||||
message: err.message,
|
||||
});
|
||||
}
|
||||
this.patchState({ fetching: false });
|
||||
console.error('GoodsInSearchStore.reload()', err);
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRe
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { debounce } from 'lodash';
|
||||
import { combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, first, map } from 'rxjs/operators';
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, first, map, withLatestFrom } from 'rxjs/operators';
|
||||
import { GoodsOutSearchStore } from '../goods-out-search.store';
|
||||
|
||||
@Component({
|
||||
@@ -18,6 +18,8 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
|
||||
|
||||
loading$ = this._goodsOutSearchStore.fetching$;
|
||||
|
||||
queryChanged$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
message: string;
|
||||
|
||||
lastProcessId: number | undefined;
|
||||
@@ -39,18 +41,25 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Clear scroll position
|
||||
localStorage.removeItem(`SCROLL_POSITION_${this.processId}`);
|
||||
|
||||
this._subscriptions.add(
|
||||
this._goodsOutSearchStore.filter$.subscribe(() => {
|
||||
this._goodsOutSearchStore.filter$.subscribe((f) => {
|
||||
this._cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
|
||||
this._subscriptions.add(
|
||||
combineLatest([this.processId$, this._activatedRoute.queryParams])
|
||||
.pipe(debounceTime(50))
|
||||
.subscribe(([processId, queryParams]) => {
|
||||
this._goodsOutSearchStore.setQueryParams(queryParams);
|
||||
.pipe(debounceTime(50), withLatestFrom(this.queryChanged$))
|
||||
.subscribe(([[processId, queryParams], queryChanged]) => {
|
||||
if (!isEqual(queryParams, this._goodsOutSearchStore.filter?.getQueryParams()) && !queryChanged) {
|
||||
this._goodsOutSearchStore.resetFilter(queryParams);
|
||||
}
|
||||
this.queryChanged$.next(false);
|
||||
this.removeBreadcrumbs(processId);
|
||||
this.updateBreadcrumb(processId, queryParams);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -79,16 +88,15 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async search() {
|
||||
this._goodsOutSearchStore.clearResults();
|
||||
await this.updateQueryParams(this.processId);
|
||||
this.message = undefined;
|
||||
|
||||
this._goodsOutSearchStore.searchResult$.pipe(first()).subscribe((result) => {
|
||||
if (result.error) {
|
||||
if (result.results.error) {
|
||||
} else {
|
||||
if (result.hits > 0) {
|
||||
if (result.hits === 1) {
|
||||
const orderItem = result.result[0];
|
||||
if (result.results.hits > 0) {
|
||||
if (result.results.hits === 1) {
|
||||
const orderItem = result.results.result[0];
|
||||
this._router.navigate([this.getDetailsPath(orderItem, this.processId)]);
|
||||
} else {
|
||||
this._router.navigate(['/kunde', this.processId, 'goods', 'out', 'results'], {
|
||||
@@ -103,8 +111,7 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
|
||||
this._cdr.markForCheck();
|
||||
});
|
||||
|
||||
this._goodsOutSearchStore.searchOptions = { take: 50, skip: 0 };
|
||||
this._goodsOutSearchStore.search({});
|
||||
this._goodsOutSearchStore.search({ clear: true });
|
||||
}
|
||||
|
||||
async updateBreadcrumb(processId: number, params: Record<string, string>) {
|
||||
@@ -131,5 +138,8 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
|
||||
: `/kunde/${processId}/goods/out/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
|
||||
}
|
||||
|
||||
queryChangeDebounce = debounce(() => this.updateQueryParams(this.processId), 500);
|
||||
queryChangeDebounce = debounce(async () => {
|
||||
this.queryChanged$.next(true);
|
||||
await this.updateQueryParams(this.processId);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
(reachEnd)="loadMore()"
|
||||
[deltaEnd]="150"
|
||||
[itemLength]="itemLength$ | async"
|
||||
[initialScroll]="scrollTo"
|
||||
>
|
||||
<ng-container *ngIf="processId$ | async; let processId">
|
||||
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
|
||||
|
||||
@@ -3,13 +3,14 @@ import { debounceTime, first, map, shareReplay, takeUntil, withLatestFrom } from
|
||||
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { GoodsOutSearchStore } from '../goods-out-search.store';
|
||||
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, Subject, Subscription } from 'rxjs';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { CommandService } from '@core/command';
|
||||
import { OrderItemsContext } from '@domain/oms';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { UiScrollContainerComponent } from '@ui/scroll-container';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
|
||||
export interface GoodsOutSearchResultsState {
|
||||
selectedOrderItemSubsetIds: number[];
|
||||
@@ -64,7 +65,8 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
|
||||
|
||||
byProcessingStatusFn = (item: OrderItemListItemDTO) => item.processingStatus;
|
||||
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) => item.compartmentCode;
|
||||
byCompartmentCodeFn = (item: OrderItemListItemDTO) =>
|
||||
!!item.compartmentInfo ? `${item.compartmentCode}_${item.compartmentInfo}` : item.compartmentCode;
|
||||
|
||||
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
|
||||
|
||||
@@ -74,6 +76,10 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
|
||||
|
||||
trackByFn = (item: OrderItemListItemDTO) => `${item.orderId}${item.orderItemId}${item.orderItemSubsetId}`;
|
||||
|
||||
private _searchResultSubscription: Subscription;
|
||||
|
||||
scrollTo: number;
|
||||
|
||||
constructor(
|
||||
private _goodsOutSearchStore: GoodsOutSearchStore,
|
||||
private _router: Router,
|
||||
@@ -87,23 +93,47 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
|
||||
});
|
||||
}
|
||||
|
||||
saveScrollPosition(processId: number, scrollPosition: number) {
|
||||
localStorage.setItem(`SCROLL_POSITION_${processId}`, JSON.stringify(scrollPosition));
|
||||
}
|
||||
|
||||
getScrollPosition(processId: number): number | undefined {
|
||||
try {
|
||||
const scroll_position = localStorage.getItem(`SCROLL_POSITION_${processId}`);
|
||||
return scroll_position ? JSON.parse(scroll_position) : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
removeScrollPosition(processId: number) {
|
||||
localStorage.removeItem(`SCROLL_POSITION_${processId}`);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.processId$
|
||||
.pipe(takeUntil(this._onDestroy$), debounceTime(1), withLatestFrom(this._activatedRoute.queryParams))
|
||||
.subscribe(([processId, params]) => {
|
||||
this._goodsOutSearchStore.setQueryParams(params);
|
||||
this.updateBreadcrumb(processId, params);
|
||||
|
||||
this.initInitialSearch(processId, params);
|
||||
this.createBreadcrumb(processId, params);
|
||||
this.removeBreadcrumbs(processId);
|
||||
|
||||
if (this.previousProcessId && processId !== this.previousProcessId) {
|
||||
this._goodsOutSearchStore.clearResults();
|
||||
this._goodsOutSearchStore.search({ siletReload: true });
|
||||
.pipe(takeUntil(this._onDestroy$), debounceTime(10), withLatestFrom(this._activatedRoute.queryParams))
|
||||
.subscribe(async ([processId, params]) => {
|
||||
if (this.previousProcessId && this.previousProcessId !== processId) {
|
||||
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
|
||||
}
|
||||
|
||||
this.previousProcessId = processId;
|
||||
if (!(this._goodsOutSearchStore.filter instanceof UiFilter)) {
|
||||
await this._goodsOutSearchStore.loadDefaultSettings();
|
||||
}
|
||||
|
||||
this._goodsOutSearchStore.resetFilter(params);
|
||||
this.updateBreadcrumb(processId, params);
|
||||
|
||||
if (this.previousProcessId !== processId) {
|
||||
this.initInitialSearch(processId);
|
||||
this.createBreadcrumb(processId, params);
|
||||
this.removeBreadcrumbs(processId);
|
||||
|
||||
this.previousProcessId = processId;
|
||||
|
||||
this._goodsOutSearchStore.search({ siletReload: true });
|
||||
}
|
||||
});
|
||||
|
||||
this._goodsOutSearchStore.searchResultCleared.pipe(takeUntil(this._onDestroy$)).subscribe((_) => this.clearSelectedItems());
|
||||
@@ -113,7 +143,12 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
|
||||
// this.updateBreadcrumb(this._goodsOutSearchStore.filter?.getQueryParams());
|
||||
if (this._searchResultSubscription) {
|
||||
this._searchResultSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.updateBreadcrumb(this.previousProcessId, this._goodsOutSearchStore.filter?.getQueryParams());
|
||||
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
|
||||
}
|
||||
|
||||
async removeBreadcrumbs(processId: number) {
|
||||
@@ -142,9 +177,6 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
|
||||
}
|
||||
|
||||
async updateBreadcrumb(processId: number, queryParams: Record<string, string>) {
|
||||
const scroll_position = this.scrollContainer?.scrollPos;
|
||||
const take = this._goodsOutSearchStore.results?.length;
|
||||
|
||||
if (queryParams) {
|
||||
const crumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'results', 'filter'])
|
||||
@@ -152,7 +184,7 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
|
||||
.toPromise();
|
||||
|
||||
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
|
||||
const params = { ...queryParams, scroll_position, take };
|
||||
const params = { ...queryParams };
|
||||
|
||||
for (const crumb of crumbs) {
|
||||
this._breadcrumb.patchBreadcrumb(crumb.id, {
|
||||
@@ -169,36 +201,53 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
|
||||
return input?.replace('ORD:', '') ?? 'Alle';
|
||||
}
|
||||
|
||||
initInitialSearch(processId: number, params: Record<string, string>) {
|
||||
if (this._goodsOutSearchStore.hits === 0) {
|
||||
this._goodsOutSearchStore.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
|
||||
if (result.hits === 0) {
|
||||
await this._router.navigate([`/kunde/${processId}/goods/out`], {
|
||||
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
|
||||
});
|
||||
} else {
|
||||
await this.createBreadcrumb(processId, params);
|
||||
if (result.hits === 1) {
|
||||
await this.navigateToDetails(processId, result.result[0]);
|
||||
} else {
|
||||
if (!!this._goodsOutSearchStore.searchOptions?.take) {
|
||||
this._goodsOutSearchStore.searchOptions = undefined;
|
||||
this.scrollContainer.scrollTo(Number(scroll_position ?? 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this._goodsOutSearchStore.search({});
|
||||
initInitialSearch(processId: number) {
|
||||
if (this._searchResultSubscription) {
|
||||
this._searchResultSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
const { scroll_position, take } = this._goodsOutSearchStore.queryParams;
|
||||
if (!!take && !!scroll_position) {
|
||||
this._goodsOutSearchStore.searchOptions = { take: Number(take) };
|
||||
}
|
||||
this._searchResultSubscription = new Subscription();
|
||||
|
||||
this._searchResultSubscription.add(
|
||||
this._goodsOutSearchStore.searchResult$.subscribe(async (result) => {
|
||||
const queryParams = this._goodsOutSearchStore.filter?.getQueryParams();
|
||||
if (result.results.hits === 0) {
|
||||
await this._router.navigate([`/kunde/${processId}/goods/out`], {
|
||||
queryParams,
|
||||
});
|
||||
} else {
|
||||
await this.createBreadcrumb(processId, queryParams);
|
||||
if (result.results.hits === 1) {
|
||||
await this.navigateToDetails(processId, result.results.result[0]);
|
||||
} else if (!result.cached) {
|
||||
const scrollPos = this.getScrollPosition(processId) || 0;
|
||||
this.scrollTo = scrollPos;
|
||||
this.scrollContainer?.scrollTo(scrollPos);
|
||||
this.removeScrollPosition(processId);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._searchResultSubscription.add(
|
||||
this._goodsOutSearchStore.searchResultFromCache$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
|
||||
if (result?.hits > 0) {
|
||||
const scrollPos = this.getScrollPosition(processId) || 0;
|
||||
this.scrollTo = scrollPos;
|
||||
this.scrollContainer?.scrollTo(scrollPos);
|
||||
this.removeScrollPosition(processId);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async loadMore() {
|
||||
if (this._goodsOutSearchStore.hits > this._goodsOutSearchStore.results.length && !this._goodsOutSearchStore.fetching) {
|
||||
if (
|
||||
this._goodsOutSearchStore.hits > this._goodsOutSearchStore.results.length &&
|
||||
!this._goodsOutSearchStore.fetching &&
|
||||
!this._goodsOutSearchStore.silentFetching
|
||||
) {
|
||||
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
|
||||
this._goodsOutSearchStore.search({});
|
||||
}
|
||||
}
|
||||
@@ -248,8 +297,9 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
|
||||
items: this.selectedItems,
|
||||
};
|
||||
try {
|
||||
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
|
||||
await this._commandService.handleCommand(action.command, commandData);
|
||||
this._goodsOutSearchStore.reload();
|
||||
this._goodsOutSearchStore.search({ siletReload: true, clear: true });
|
||||
this.clearSelectedItems();
|
||||
this.loadingFetchedActionButton$.next(false);
|
||||
} catch (error) {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="grid grid-flow-row gap-1">
|
||||
<div class="flex flex-row">
|
||||
<div class="w-32">Format</div>
|
||||
<div data-name="format" class="flex-grow">
|
||||
<div data-name="format" class="flex-grow" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img class="inline" src="/assets/images/Icon_{{ item.product.format }}.svg" [alt]="item.product.formatDetail" />
|
||||
<span class="ml-1 font-bold">{{ item.product.formatDetail }}</span>
|
||||
</div>
|
||||
|
||||
@@ -14,15 +14,11 @@
|
||||
></ui-filter>
|
||||
|
||||
<div class="sticky-cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter()" [disabled]="store.fetching$ | async">
|
||||
<span>
|
||||
Filter zurücksetzen
|
||||
</span>
|
||||
<button class="cta-reset-filter" (click)="resetFilter()">
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
<button class="apply-filter" (click)="applyFilter(filter)" [disabled]="store.fetching$ | async">
|
||||
<ui-spinner [show]="store.fetching$ | async">
|
||||
Filter anwenden
|
||||
</ui-spinner>
|
||||
<button class="apply-filter" (click)="applyFilter(filter)">
|
||||
Filter anwenden
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -34,13 +34,11 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row gap-1 w-48">
|
||||
<div class="overflow-hidden overflow-ellipsis whitespace-nowrap" *ngIf="!!item.formatDetail">
|
||||
<img
|
||||
*ngIf="!!item.dto.product.format"
|
||||
class="inline"
|
||||
src="/assets/images/Icon_{{ item.dto.product.format }}.svg"
|
||||
[alt]="item.formatDetail"
|
||||
/>
|
||||
<div
|
||||
class="overflow-hidden overflow-ellipsis whitespace-nowrap"
|
||||
*ngIf="!!item.format && !!item.formatDetail && item.format !== 'UNKNOWN'"
|
||||
>
|
||||
<img class="inline" src="/assets/images/Icon_{{ item.dto.product.format }}.svg" [alt]="item.formatDetail" />
|
||||
<span class="ml-1 font-bold">{{ item.formatDetail }}</span>
|
||||
</div>
|
||||
<div class="font-bold">
|
||||
|
||||
@@ -461,10 +461,10 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
this.clearCache();
|
||||
await this._router.navigate([], {
|
||||
queryParams: {
|
||||
source: this.selectedSource,
|
||||
supplier: supplier.id,
|
||||
scroll_position: 0,
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -478,9 +478,9 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
|
||||
await this._router.navigate([], {
|
||||
queryParams: {
|
||||
source,
|
||||
supplier: this.selectedSupplier.id,
|
||||
scroll_position: 0,
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@
|
||||
<ng-container>
|
||||
<a
|
||||
*ngIf="showStartRemissionAction$ | async"
|
||||
routerLink="../create"
|
||||
[class.disabled]="fetching$ | async"
|
||||
[routerLink]="(fetching$ | async) ? null : '../create'"
|
||||
[queryParams]="queryParams$ | async"
|
||||
routerLinkParam
|
||||
class="bg-brand text-white font-bold text-lg px-6 py-3 rounded-full"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
button:disabled {
|
||||
.disabled {
|
||||
@apply cursor-not-allowed bg-inactive-branch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { Config } from '@core/config';
|
||||
import { DomainRemissionService } from '@domain/remission';
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator';
|
||||
|
||||
import { RemissionComponent } from './remission.component';
|
||||
|
||||
describe('RemissionComponent', () => {
|
||||
xdescribe('RemissionComponent', () => {
|
||||
let spectator: Spectator<RemissionComponent>;
|
||||
const createComponent = createComponentFactory({
|
||||
component: RemissionComponent,
|
||||
mocks: [BreadcrumbService, Config, ActivatedRoute, ApplicationService, Router],
|
||||
mocks: [BreadcrumbService, Config, ActivatedRoute, ApplicationService, Router, DomainRemissionService],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -44,3 +44,8 @@ h1 {
|
||||
@apply text-brand border-2 border-none bg-white font-bold text-lg px-4 py-2 rounded-full mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-article-list-modal ui-slider .ui-slider-wrapper {
|
||||
display: grid !important;
|
||||
grid-auto-flow: column !important;
|
||||
}
|
||||
|
||||
@@ -19,10 +19,9 @@
|
||||
uiFocus
|
||||
type="text"
|
||||
[ngModel]="inputValue$ | async"
|
||||
(ngModelChange)="inputValueSubject.next($event)"
|
||||
(ngModelChange)="inputValueSubject.next($event); setCompartmentInfo(inputValue)"
|
||||
placeholder="..."
|
||||
[size]="controlSize$ | async"
|
||||
(blur)="setCompartmentInfo(inputValue)"
|
||||
maxlength="15"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="goods-in-out-order-details-action-wrapper">
|
||||
<button
|
||||
[disabled]="changeActionDisabled$ | async"
|
||||
[disabled]="addToPreviousCompartmentActionDisabled$ | async"
|
||||
*ngIf="addToPreviousCompartmentAction$ | async; let action"
|
||||
class="cta-action shadow-action"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
button {
|
||||
&:disabled {
|
||||
@apply bg-inactive-customer border-inactive-customer text-white;
|
||||
@apply bg-inactive-customer border-inactive-customer text-white cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { OrderItemsContext } from '@domain/oms';
|
||||
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { BehaviorSubject, combineLatest, merge, of, Subscription } from 'rxjs';
|
||||
import { first, switchMap } from 'rxjs/operators';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { SharedGoodsInOutOrderDetailsCoversComponent } from './goods-in-out-order-details-covers';
|
||||
import { SharedGoodsInOutOrderDetailsItemComponent } from './goods-in-out-order-details-item';
|
||||
import { SharedGoodsInOutOrderDetailsTagsComponent } from './goods-in-out-order-details-tags';
|
||||
@@ -53,6 +53,10 @@ export class SharedGoodsInOutOrderDetailsComponent extends SharedGoodsInOutOrder
|
||||
changeActionLoader$ = new BehaviorSubject<string>(undefined);
|
||||
changeActionDisabled$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
addToPreviousCompartmentActionDisabled$ = combineLatest([this.compartmentInfo$, this.changeActionDisabled$]).pipe(
|
||||
map(([compartmentInfo, changeActionDisabled]) => !!compartmentInfo || changeActionDisabled)
|
||||
);
|
||||
|
||||
@Output()
|
||||
actionHandled = new EventEmitter<{
|
||||
orderItemsContext: OrderItemsContext;
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
</div>
|
||||
<div class="item-data-wrapper">
|
||||
<div class="item-data">
|
||||
<div class="item-format">
|
||||
<div
|
||||
class="item-format"
|
||||
[class.invisible]="!item?.product?.format || !item?.product?.formatDetail || item?.product?.format === 'UNKNOWN'"
|
||||
>
|
||||
<img
|
||||
class="format-icon"
|
||||
*ngIf="item?.product?.format && item?.product?.format !== 'UNKNOWN'"
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="nc-generate" *ngIf="channelActionName && notificationChannels.length !== 2">
|
||||
<button [disabled]="channelActionLoading" type="button" (click)="channelActionEvent.emit(notificationChannels)">
|
||||
<button
|
||||
[disabled]="channelActionLoading || emailControl?.errors?.required || emailControl?.errors?.pattern"
|
||||
type="button"
|
||||
(click)="channelActionEvent.emit(notificationChannels)"
|
||||
>
|
||||
{{ channelActionName }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -42,7 +46,17 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="nc-generate" *ngIf="channelActionName">
|
||||
<button [disabled]="channelActionLoading" type="button" (click)="channelActionEvent.emit(notificationChannels)">
|
||||
<button
|
||||
[disabled]="
|
||||
channelActionLoading ||
|
||||
mobileControl?.errors?.required ||
|
||||
mobileControl?.errors?.pattern ||
|
||||
emailControl?.errors?.required ||
|
||||
emailControl?.errors?.pattern
|
||||
"
|
||||
type="button"
|
||||
(click)="channelActionEvent.emit(notificationChannels)"
|
||||
>
|
||||
{{ channelActionName }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
.nc-generate {
|
||||
button:disabled {
|
||||
@apply text-disabled-customer;
|
||||
@apply text-disabled-customer cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@
|
||||
|
||||
.nc-generate {
|
||||
button:disabled {
|
||||
@apply text-disabled-branch;
|
||||
@apply text-disabled-branch cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
|
||||
}
|
||||
|
||||
get displayEmail() {
|
||||
return !!(this.notificationChannelControl.value & 1) && this.emailControl && !this.communicationDetails?.email;
|
||||
return !!(this.notificationChannelControl.value & 1) && this.emailControl;
|
||||
}
|
||||
|
||||
get mobileControl() {
|
||||
@@ -48,7 +48,7 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
|
||||
}
|
||||
|
||||
get displayMobile() {
|
||||
return !!(this.notificationChannelControl.value & 2) && this.mobileControl && !this.communicationDetails?.mobile;
|
||||
return !!(this.notificationChannelControl.value & 2) && this.mobileControl;
|
||||
}
|
||||
|
||||
get displayToggle() {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AbstractControl, ValidatorFn, Validators } from '@angular/forms';
|
||||
import { UiValidators } from '@ui/validators';
|
||||
|
||||
// RFC 5322 Official Standard
|
||||
const emailRegexPattern = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
|
||||
const emailRegexPattern = /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
|
||||
|
||||
export const emailNotificationValidator: ValidatorFn = (control: AbstractControl) => {
|
||||
if (control.parent) {
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
}
|
||||
|
||||
button.cancel {
|
||||
@apply px-5 py-3 bg-white text-brand text-cta-l rounded-full border-none outline-none;
|
||||
@apply bg-white text-brand border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none;
|
||||
}
|
||||
|
||||
button.confirm {
|
||||
@apply px-5 py-3 bg-brand text-white text-cta-l rounded-full border-none outline-none;
|
||||
@apply bg-brand text-white border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export class OpenDialogInterceptor implements HttpInterceptor {
|
||||
if (response?.body?.dialog?.area === 'dialog') {
|
||||
this.openDialog(response.body.dialog);
|
||||
}
|
||||
if (response?.body?.dialog?.area === 'toast') {
|
||||
if (response?.body?.dialog?.area === 'Toaster') {
|
||||
this.createToast(response.body.dialog);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export class OpenDialogInterceptor implements HttpInterceptor {
|
||||
if (error?.error?.dialog?.area === 'dialog') {
|
||||
this.openDialog(error.error.dialog);
|
||||
}
|
||||
if (error?.error?.dialog?.area === 'toast') {
|
||||
if (error?.error?.dialog?.area === 'Toaster') {
|
||||
this.createToast(error.error.dialog);
|
||||
}
|
||||
return throwError(error);
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-scroll-container',
|
||||
@@ -6,7 +17,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Ou
|
||||
styleUrls: ['scroll-container.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class UiScrollContainerComponent {
|
||||
export class UiScrollContainerComponent implements OnInit {
|
||||
@ViewChild('scrollContainer', { read: ElementRef, static: false })
|
||||
scrollContainer: ElementRef;
|
||||
|
||||
@@ -35,12 +46,20 @@ export class UiScrollContainerComponent {
|
||||
|
||||
@Input() useLoadAnimation = true;
|
||||
|
||||
@Input() initialScroll: number;
|
||||
|
||||
get containerHeightString() {
|
||||
return `calc(100vh - ${this.containerHeight}rem)`;
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.initialScroll !== undefined) {
|
||||
this.scrollTo(this.initialScroll);
|
||||
}
|
||||
}
|
||||
|
||||
createSkeletons() {
|
||||
if (this.itemLength && this.itemLength !== 0) {
|
||||
return Array.from(Array(this.itemLength - 1), (_, i) => i);
|
||||
|
||||
9025
package-lock.json
generated
9025
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -66,7 +66,6 @@
|
||||
"lodash": "^4.17.21",
|
||||
"ng2-pdf-viewer": "^6.4.1",
|
||||
"ngx-device-detector": "^2.0.6",
|
||||
"object-hash": "^2.1.1",
|
||||
"rxjs": "^6.6.7",
|
||||
"socket.io": "^2.2.0",
|
||||
"tslib": "^2.0.0",
|
||||
@@ -81,16 +80,16 @@
|
||||
"@angular/cli": "^12.2.13",
|
||||
"@angular/compiler-cli": "~12.2.14",
|
||||
"@angular/language-service": "~12.2.14",
|
||||
"@ngneat/spectator": "^9.0.0",
|
||||
"@ngneat/spectator": "^8.0.0",
|
||||
"@types/jasmine": "~3.6.0",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/node": "^12.11.1",
|
||||
"@types/node": "^18.6.1",
|
||||
"@types/object-hash": "^2.1.0",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"codelyzer": "^6.0.0",
|
||||
"husky": "^4.2.3",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-core": "~3.8.0",
|
||||
"jasmine-marbles": "^0.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"karma": "~6.3.9",
|
||||
|
||||
Reference in New Issue
Block a user