mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Compare commits
269 Commits
split-scre
...
fix/4541-K
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04b78e66e8 | ||
|
|
f0b653fd0f | ||
|
|
14eba6e5ea | ||
|
|
5073693fc2 | ||
|
|
f3cb6236a5 | ||
|
|
4ab9890313 | ||
|
|
a8cd6ce844 | ||
|
|
3bba23cc76 | ||
|
|
e25f176a7b | ||
|
|
a169d2a4e9 | ||
|
|
6a9caa432e | ||
|
|
389948c077 | ||
|
|
e7724ed8b9 | ||
|
|
c7f1b27fdf | ||
|
|
135f0255b8 | ||
|
|
65a7aa569d | ||
|
|
211eaa6175 | ||
|
|
8097c6ad9e | ||
|
|
b0d76b01d7 | ||
|
|
626fd0081f | ||
|
|
362fca74bc | ||
|
|
b8f0a29f79 | ||
|
|
f54400f00d | ||
|
|
54094695b1 | ||
|
|
1e3e9588da | ||
|
|
f04705b659 | ||
|
|
c22672fad0 | ||
|
|
59673a47db | ||
|
|
e56ea0bd4e | ||
|
|
f8c4d4a842 | ||
|
|
c4dd9214a3 | ||
|
|
4d74b3a89e | ||
|
|
b0b3fd40ce | ||
|
|
3404c930c5 | ||
|
|
abcd940ed3 | ||
|
|
c1756942b2 | ||
|
|
ea4d036066 | ||
|
|
101a34bd3f | ||
|
|
99bad149cb | ||
|
|
a0bff7164c | ||
|
|
0c4a4130b9 | ||
|
|
8dd1211729 | ||
|
|
a2f1b8b624 | ||
|
|
98a331ffe5 | ||
|
|
c0f97c9bae | ||
|
|
960ffa165f | ||
|
|
6bdfbe2eff | ||
|
|
80342e61ac | ||
|
|
6bf3894e4d | ||
|
|
aab29838bf | ||
|
|
856ca5651e | ||
|
|
a7d4b8d7fb | ||
|
|
62d260473c | ||
|
|
bde52a2526 | ||
|
|
6243b03cfc | ||
|
|
d24841800e | ||
|
|
f60628c769 | ||
|
|
034f697da5 | ||
|
|
ec9f80767b | ||
|
|
a5e569cf05 | ||
|
|
b62259f9b4 | ||
|
|
95baeaa8a8 | ||
|
|
a5b9115a91 | ||
|
|
1885c58d86 | ||
|
|
add55a47d6 | ||
|
|
129f49a9ee | ||
|
|
9560eb7ad6 | ||
|
|
772ba29a8e | ||
|
|
8ac8f6cc1f | ||
|
|
a0f496475c | ||
|
|
8979a388ee | ||
|
|
8b9a209c49 | ||
|
|
f4c3e3ceee | ||
|
|
5ca8a83f25 | ||
|
|
006011885f | ||
|
|
9c9e061f6d | ||
|
|
c9782a7d29 | ||
|
|
59de82def8 | ||
|
|
dc2617bb5d | ||
|
|
d80e621563 | ||
|
|
63c02e4605 | ||
|
|
3f93fe0869 | ||
|
|
9011f76e95 | ||
|
|
dd88e4ad3e | ||
|
|
a0869aa4a5 | ||
|
|
1107264d7c | ||
|
|
31512546d3 | ||
|
|
183e7b6945 | ||
|
|
fba465d573 | ||
|
|
dd6784e3b3 | ||
|
|
9caa0fc0fa | ||
|
|
1102fb4608 | ||
|
|
012cc6ac67 | ||
|
|
72de7efc1d | ||
|
|
b8c7bbec88 | ||
|
|
608513b6dc | ||
|
|
fa78eca087 | ||
|
|
77fda0f939 | ||
|
|
81d210a77b | ||
|
|
5ab4456040 | ||
|
|
2db45c900a | ||
|
|
705dc23908 | ||
|
|
9def487ab8 | ||
|
|
50e08f115a | ||
|
|
b15693a914 | ||
|
|
cbf23b6f30 | ||
|
|
fc45efb4af | ||
|
|
b4fbcd6d16 | ||
|
|
c54e5c27ae | ||
|
|
4b80765b26 | ||
|
|
d223e064c2 | ||
|
|
1f8d6c5898 | ||
|
|
66555e9c7e | ||
|
|
bb81b8f826 | ||
|
|
ecb5a77fdd | ||
|
|
6652c2f97e | ||
|
|
e9f16f72cb | ||
|
|
a687b1771f | ||
|
|
ec79e315e5 | ||
|
|
af10e66b1a | ||
|
|
2d8a0f514d | ||
|
|
02ade0a377 | ||
|
|
c2943037d9 | ||
|
|
c30d8fa5fd | ||
|
|
4fd10bc8a4 | ||
|
|
1d25fec9ff | ||
|
|
6f77527d59 | ||
|
|
7a2cd5cef8 | ||
|
|
d1de4d96d8 | ||
|
|
dd1652c3a6 | ||
|
|
66fd2eed81 | ||
|
|
f5d90f97e8 | ||
|
|
ade3800568 | ||
|
|
486e2e5a28 | ||
|
|
86d3b4e3f5 | ||
|
|
4d669731fb | ||
|
|
5bc81f7048 | ||
|
|
6cf6dec001 | ||
|
|
526d82752c | ||
|
|
7565b2bb54 | ||
|
|
692a32f4d7 | ||
|
|
4039ffdf20 | ||
|
|
cb39b3b79b | ||
|
|
fe3dfd00ab | ||
|
|
08a4b5a2ca | ||
|
|
e8a036b6df | ||
|
|
ff698ec6b2 | ||
|
|
8644ea515b | ||
|
|
10a77c25e9 | ||
|
|
25d3adc28c | ||
|
|
5cdbbb995f | ||
|
|
01a26ef57a | ||
|
|
3b337ea127 | ||
|
|
c66ef5ba91 | ||
|
|
b2edcf8a20 | ||
|
|
06af33e37e | ||
|
|
1a217d0870 | ||
|
|
d6c52baf53 | ||
|
|
f923fdefa4 | ||
|
|
bd674a0e14 | ||
|
|
bd3f8af924 | ||
|
|
5e720876ac | ||
|
|
8543db465b | ||
|
|
ec4951d8dd | ||
|
|
17f1846c69 | ||
|
|
6b71a544fe | ||
|
|
7f1f097179 | ||
|
|
a7003b84bf | ||
|
|
8a84e69ce3 | ||
|
|
3ebd50a8c1 | ||
|
|
1f7a952c91 | ||
|
|
560ef57915 | ||
|
|
4fb81526ae | ||
|
|
4bee8117ee | ||
|
|
66991684d2 | ||
|
|
06abcbff51 | ||
|
|
17197461f7 | ||
|
|
f036190019 | ||
|
|
e68975b0f7 | ||
|
|
692c1cb596 | ||
|
|
7ace3b7685 | ||
|
|
6d6077c54f | ||
|
|
b60913de3c | ||
|
|
357b89f1ea | ||
|
|
c37b05d4b8 | ||
|
|
94fe011d49 | ||
|
|
0e458e81d8 | ||
|
|
efbdb134a9 | ||
|
|
a97d87ed7b | ||
|
|
cb56cfcb00 | ||
|
|
d13cf0ef8d | ||
|
|
e6afd6887a | ||
|
|
7378b7db53 | ||
|
|
70f9bb0f73 | ||
|
|
2fa7451716 | ||
|
|
871aeaed1f | ||
|
|
09f0337489 | ||
|
|
51f36de7dd | ||
|
|
0999e1ea51 | ||
|
|
d079b276cf | ||
|
|
e1bd87418c | ||
|
|
d06af28e11 | ||
|
|
c123a29b1d | ||
|
|
1398a3bdee | ||
|
|
340e866aed | ||
|
|
9338162906 | ||
|
|
23d61bfa60 | ||
|
|
7409053cef | ||
|
|
0c372b0245 | ||
|
|
dd68405522 | ||
|
|
69e792ae41 | ||
|
|
02b507aca9 | ||
|
|
05f94c65fc | ||
|
|
8b6ebd1820 | ||
|
|
45cb411e17 | ||
|
|
dec66de61d | ||
|
|
e9a490d7f3 | ||
|
|
280b28a474 | ||
|
|
cfba5f34d4 | ||
|
|
582e2d988c | ||
|
|
4c627986d1 | ||
|
|
299dab43b9 | ||
|
|
ce0823a6fd | ||
|
|
e0963e9b65 | ||
|
|
a88552f975 | ||
|
|
66a7bab287 | ||
|
|
088d9e1b6f | ||
|
|
29619b2fec | ||
|
|
a5effc89a7 | ||
|
|
e7fe2a2676 | ||
|
|
53bb01db2d | ||
|
|
003d0cbcb2 | ||
|
|
a71c627a30 | ||
|
|
261c851514 | ||
|
|
58cfcba738 | ||
|
|
50dac899c9 | ||
|
|
9fe25f0f73 | ||
|
|
678c961ea9 | ||
|
|
f6b4633ac4 | ||
|
|
0ecd2916a2 | ||
|
|
e786b60bfc | ||
|
|
8c079e9064 | ||
|
|
3b08fe438b | ||
|
|
fedbdcc35c | ||
|
|
ac656ddc04 | ||
|
|
85d8d75da8 | ||
|
|
e6d389d848 | ||
|
|
664053f231 | ||
|
|
18b2230dd7 | ||
|
|
0876ed3acc | ||
|
|
7b4edbee8b | ||
|
|
05ef1edfeb | ||
|
|
fb8db78bbd | ||
|
|
aecc4a477f | ||
|
|
fd63ce8b3c | ||
|
|
423cd498cf | ||
|
|
b421c8b08c | ||
|
|
4304286f48 | ||
|
|
df308c98ff | ||
|
|
1d1221e8c5 | ||
|
|
e2a6eac0a2 | ||
|
|
4e54baf9fb | ||
|
|
7593e420de | ||
|
|
84ca80a1c9 | ||
|
|
7265f4d4ce | ||
|
|
fca1eacc6e | ||
|
|
b440ddbe82 | ||
|
|
d6e0d92132 | ||
|
|
b16ffa4352 |
@@ -86,6 +86,28 @@ export class ApplicationService {
|
||||
return this.getProcessById$(processId).pipe(map((process) => process?.data?.selectedBranch));
|
||||
}
|
||||
|
||||
readonly REGEX_PROCESS_NAME = /^Vorgang \d+$/;
|
||||
|
||||
async createCustomerProcess(processId?: number): Promise<ApplicationProcess> {
|
||||
const processes = await this.getProcesses$('customer').pipe(first()).toPromise();
|
||||
|
||||
const processIds = processes.filter((x) => this.REGEX_PROCESS_NAME.test(x.name)).map((x) => +x.name.split(' ')[1]);
|
||||
|
||||
const maxId = processIds.length > 0 ? Math.max(...processIds) : 0;
|
||||
|
||||
const process: ApplicationProcess = {
|
||||
id: processId ?? Date.now(),
|
||||
type: 'cart',
|
||||
name: `Vorgang ${maxId + 1}`,
|
||||
section: 'customer',
|
||||
closeable: true,
|
||||
};
|
||||
|
||||
await this.createProcess(process);
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
async createProcess(process: ApplicationProcess) {
|
||||
const existingProcess = await this.getProcessById$(process?.id).pipe(first()).toPromise();
|
||||
if (existingProcess?.id === process?.id) {
|
||||
|
||||
44
apps/core/cache/src/lib/cache.service.ts
vendored
44
apps/core/cache/src/lib/cache.service.ts
vendored
@@ -1,14 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CacheOptions } from './cache-options';
|
||||
import { Cached } from './cached';
|
||||
import { interval } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CacheService {
|
||||
constructor() {}
|
||||
constructor() {
|
||||
this._registerCleanupTask();
|
||||
}
|
||||
|
||||
set(token: Object, data: any, options?: CacheOptions) {
|
||||
_registerCleanupTask() {
|
||||
this.cleanup();
|
||||
interval(1000 * 60).subscribe(() => {
|
||||
this.cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
set<T>(token: Object, data: T, options?: CacheOptions) {
|
||||
const persist = options?.persist;
|
||||
const ttl = options?.ttl;
|
||||
const cached: Cached = {
|
||||
@@ -17,6 +27,8 @@ export class CacheService {
|
||||
|
||||
if (ttl) {
|
||||
cached.until = Date.now() + ttl;
|
||||
} else {
|
||||
cached.until = Date.now() + 1000 * 60 * 60 * 12;
|
||||
}
|
||||
|
||||
if (persist) {
|
||||
@@ -29,13 +41,15 @@ export class CacheService {
|
||||
return cached;
|
||||
}
|
||||
|
||||
get<T = any>(token: Object, from: 'session' | 'persist' = 'session'): T {
|
||||
get<T = any>(token: Object, from?: 'session' | 'persist'): T {
|
||||
let cached: Cached;
|
||||
|
||||
if (from === 'session') {
|
||||
cached = this.deserialize(sessionStorage.getItem(this.getKey(token)));
|
||||
} else if (from === 'persist') {
|
||||
cached = this.deserialize(localStorage.getItem(this.getKey(token)));
|
||||
} else {
|
||||
cached = this.deserialize(sessionStorage.getItem(this.getKey(token))) || this.deserialize(localStorage.getItem(this.getKey(token)));
|
||||
}
|
||||
|
||||
if (!cached) {
|
||||
@@ -59,7 +73,8 @@ export class CacheService {
|
||||
}
|
||||
|
||||
private getKey(token: Object) {
|
||||
return this.hash(JSON.stringify(token));
|
||||
const key = `CacheService_` + this.hash(JSON.stringify(token));
|
||||
return key;
|
||||
}
|
||||
|
||||
private hash(data: string): string {
|
||||
@@ -77,4 +92,25 @@ export class CacheService {
|
||||
private deserialize(data: string): Cached {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
// get all keys created by this service by looking for the service name and remove the entries
|
||||
// that ttl is expired
|
||||
let localStorageKeys = Object.keys(localStorage).filter((key) => key.startsWith('CacheService_'));
|
||||
let seesionStorageKeys = Object.keys(sessionStorage).filter((key) => key.startsWith('CacheService_'));
|
||||
|
||||
localStorageKeys.forEach((key) => {
|
||||
const cached = this.deserialize(localStorage.getItem(key));
|
||||
if (cached.until < Date.now()) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
|
||||
seesionStorageKeys.forEach((key) => {
|
||||
const cached = this.deserialize(sessionStorage.getItem(key));
|
||||
if (cached.until < Date.now()) {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { map } from 'rxjs/operators';
|
||||
|
||||
const MATCH_TABLET = '(max-width: 1023px)';
|
||||
|
||||
const MATCH_DESKTOP_SMALL = '(min-width: 1024px) and (max-width: 1439px)';
|
||||
const MATCH_DESKTOP_SMALL = '(min-width: 1024px) and (max-width: 1279px)';
|
||||
|
||||
const MATCH_DESKTOP = '(min-width: 1280px)';
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ import {
|
||||
StoreCheckoutPayerService,
|
||||
StoreCheckoutBranchService,
|
||||
ItemsResult,
|
||||
ShoppingCartItemDTO,
|
||||
KulturPassService,
|
||||
ProductDTO,
|
||||
KulturPassResult,
|
||||
} from '@swagger/checkout';
|
||||
import {
|
||||
DisplayOrderDTO,
|
||||
@@ -37,22 +39,20 @@ import {
|
||||
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
|
||||
} from '@swagger/oms';
|
||||
import { isNullOrUndefined, memorize } from '@utils/common';
|
||||
import { combineLatest, Observable, of, concat, isObservable, throwError, interval, zip, EMPTY, Subscription } from 'rxjs';
|
||||
import { combineLatest, Observable, of, concat, isObservable, throwError, interval as rxjsInterval } from 'rxjs';
|
||||
import {
|
||||
bufferCount,
|
||||
catchError,
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
first,
|
||||
map,
|
||||
mergeMap,
|
||||
share,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
withLatestFrom,
|
||||
startWith,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import * as DomainCheckoutSelectors from './store/domain-checkout.selectors';
|
||||
@@ -63,8 +63,6 @@ import { ApplicationService } from '@core/application';
|
||||
import { CustomerDTO } from '@swagger/crm';
|
||||
import { Config } from '@core/config';
|
||||
import parseDuration from 'parse-duration';
|
||||
import { CheckoutEntity } from './store/defs/checkout.entity';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
@Injectable()
|
||||
export class DomainCheckoutService {
|
||||
@@ -84,7 +82,8 @@ export class DomainCheckoutService {
|
||||
private _paymentService: StoreCheckoutPaymentService,
|
||||
private _buyerService: StoreCheckoutBuyerService,
|
||||
private _payerService: StoreCheckoutPayerService,
|
||||
private _branchService: StoreCheckoutBranchService
|
||||
private _branchService: StoreCheckoutBranchService,
|
||||
private _kulturpassService: KulturPassService
|
||||
) {}
|
||||
|
||||
//#region shoppingcart
|
||||
@@ -231,6 +230,10 @@ export class DomainCheckoutService {
|
||||
);
|
||||
}
|
||||
|
||||
canAddItemsKulturpass(payload: ProductDTO[]): Observable<KulturPassResult[]> {
|
||||
return this._kulturpassService.KulturPassCanAddForKulturPass({ payload }).pipe(map((response) => response?.result));
|
||||
}
|
||||
|
||||
canAddItems({
|
||||
processId,
|
||||
payload,
|
||||
@@ -701,57 +704,60 @@ export class DomainCheckoutService {
|
||||
* @returns true if the availability of all items is valid
|
||||
*/
|
||||
validateOlaStatus({ processId, interval }: { processId: number; interval?: number }): Observable<boolean> {
|
||||
return new Observable<boolean>((observer) => {
|
||||
const enity$ = this.store.select(DomainCheckoutSelectors.selectCheckoutEntityByProcessId, { processId });
|
||||
return rxjsInterval(interval ?? this.olaExpiration / 10).pipe(
|
||||
startWith(0),
|
||||
switchMap(() =>
|
||||
this.store.select(DomainCheckoutSelectors.selectCheckoutEntityByProcessId, { processId }).pipe(
|
||||
map((entity) => {
|
||||
const now = Date.now();
|
||||
if (!entity || !entity.shoppingCart || !entity.shoppingCart.items) {
|
||||
return;
|
||||
}
|
||||
|
||||
const olaExpiration = this.olaExpiration;
|
||||
const itemAvailabilityTimestamp = entity.itemAvailabilityTimestamp ?? {};
|
||||
const shoppingCart = entity.shoppingCart;
|
||||
|
||||
let timeout: any;
|
||||
const timestamps = shoppingCart.items
|
||||
?.map((i) => i.data)
|
||||
?.filter((item) => !!item?.features?.orderType)
|
||||
?.map((item) => {
|
||||
const orderType = item.features.orderType;
|
||||
|
||||
let subscription: Subscription;
|
||||
let timestamp = itemAvailabilityTimestamp[`${item.id}_${orderType}`];
|
||||
|
||||
function check() {
|
||||
const now = Date.now();
|
||||
if (timestamp) {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
subscription?.unsubscribe();
|
||||
subscription = enity$.pipe(take(1)).subscribe((entity) => {
|
||||
if (!entity || !entity.shoppingCart || !entity.shoppingCart.items) {
|
||||
return;
|
||||
}
|
||||
if (orderType.endsWith('Versand')) {
|
||||
timestamp =
|
||||
itemAvailabilityTimestamp[`${item.id}_Versand`] ??
|
||||
itemAvailabilityTimestamp[`${item.id}_DIG-Versand`] ??
|
||||
itemAvailabilityTimestamp[`${item.id}_B2B-Versand`];
|
||||
}
|
||||
|
||||
const itemAvailabilityTimestamp = entity.itemAvailabilityTimestamp ?? {};
|
||||
const shoppingCart = entity.shoppingCart;
|
||||
return timestamp;
|
||||
})
|
||||
?.filter((timestamp) => !!timestamp);
|
||||
|
||||
const timestamps = shoppingCart.items
|
||||
?.map((i) => i.data)
|
||||
?.filter((item) => !!item?.features?.orderType)
|
||||
?.map((item) => itemAvailabilityTimestamp[`${item.id}_${item.features.orderType}`]);
|
||||
if (timestamps?.length > 0) {
|
||||
const oldestTimestamp = Math.min(...timestamps);
|
||||
const expirationTimestamp = oldestTimestamp + this.olaExpiration;
|
||||
return expirationTimestamp > now;
|
||||
}
|
||||
|
||||
if (timestamps?.length > 0) {
|
||||
const oldestTimestamp = Math.min(...timestamps);
|
||||
observer.next(now - oldestTimestamp < olaExpiration);
|
||||
}
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
check.call(this);
|
||||
}, interval ?? olaExpiration / 10);
|
||||
});
|
||||
}
|
||||
|
||||
check.call(this);
|
||||
|
||||
return () => {
|
||||
subscription?.unsubscribe();
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}).pipe(distinctUntilChanged());
|
||||
return false;
|
||||
})
|
||||
)
|
||||
),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
validateAvailabilities({ processId }: { processId: number }): Observable<boolean> {
|
||||
return this.getShoppingCart({ processId }).pipe(
|
||||
map((shoppingCart) => {
|
||||
const items = shoppingCart?.items?.map((item) => item.data) || [];
|
||||
|
||||
return items.every((i) => this.availabilityService.isAvailable({ availability: i.availability }));
|
||||
})
|
||||
);
|
||||
@@ -1015,6 +1021,7 @@ export class DomainCheckoutService {
|
||||
|
||||
//#region Common
|
||||
|
||||
@memorize()
|
||||
canSetCustomer({
|
||||
processId,
|
||||
customerFeatures,
|
||||
@@ -1022,24 +1029,26 @@ export class DomainCheckoutService {
|
||||
processId: number;
|
||||
customerFeatures?: { [key: string]: string };
|
||||
}): Observable<{ ok?: boolean; filter?: { [key: string]: string }; message?: string; create?: InputDTO }> {
|
||||
return this.getShoppingCart({ processId }).pipe(
|
||||
first(),
|
||||
mergeMap((shoppingCart) =>
|
||||
this._shoppingCartService
|
||||
.StoreCheckoutShoppingCartCanAddBuyer({
|
||||
shoppingCartId: shoppingCart.id,
|
||||
payload: { customerFeatures },
|
||||
})
|
||||
.pipe(
|
||||
map((response) => ({
|
||||
ok: response.result.ok,
|
||||
filter: response.result.queryToken?.filter || {},
|
||||
message: response.message,
|
||||
create: response.result.create,
|
||||
}))
|
||||
)
|
||||
return this.getShoppingCart({ processId })
|
||||
.pipe(
|
||||
first(),
|
||||
mergeMap((shoppingCart) =>
|
||||
this._shoppingCartService
|
||||
.StoreCheckoutShoppingCartCanAddBuyer({
|
||||
shoppingCartId: shoppingCart.id,
|
||||
payload: { customerFeatures },
|
||||
})
|
||||
.pipe(
|
||||
map((response) => ({
|
||||
ok: response.result.ok,
|
||||
filter: response.result.queryToken?.filter || {},
|
||||
message: response.message,
|
||||
create: response.result.create,
|
||||
}))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
.pipe(shareReplay(1));
|
||||
}
|
||||
|
||||
setNotificationChannels({ processId, notificationChannels }: { processId: number; notificationChannels: NotificationChannel }): void {
|
||||
|
||||
@@ -22,5 +22,5 @@ export interface CheckoutEntity {
|
||||
specialComment: string;
|
||||
notificationChannels: NotificationChannel;
|
||||
olaErrorIds: number[];
|
||||
itemAvailabilityTimestamp: Record<string, number>;
|
||||
itemAvailabilityTimestamp: Record<string, number | undefined>;
|
||||
}
|
||||
|
||||
88
apps/domain/oms/src/lib/action-handler-services.ts
Normal file
88
apps/domain/oms/src/lib/action-handler-services.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { ActionHandler } from '@core/command';
|
||||
import {
|
||||
AcceptedActionHandler,
|
||||
ArrivedActionHandler,
|
||||
AssembledActionHandler,
|
||||
AvailableForDownloadActionHandler,
|
||||
BackToStockActionHandler,
|
||||
CanceledByBuyerActionHandler,
|
||||
CanceledByRetailerActionHandler,
|
||||
CanceledBySupplierActionHandler,
|
||||
CreateShippingNoteActionHandler,
|
||||
DeliveredActionHandler,
|
||||
DetermineSupplierActionHandler,
|
||||
DispatchedActionHandler,
|
||||
DownloadedActionHandler,
|
||||
FetchedActionHandler,
|
||||
InProcessActionHandler,
|
||||
NotAvailableActionHandler,
|
||||
NotFetchedActionHandler,
|
||||
OrderAtSupplierActionHandler,
|
||||
OrderingActionHandler,
|
||||
OverdueActionHandler,
|
||||
PackedActionHandler,
|
||||
ParkedActionHandler,
|
||||
PlacedActionHandler,
|
||||
PreparationForShippingActionHandler,
|
||||
PrintCompartmentLabelActionHandler,
|
||||
PrintShippingNoteActionHandler,
|
||||
ReOrderActionHandler,
|
||||
RedirectedInternaqllyActionHandler,
|
||||
RequestedActionHandler,
|
||||
ReserverdActionHandler,
|
||||
ReturnedByBuyerActionHandler,
|
||||
ShippingNoteActionHandler,
|
||||
SupplierTemporarilyOutOfStockActionHandler,
|
||||
ReOrderedActionHandler,
|
||||
CollectOnDeliveryNoteActionHandler,
|
||||
PrintPriceDiffQrCodeLabelActionHandler,
|
||||
CollectWithSmallAmountinvoiceActionHandler,
|
||||
PrintSmallamountinvoiceActionHandler,
|
||||
ShopWithKulturpassActionHandler,
|
||||
ChangeOrderItemStatusBaseActionHandler,
|
||||
CreateReturnItemActionHandler,
|
||||
} from './action-handlers';
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
export const ActionHandlerServices: Type<ActionHandler>[] = [
|
||||
AcceptedActionHandler,
|
||||
ArrivedActionHandler,
|
||||
AssembledActionHandler,
|
||||
AvailableForDownloadActionHandler,
|
||||
BackToStockActionHandler,
|
||||
CanceledByBuyerActionHandler,
|
||||
CanceledByRetailerActionHandler,
|
||||
CanceledBySupplierActionHandler,
|
||||
CreateShippingNoteActionHandler,
|
||||
DeliveredActionHandler,
|
||||
DetermineSupplierActionHandler,
|
||||
DispatchedActionHandler,
|
||||
DownloadedActionHandler,
|
||||
FetchedActionHandler,
|
||||
InProcessActionHandler,
|
||||
NotAvailableActionHandler,
|
||||
NotFetchedActionHandler,
|
||||
OrderAtSupplierActionHandler,
|
||||
OrderingActionHandler,
|
||||
OverdueActionHandler,
|
||||
PackedActionHandler,
|
||||
ParkedActionHandler,
|
||||
PlacedActionHandler,
|
||||
PreparationForShippingActionHandler,
|
||||
PrintCompartmentLabelActionHandler,
|
||||
PrintShippingNoteActionHandler,
|
||||
ReOrderActionHandler,
|
||||
RedirectedInternaqllyActionHandler,
|
||||
RequestedActionHandler,
|
||||
ReserverdActionHandler,
|
||||
ReturnedByBuyerActionHandler,
|
||||
ShippingNoteActionHandler,
|
||||
SupplierTemporarilyOutOfStockActionHandler,
|
||||
ReOrderedActionHandler,
|
||||
CollectOnDeliveryNoteActionHandler,
|
||||
PrintPriceDiffQrCodeLabelActionHandler,
|
||||
CollectWithSmallAmountinvoiceActionHandler,
|
||||
PrintSmallamountinvoiceActionHandler,
|
||||
ShopWithKulturpassActionHandler,
|
||||
CreateReturnItemActionHandler,
|
||||
];
|
||||
@@ -2,6 +2,7 @@
|
||||
* Public API Surface of oms
|
||||
*/
|
||||
|
||||
export * from './lib/action-handler-services';
|
||||
export * from './lib/goods.service';
|
||||
export * from './lib/receipt.service';
|
||||
export * from './lib/oms.service';
|
||||
|
||||
60
apps/domain/pickup-shelf/src/lib/pickup-shelf-in.service.ts
Normal file
60
apps/domain/pickup-shelf/src/lib/pickup-shelf-in.service.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { AbholfachService, AutocompleteTokenDTO, ListResponseArgsOfDBHOrderItemListItemDTO, QueryTokenDTO } from '@swagger/oms';
|
||||
import { PickupShelfIOService } from './pickup-shelf-io.service';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { Filter } from '@shared/components/filter';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PickupShelfInService extends PickupShelfIOService {
|
||||
private _abholfachService = inject(AbholfachService);
|
||||
|
||||
name() {
|
||||
return 'PickupShelfInService';
|
||||
}
|
||||
|
||||
getQuerySettings() {
|
||||
return this._abholfachService.AbholfachWareneingangQuerySettings();
|
||||
}
|
||||
|
||||
search(queryToken: QueryTokenDTO) {
|
||||
return this._abholfachService.AbholfachWareneingang(queryToken);
|
||||
}
|
||||
|
||||
complete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
return this._abholfachService.AbholfachWareneingangAutocomplete(autocompleteToken);
|
||||
}
|
||||
|
||||
getOrderItemsByOrderNumberOrCompartmentCode(args: {
|
||||
orderNumber?: string;
|
||||
compartmentCode?: string;
|
||||
filter?: Filter;
|
||||
}): Observable<ListResponseArgsOfDBHOrderItemListItemDTO> {
|
||||
if (!args.orderNumber && !args.compartmentCode) {
|
||||
return throwError(
|
||||
'PickupShelfInService.getOrderItemsByOrderNumberOrCompartmentCode(): Either orderNumber or compartmentCode must be provided.'
|
||||
);
|
||||
}
|
||||
|
||||
const { orderdate } = args.filter?.getQueryToken()?.filter ?? {};
|
||||
|
||||
return this._abholfachService.AbholfachWareneingang({
|
||||
input: {
|
||||
qs: args.compartmentCode ?? args.orderNumber,
|
||||
},
|
||||
filter: {
|
||||
archive: String(true),
|
||||
all_branches: String(true),
|
||||
orderdate,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getOrderItemsByCustomerNumber(args: { customerNumber: string }): Observable<ListResponseArgsOfDBHOrderItemListItemDTO> {
|
||||
return this._abholfachService.AbholfachWareneingang({
|
||||
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
|
||||
input: {
|
||||
customer_name: args.customerNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
29
apps/domain/pickup-shelf/src/lib/pickup-shelf-io.service.ts
Normal file
29
apps/domain/pickup-shelf/src/lib/pickup-shelf-io.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Filter } from '@shared/components/filter';
|
||||
import {
|
||||
AutocompleteTokenDTO,
|
||||
ListResponseArgsOfDBHOrderItemListItemDTO,
|
||||
QueryTokenDTO,
|
||||
ResponseArgsOfIEnumerableOfAutocompleteDTO,
|
||||
ResponseArgsOfQuerySettingsDTO,
|
||||
} from '@swagger/oms';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export abstract class PickupShelfIOService {
|
||||
abstract name(): string;
|
||||
|
||||
abstract getQuerySettings(): Observable<ResponseArgsOfQuerySettingsDTO>;
|
||||
|
||||
abstract search(queryToken: QueryTokenDTO): Observable<ListResponseArgsOfDBHOrderItemListItemDTO>;
|
||||
|
||||
abstract complete(autocompleteToken: AutocompleteTokenDTO): Observable<ResponseArgsOfIEnumerableOfAutocompleteDTO>;
|
||||
|
||||
abstract getOrderItemsByOrderNumberOrCompartmentCode(args: {
|
||||
orderNumber?: string;
|
||||
compartmentCode?: string;
|
||||
filter?: Filter;
|
||||
}): Observable<ListResponseArgsOfDBHOrderItemListItemDTO>;
|
||||
|
||||
abstract getOrderItemsByCustomerNumber(args: { customerNumber: string }): Observable<ListResponseArgsOfDBHOrderItemListItemDTO>;
|
||||
}
|
||||
55
apps/domain/pickup-shelf/src/lib/pickup-shelf-out.service.ts
Normal file
55
apps/domain/pickup-shelf/src/lib/pickup-shelf-out.service.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { AbholfachService, AutocompleteTokenDTO, ListResponseArgsOfDBHOrderItemListItemDTO, QueryTokenDTO } from '@swagger/oms';
|
||||
import { PickupShelfIOService } from './pickup-shelf-io.service';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { Filter } from '@shared/components/filter';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PickupShelfOutService extends PickupShelfIOService {
|
||||
private _abholfachService = inject(AbholfachService);
|
||||
|
||||
name() {
|
||||
return 'PickupShelfOutService';
|
||||
}
|
||||
|
||||
getQuerySettings() {
|
||||
return this._abholfachService.AbholfachWarenausgabeQuerySettings();
|
||||
}
|
||||
|
||||
search(queryToken: QueryTokenDTO) {
|
||||
return this._abholfachService.AbholfachWarenausgabe(queryToken);
|
||||
}
|
||||
|
||||
complete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
return this._abholfachService.AbholfachWarenausgabeAutocomplete(autocompleteToken);
|
||||
}
|
||||
|
||||
getOrderItemsByOrderNumberOrCompartmentCode(args: {
|
||||
orderNumber?: string;
|
||||
compartmentCode?: string;
|
||||
filter?: Filter;
|
||||
}): Observable<ListResponseArgsOfDBHOrderItemListItemDTO> {
|
||||
if (!args.orderNumber && !args.compartmentCode) {
|
||||
return throwError(
|
||||
'PickupShelfOutService.getOrderItemsByOrderNumberOrCompartmentCode(): Either orderNumber or compartmentCode must be provided.'
|
||||
);
|
||||
}
|
||||
|
||||
const { orderdate } = args.filter?.getQueryToken()?.filter ?? {};
|
||||
|
||||
return this._abholfachService.AbholfachWarenausgabe({
|
||||
input: {
|
||||
qs: args.compartmentCode ?? args.orderNumber,
|
||||
},
|
||||
filter: {
|
||||
archive: String(true),
|
||||
all_branches: String(true),
|
||||
orderdate,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getOrderItemsByCustomerNumber(args: { customerNumber: string }): Observable<ListResponseArgsOfDBHOrderItemListItemDTO> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
27
apps/domain/pickup-shelf/src/lib/pickup-shelf.service.ts
Normal file
27
apps/domain/pickup-shelf/src/lib/pickup-shelf.service.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { DBHOrderItemListItemDTO, OrderItemDTO, OrderItemSubsetDTO, OrderService } from '@swagger/oms';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PickupShelfService {
|
||||
private _orderService = inject(OrderService);
|
||||
|
||||
getOrderByOrderId(orderId: number) {
|
||||
return this._orderService.OrderGetOrder(orderId);
|
||||
}
|
||||
patchOrderItemSubset(item: DBHOrderItemListItemDTO, changes: Partial<OrderItemSubsetDTO>) {
|
||||
return this._orderService.OrderPatchOrderItemSubset({
|
||||
orderId: item.orderId,
|
||||
orderItemId: item.orderItemId,
|
||||
orderItemSubsetId: item.orderItemSubsetId,
|
||||
orderItemSubset: changes,
|
||||
});
|
||||
}
|
||||
|
||||
getOrderItemSubsetTasks(item: DBHOrderItemListItemDTO) {
|
||||
return this._orderService.OrderGetOrderItemSubsetTasks({
|
||||
orderId: item.orderId,
|
||||
orderItemId: item.orderItemId,
|
||||
orderItemSubsetId: item.orderItemSubsetId,
|
||||
});
|
||||
}
|
||||
}
|
||||
4
apps/domain/pickup-shelf/src/public-api.ts
Normal file
4
apps/domain/pickup-shelf/src/public-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './lib/pickup-shelf-in.service';
|
||||
export * from './lib/pickup-shelf-io.service';
|
||||
export * from './lib/pickup-shelf-out.service';
|
||||
export * from './lib/pickup-shelf.service';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isDevMode, NgModule } from '@angular/core';
|
||||
import { inject, isDevMode, NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import {
|
||||
CanActivateCartGuard,
|
||||
@@ -22,6 +22,9 @@ import { MainComponent } from './main.component';
|
||||
import { PreviewComponent } from './preview';
|
||||
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ProcessIdGuard } from './guards/process-id.guard';
|
||||
import { ActivateProcessIdGuard, ActivateProcessIdWithConfigKeyGuard } from './guards/activate-process-id.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -36,111 +39,111 @@ const routes: Routes = [
|
||||
canActivate: [IsAuthenticatedGuard],
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
canActivate: [],
|
||||
path: 'kunde',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'kunde',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('@page/dashboard').then((m) => m.DashboardModule),
|
||||
},
|
||||
{
|
||||
path: 'product',
|
||||
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
|
||||
canActivate: [CanActivateProductGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/product',
|
||||
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
|
||||
canActivate: [CanActivateProductWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'order',
|
||||
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/order',
|
||||
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'cart',
|
||||
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
|
||||
canActivate: [CanActivateCartGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/cart',
|
||||
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
|
||||
canActivate: [CanActivateCartWithProcessIdGuard],
|
||||
},
|
||||
{
|
||||
path: 'goods/out',
|
||||
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
|
||||
canActivate: [CanActivateGoodsOutGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/goods/out',
|
||||
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
|
||||
canActivate: [CanActivateGoodsOutWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||
],
|
||||
resolve: { section: CustomerSectionResolver },
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('@page/dashboard').then((m) => m.DashboardModule),
|
||||
},
|
||||
{
|
||||
path: 'filiale',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'task-calendar',
|
||||
loadChildren: () => import('@page/task-calendar').then((m) => m.PageTaskCalendarModule),
|
||||
canActivate: [CanActivateTaskCalendarGuard],
|
||||
},
|
||||
{
|
||||
path: 'goods/in',
|
||||
loadChildren: () => import('@page/goods-in').then((m) => m.GoodsInModule),
|
||||
canActivate: [CanActivateGoodsInGuard],
|
||||
},
|
||||
{
|
||||
path: 'remission',
|
||||
loadChildren: () => import('@page/remission').then((m) => m.PageRemissionModule),
|
||||
canActivate: [CanActivateRemissionGuard],
|
||||
},
|
||||
{
|
||||
path: 'package-inspection',
|
||||
loadChildren: () => import('@page/package-inspection').then((m) => m.PackageInspectionModule),
|
||||
canActivate: [CanActivatePackageInspectionGuard],
|
||||
},
|
||||
{
|
||||
path: 'assortment',
|
||||
loadChildren: () => import('@page/assortment').then((m) => m.AssortmentModule),
|
||||
canActivate: [CanActivateAssortmentGuard],
|
||||
},
|
||||
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
|
||||
],
|
||||
resolve: { section: BranchSectionResolver },
|
||||
path: 'product',
|
||||
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
|
||||
canActivate: [CanActivateProductGuard],
|
||||
},
|
||||
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
|
||||
{
|
||||
path: ':processId/product',
|
||||
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
|
||||
canActivate: [CanActivateProductWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'order',
|
||||
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/order',
|
||||
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'cart',
|
||||
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
|
||||
canActivate: [CanActivateCartGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/cart',
|
||||
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
|
||||
canActivate: [CanActivateCartWithProcessIdGuard],
|
||||
},
|
||||
{
|
||||
path: 'pickup-shelf',
|
||||
canActivate: [ProcessIdGuard],
|
||||
// NOTE: This is a workaround for the canActivate guard not being called
|
||||
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
|
||||
},
|
||||
{
|
||||
path: ':processId/pickup-shelf',
|
||||
canActivate: [ActivateProcessIdGuard],
|
||||
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
|
||||
},
|
||||
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||
],
|
||||
resolve: { section: CustomerSectionResolver },
|
||||
},
|
||||
{
|
||||
path: 'filiale',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'task-calendar',
|
||||
loadChildren: () => import('@page/task-calendar').then((m) => m.PageTaskCalendarModule),
|
||||
canActivate: [CanActivateTaskCalendarGuard],
|
||||
},
|
||||
{
|
||||
path: 'pickup-shelf',
|
||||
canActivate: [ActivateProcessIdWithConfigKeyGuard('pickupShelf')],
|
||||
// NOTE: This is a workaround for the canActivate guard not being called
|
||||
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfInModule),
|
||||
},
|
||||
{
|
||||
path: 'goods/in',
|
||||
loadChildren: () => import('@page/goods-in').then((m) => m.GoodsInModule),
|
||||
canActivate: [CanActivateGoodsInGuard],
|
||||
},
|
||||
{
|
||||
path: 'remission',
|
||||
loadChildren: () => import('@page/remission').then((m) => m.PageRemissionModule),
|
||||
canActivate: [CanActivateRemissionGuard],
|
||||
},
|
||||
{
|
||||
path: 'package-inspection',
|
||||
loadChildren: () => import('@page/package-inspection').then((m) => m.PackageInspectionModule),
|
||||
canActivate: [CanActivatePackageInspectionGuard],
|
||||
},
|
||||
{
|
||||
path: 'assortment',
|
||||
loadChildren: () => import('@page/assortment').then((m) => m.AssortmentModule),
|
||||
canActivate: [CanActivateAssortmentGuard],
|
||||
},
|
||||
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
|
||||
],
|
||||
resolve: { section: BranchSectionResolver },
|
||||
},
|
||||
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
46
apps/isa-app/src/app/guards/activate-process-id.guard.ts
Normal file
46
apps/isa-app/src/app/guards/activate-process-id.guard.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { Config } from '@core/config';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
export const ActivateProcessIdGuard: CanActivateFn = async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
|
||||
const application = inject(ApplicationService);
|
||||
|
||||
const processIdStr = route.params.processId;
|
||||
|
||||
if (!processIdStr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const processId = Number(processIdStr);
|
||||
|
||||
// Check if Process already exists
|
||||
const process = await application.getProcessById$(processId).pipe(take(1)).toPromise();
|
||||
|
||||
if (!process) {
|
||||
application.createCustomerProcess(processId);
|
||||
}
|
||||
|
||||
application.activateProcess(processId);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const ActivateProcessIdWithConfigKeyGuard: (key: string) => CanActivateFn = (key) => async (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
) => {
|
||||
const application = inject(ApplicationService);
|
||||
const config = inject(Config);
|
||||
|
||||
const processId = config.get(`process.ids.${key}`);
|
||||
|
||||
if (isNaN(processId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
application.activateProcess(processId);
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -15,11 +15,12 @@ export class CanActivateCustomerOrdersWithProcessIdGuard {
|
||||
.toPromise();
|
||||
|
||||
if (!process) {
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
await this._applicationService.createProcess({
|
||||
id: +route.params.processId,
|
||||
type: 'customer-order',
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Kundenbestellungen`,
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,6 +47,18 @@ export class CanActivateCustomerOrdersWithProcessIdGuard {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -9,7 +10,8 @@ export class CanActivateCustomerGuard {
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _router: Router
|
||||
private readonly _router: Router,
|
||||
private readonly _navigation: CustomerSearchNavigation
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
@@ -41,11 +43,17 @@ export class CanActivateCustomerGuard {
|
||||
await this.fromCartProcess(processes);
|
||||
return false;
|
||||
} else {
|
||||
await this._router.navigate(['/kunde', String(lastActivatedProcessId), 'customer']);
|
||||
await this.navigateToDefaultRoute(lastActivatedProcessId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async navigateToDefaultRoute(processId: number) {
|
||||
const route = this._navigation.defaultRoute({ processId });
|
||||
|
||||
await this._router.navigate(route.path, { queryParams: route.queryParams });
|
||||
}
|
||||
|
||||
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Kundensuche
|
||||
async fromCartProcess(processes: ApplicationProcess[]) {
|
||||
const newProcessId = Date.now();
|
||||
@@ -56,7 +64,7 @@ export class CanActivateCustomerGuard {
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this._router.navigate(['/kunde', String(newProcessId), 'customer']);
|
||||
await this.navigateToDefaultRoute(newProcessId);
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Footer Kundensuche
|
||||
@@ -74,7 +82,7 @@ export class CanActivateCustomerGuard {
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._router.navigate(['/kunde', String(processId), 'customer']);
|
||||
await this.navigateToDefaultRoute(processId);
|
||||
}
|
||||
|
||||
// Bei offener Warenausgabe und Klick auf Footer Kundensuche
|
||||
@@ -98,7 +106,7 @@ export class CanActivateCustomerGuard {
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._router.navigate(['/kunde', String(processId), 'customer']);
|
||||
await this.navigateToDefaultRoute(processId);
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class CanActivateGoodsInGuard {
|
||||
id: this._config.get('process.ids.goodsIn'),
|
||||
type: 'goods-in',
|
||||
section: 'branch',
|
||||
name: 'Abholfach',
|
||||
name: '',
|
||||
});
|
||||
}
|
||||
this._applicationService.activateProcess(this._config.get('process.ids.goodsIn'));
|
||||
|
||||
33
apps/isa-app/src/app/guards/process-id.guard.ts
Normal file
33
apps/isa-app/src/app/guards/process-id.guard.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
export const ProcessIdGuard: CanActivateFn = async (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Promise<boolean | UrlTree> => {
|
||||
const application = inject(ApplicationService);
|
||||
const router = inject(Router);
|
||||
|
||||
const process = await application.getLastActivatedProcessWithSection$('customer').pipe(take(1)).toPromise();
|
||||
|
||||
const processId = process?.id ?? Date.now();
|
||||
|
||||
const originalUrl = state.url?.split('?')[0] ?? '';
|
||||
|
||||
let url: string = '';
|
||||
|
||||
if (originalUrl.startsWith('/kunde')) {
|
||||
url = originalUrl.replace('/kunde', `/kunde/${processId}`);
|
||||
} else {
|
||||
url = originalUrl;
|
||||
}
|
||||
|
||||
if (originalUrl === url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await router.navigateByUrl(url);
|
||||
return false;
|
||||
};
|
||||
@@ -6,6 +6,10 @@
|
||||
"name": "account",
|
||||
"data": "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"
|
||||
},
|
||||
{
|
||||
"name": "account-circle",
|
||||
"data": "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7.07,18.28C7.5,17.38 10.12,16.5 12,16.5C13.88,16.5 16.5,17.38 16.93,18.28C15.57,19.36 13.86,20 12,20C10.14,20 8.43,19.36 7.07,18.28M18.36,16.83C16.93,15.09 13.46,14.5 12,14.5C10.54,14.5 7.07,15.09 5.64,16.83C4.62,15.5 4,13.82 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,13.82 19.38,15.5 18.36,16.83M12,6C10.06,6 8.5,7.56 8.5,9.5C8.5,11.44 10.06,13 12,13C13.94,13 15.5,11.44 15.5,9.5C15.5,7.56 13.94,6 12,6M12,11A1.5,1.5 0 0,1 10.5,9.5A1.5,1.5 0 0,1 12,8A1.5,1.5 0 0,1 13.5,9.5A1.5,1.5 0 0,1 12,11Z"
|
||||
},
|
||||
{
|
||||
"name": "package-variant-closed",
|
||||
"data": "M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L10.11,5.22L16,8.61L17.96,7.5L12,4.15M6.04,7.5L12,10.85L13.96,9.75L8.08,6.35L6.04,7.5M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"
|
||||
@@ -411,6 +415,10 @@
|
||||
{
|
||||
"name": "isa-box-out",
|
||||
"alias": "Versandbestellung (oder gemischt)"
|
||||
},
|
||||
{
|
||||
"name": "package-variant-closed",
|
||||
"alias": "Bestellung ohne Konto"
|
||||
},{
|
||||
"name": "person",
|
||||
"alias": "Onlinekonto"
|
||||
|
||||
@@ -67,12 +67,13 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000,
|
||||
"packageInspection": 5000,
|
||||
"assortment": 6000
|
||||
"assortment": 6000,
|
||||
"pickupShelf": 7000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
|
||||
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
|
||||
|
||||
@@ -66,12 +66,13 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000,
|
||||
"packageInspection": 5000,
|
||||
"assortment": 6000
|
||||
"assortment": 6000,
|
||||
"pickupShelf": 7000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 900000,
|
||||
"licence": {
|
||||
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
|
||||
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "30s"
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
@@ -68,12 +68,13 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000,
|
||||
"packageInspection": 5000,
|
||||
"assortment": 6000
|
||||
"assortment": 6000,
|
||||
"pickupShelf": 7000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
|
||||
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -67,12 +67,13 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000,
|
||||
"packageInspection": 5000,
|
||||
"assortment": 6000
|
||||
"assortment": 6000,
|
||||
"pickupShelf": 7000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZZzfQ+eLFl3Dzf1QSBag1lDibIoOPh4W33erRIRe3SDUMkHDX8eczEjd2TnfRMWoE5lXOBGtESCWICN9EbrmI1S9Lu5APsvvEOD+K54ADwIVawx0HNZRAc8/+9Vf/izcEGOFQFGBQJyR6vzdzFv5HcjznhxI9E3LiF+uVQPtCqsVYzpkMWIrC5VCg2uwNrj9Bw6f8zYi/lZPrDMS5yVKVcajeK7sh9QAq17dR0opjIIuP5t5nDEJ7hnITwtTR5HaM6cX/KhKpTILOgKexvLYqrK6QJWpU85sDwqwn6T7av4V68qL3XrUo60dScop4QsvraQe1HkRsffl6DkAEoX0RNMS5qVWjGerW7lvA/DQd9hsAO3jWFDR9hVDyt2VvmzzFKnHYqTYxC5qG4bCEJ0RJjy6tEP5Q7vL5SxWygVadmjPv+TwDOCS7DxzxIjcO+BXQY7gW6qn0hx9fXzyvO3avrGWqyImMlgEApZq+36ANqtRcPD/stEe4i0N9dSPhYoHPcc/9/9jpts43FozlgfY4wY8Wt5ybB3X0caISMmB/klFIJKKN7num439z3+Xk7ENB/Xvb0XAtnOt/cuxQYsGQ7fb62GOO/7Va5fdE9ZfaIJsS5ToE6oIbV04pLUssJf9cUMsyPFVELYSJmyGPQQFRz0TTxxRvPapIWrfa2x5x3hYUpNTAdY3v0fN9l/1ZqNSBmIBLH/LoXaVJQ2DydGD1/QFZ2Z/S7zTYKg5/cSEpUgiYtbwutNZSjRH29ucSizC524k+Zst95T8G7LJaWCT8SQAcKXqCnjpiEGWzD++h0jXjn6BWjUnIHi0te+27vF/z6UQL00sWco5hUIqF66EiU="
|
||||
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -67,12 +67,13 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000,
|
||||
"packageInspection": 5000,
|
||||
"assortment": 6000
|
||||
"assortment": 6000,
|
||||
"pickupShelf": 7000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZZzfQ+eLFl3Dzf1QSBag1lDibIoOPh4W33erRIRe3SDUMkHDX8eczEjd2TnfRMWoE5lXOBGtESCWICN9EbrmI1S9Lu5APsvvEOD+K54ADwIVawx0HNZRAc8/+9Vf/izcEGOFQFGBQJyR6vzdzFv5HcjznhxI9E3LiF+uVQPtCqsVYzpkMWIrC5VCg2uwNrj9Bw6f8zYi/lZPrDMS5yVKVcajeK7sh9QAq17dR0opjIIuP5t5nDEJ7hnITwtTR5HaM6cX/KhKpTILOgKexvLYqrK6QJWpU85sDwqwn6T7av4V68qL3XrUo60dScop4QsvraQe1HkRsffl6DkAEoX0RNMS5qVWjGerW7lvA/DQd9hsAO3jWFDR9hVDyt2VvmzzFKnHYqTYxC5qG4bCEJ0RJjy6tEP5Q7vL5SxWygVadmjPv+TwDOCS7DxzxIjcO+BXQY7gW6qn0hx9fXzyvO3avrGWqyImMlgEApZq+36ANqtRcPD/stEe4i0N9dSPhYoHPcc/9/9jpts43FozlgfY4wY8Wt5ybB3X0caISMmB/klFIJKKN7num439z3+Xk7ENB/Xvb0XAtnOt/cuxQYsGQ7fb62GOO/7Va5fdE9ZfaIJsS5ToE6oIbV04pLUssJf9cUMsyPFVELYSJmyGPQQFRz0TTxxRvPapIWrfa2x5x3hYUpNTAdY3v0fN9l/1ZqNSBmIBLH/LoXaVJQ2DydGD1/QFZ2Z/S7zTYKg5/cSEpUgiYtbwutNZSjRH29ucSizC524k+Zst95T8G7LJaWCT8SQAcKXqCnjpiEGWzD++h0jXjn6BWjUnIHi0te+27vF/z6UQL00sWco5hUIqF66EiU="
|
||||
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -68,12 +68,13 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000,
|
||||
"packageInspection": 5000,
|
||||
"assortment": 6000
|
||||
"assortment": 6000,
|
||||
"pickupShelf": 7000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
|
||||
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { PickupShelfInNavigationService } from '@shared/services';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
|
||||
@@ -9,6 +10,8 @@ import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ModalNotificationsRemissionGroupComponent {
|
||||
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
|
||||
|
||||
@Input()
|
||||
notifications: MessageBoardItemDTO[];
|
||||
|
||||
@@ -18,8 +21,14 @@ export class ModalNotificationsRemissionGroupComponent {
|
||||
constructor(private _router: Router) {}
|
||||
|
||||
itemSelected(item: MessageBoardItemDTO) {
|
||||
const defaultNav = this._pickupShelfInNavigationService.listRoute();
|
||||
const queryParams = UiFilter.getQueryParamsFromQueryTokenDTO(item.queryToken);
|
||||
this._router.navigate(['/filiale/goods/in/results'], { queryParams });
|
||||
this._router.navigate(defaultNav.path, {
|
||||
queryParams: {
|
||||
...defaultNav.queryParams,
|
||||
...queryParams,
|
||||
},
|
||||
});
|
||||
this.navigated.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { PickupShelfInNavigationService } from '@shared/services';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
|
||||
@@ -9,6 +10,7 @@ import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ModalNotificationsReservationGroupComponent {
|
||||
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
|
||||
@Input()
|
||||
notifications: MessageBoardItemDTO[];
|
||||
|
||||
@@ -18,8 +20,14 @@ export class ModalNotificationsReservationGroupComponent {
|
||||
constructor(private _router: Router) {}
|
||||
|
||||
itemSelected(item: MessageBoardItemDTO) {
|
||||
const defaultNav = this._pickupShelfInNavigationService.listRoute();
|
||||
const queryParams = UiFilter.getQueryParamsFromQueryTokenDTO(item.queryToken);
|
||||
this._router.navigate(['/filiale/goods/in/results'], { queryParams });
|
||||
this._router.navigate(defaultNav.path, {
|
||||
queryParams: {
|
||||
...defaultNav.queryParams,
|
||||
...queryParams,
|
||||
},
|
||||
});
|
||||
this.navigated.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { PickupShelfInNavigationService } from '@shared/services';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
|
||||
@@ -9,6 +10,8 @@ import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ModalNotificationsTaskCalendarGroupComponent {
|
||||
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
|
||||
|
||||
@Input()
|
||||
notifications: MessageBoardItemDTO[];
|
||||
|
||||
@@ -18,8 +21,14 @@ export class ModalNotificationsTaskCalendarGroupComponent {
|
||||
constructor(private _router: Router) {}
|
||||
|
||||
itemSelected(item: MessageBoardItemDTO) {
|
||||
const defaultNav = this._pickupShelfInNavigationService.listRoute();
|
||||
const queryParams = UiFilter.getQueryParamsFromQueryTokenDTO(item.queryToken);
|
||||
this._router.navigate(['/filiale/goods/in/results'], { queryParams });
|
||||
this._router.navigate(defaultNav.path, {
|
||||
queryParams: {
|
||||
...defaultNav.queryParams,
|
||||
...queryParams,
|
||||
},
|
||||
});
|
||||
this.navigated.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export class ModalNotificationsComponent extends ComponentStore<ModalNotificatio
|
||||
|
||||
private _categorySelector = (state: ModalNotificationComponentState) => {
|
||||
const selectedArea = this._selectedAreaSelector(state);
|
||||
console.log('_categorySelector', state.notifications[selectedArea]?.[0]?.category);
|
||||
|
||||
return state.notifications[selectedArea]?.[0]?.category;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<shared-breadcrumb class="my-4" [key]="breadcrumbKey"></shared-breadcrumb>
|
||||
<shared-breadcrumb [key]="breadcrumbKey"></shared-breadcrumb>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,10 +1,393 @@
|
||||
<div class="page-article-details__wrapper">
|
||||
<div #detailsContainer class="page-article-details__container px-5" *ngIf="store.item$ | async; let item">
|
||||
<div class="page-article-details__product-details mb-3">
|
||||
<div class="page-article-details__product-bookmark justify-self-end">
|
||||
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
||||
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
|
||||
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
|
||||
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
|
||||
Archivtitel. Wird nicht mehr gedruckt. Artikel ist bestellbar, weil lieferbar.
|
||||
</ng-container>
|
||||
<ng-template #notAvailable>
|
||||
Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar.
|
||||
</ng-template>
|
||||
</ui-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="showSubscriptionBadge$ | async">
|
||||
<button
|
||||
[uiOverlayTrigger]="subscribtionTooltip"
|
||||
class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]"
|
||||
>
|
||||
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
|
||||
</button>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
|
||||
>Artikel ist ein Fortsetzungsartikel,<br />
|
||||
Artikel muss über eine Aboabteilung<br />
|
||||
bestellt werden.
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
|
||||
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
|
||||
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
|
||||
Dieser Artikel befindet sich im Prämienkatalog.
|
||||
</ui-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-image-recessions flex flex-col items-center">
|
||||
<div class="page-article-details__product-image">
|
||||
<button class="border-none outline-none bg-transparent relative" (click)="showImages()">
|
||||
<img
|
||||
class="max-h-[19.6875rem] max-w-[12.1875rem] rounded"
|
||||
(load)="loadImage()"
|
||||
[src]="item.imageId | productImage: 195:315:true"
|
||||
alt="product image"
|
||||
/>
|
||||
<ui-icon
|
||||
class="absolute text-[#A7B9CB] inline-block bottom-[0.875rem] right-[1.125rem]"
|
||||
*ngIf="imageLoaded$ | async"
|
||||
icon="search_add"
|
||||
size="25px"
|
||||
></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
(click)="showReviews()"
|
||||
class="page-article-details__product-recessions flex flex-col mt-2 items-center bg-transparent border-none outline-none"
|
||||
*ngIf="item.reviews?.length > 0"
|
||||
>
|
||||
<ui-stars [rating]="store.reviewRating$ | async"></ui-stars>
|
||||
|
||||
<div class="text-p2 text-[#0556B4] font-bold">{{ item.reviews.length }} Rezensionen</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-contributors">
|
||||
<a
|
||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||
class="text-[#0556B4] font-semibold no-underline text-p2"
|
||||
[routerLink]="resultsPath"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-print justify-self-end" [class.mt-4]="isBadgeVisible$ | async">
|
||||
<button class="bg-transparent text-brand font-bold text-lg outline-none border-none p-0" (click)="print()">Drucken</button>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-title text-h3 font-bold mb-6">
|
||||
{{ item.product?.name }}
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-misc flex flex-col mb-4">
|
||||
<div
|
||||
class="page-article-details__product-format flex items-center font-bold text-p3"
|
||||
*ngIf="item?.product?.format && item?.product?.formatDetail"
|
||||
>
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
class="flex mr-2 h-[1.125rem]"
|
||||
[src]="'/assets/images/Icon_' + item.product?.format + '.svg'"
|
||||
[alt]="item.product?.formatDetail"
|
||||
/>
|
||||
{{ item.product?.formatDetail }}
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-volume" *ngIf="item?.product?.volume">Band/Reihe {{ item?.product?.volume }}</div>
|
||||
|
||||
<div class="page-article-details__product-publication">{{ publicationDate$ | async }}</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-price-info flex flex-col mb-4 flex-nowrap self-end">
|
||||
<div class="page-article-details__product-price font-bold text-xl self-end" *ngIf="price$ | async; let price">
|
||||
{{ price?.value?.value | currency: price?.value?.currency:'code' }}
|
||||
</div>
|
||||
<div *ngIf="price$ | async; let price" class="page-article-details__product-price-bound self-end">
|
||||
{{ price?.vat?.vatType | vat: (priceMaintained$ | async) }}
|
||||
</div>
|
||||
<div class="page-article-details__product-points self-end" *ngIf="store.promotionPoints$ | async; let promotionPoints">
|
||||
{{ promotionPoints }} Lesepunkte
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-origin-infos flex flex-col mb-4">
|
||||
<div class="page-article-details__product-manufacturer" data-name="product-manufacturer">{{ item.product?.manufacturer }}</div>
|
||||
|
||||
<div class="page-article-details__product-language" *ngIf="item?.product?.locale" data-name="product-language">
|
||||
{{ item?.product?.locale }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-stock flex justify-end items-center">
|
||||
<div class="h-5 w-16 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="store.fetchingTakeAwayAvailability$ | async"></div>
|
||||
<button
|
||||
class="flex flex-row py-4 pl-4"
|
||||
type="button"
|
||||
[uiOverlayTrigger]="tooltip"
|
||||
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
|
||||
(click)="showTooltip()"
|
||||
*ngIf="!(store.fetchingTakeAwayAvailability$ | async)"
|
||||
>
|
||||
<ng-container *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
|
||||
<ui-icon class="mr-2 mb-1" icon="home" size="15px"></ui-icon>
|
||||
<span class="font-bold text-p3">{{ takeAwayAvailability.inStock || 0 }}x</span>
|
||||
</ng-container>
|
||||
</button>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-12" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-ean-specs flex flex-col">
|
||||
<div class="page-article-details__product-ean" data-name="product-ean">{{ item.product?.ean }}</div>
|
||||
|
||||
<div class="page-article-details__product-specs">
|
||||
<ng-container *ngIf="item?.specs?.length > 0">
|
||||
{{ (item?.specs)[0]?.value }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-availabilities flex flex-row items-center justify-end mt-4">
|
||||
<div
|
||||
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||
*ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityTakeAwayIcon>
|
||||
<div
|
||||
*ngIf="store.isTakeAwayAvailabilityAvailable$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center"
|
||||
>
|
||||
<ui-icon class="mx-1" icon="shopping_bag" size="18px"> </ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||
*ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityPickUpIcon>
|
||||
<div
|
||||
#uiOverlayTrigger="uiOverlayTrigger"
|
||||
[uiOverlayTrigger]="orderDeadlineTooltip"
|
||||
*ngIf="store.isPickUpAvailabilityAvailable$ | async"
|
||||
class="page-article-details__product-pick-up-availability w-[2.25rem] h-[2.25rem] cursor-pointer bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
[class.tooltip-active]="uiOverlayTrigger.opened"
|
||||
>
|
||||
<shared-icon icon="isa-box-out" [size]="24"></shared-icon>
|
||||
</div>
|
||||
|
||||
<ui-tooltip [warning]="true" yPosition="above" xPosition="after" [yOffset]="-12" #orderDeadlineTooltip [closeable]="true">
|
||||
<b>{{ (store.pickUpAvailability$ | async)?.orderDeadline | orderDeadline }}</b>
|
||||
</ui-tooltip>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||
*ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityDeliveryIcon>
|
||||
<div
|
||||
*ngIf="showDeliveryTruck$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
>
|
||||
<ui-icon class="-mb-[0.3125rem] -mt-[0.3125rem] mx-1" icon="truck" size="30px"></ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
|
||||
></div>
|
||||
<ng-template #showAvailabilityDeliveryB2BIcon>
|
||||
<div
|
||||
*ngIf="showDeliveryB2BTruck$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
>
|
||||
<ui-icon class="-mb-[0.625rem] -mt-[0.625rem] mx-1" icon="truck_b2b" size="30px"> </ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<span *ngIf="store.isDownload$ | async" class="flex flex-row items-center">
|
||||
<div class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3">
|
||||
<ui-icon class="mx-1" icon="download" size="18px"></ui-icon>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__shelf-ssc">
|
||||
<div class="page-article-details__ssc flex justify-end my-2 font-bold text-lg">
|
||||
<div class="w-52 h-5 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
|
||||
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
|
||||
<div class="text-right" *ngIf="store.sscText$ | async; let sscText">
|
||||
{{ sscText }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__shelfinfo text-right" *ngIf="store.isDownload$ | async">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||
else stockInfos
|
||||
"
|
||||
>
|
||||
<span data-name="compartment">
|
||||
{{ (item?.stockInfos)[0]?.compartment }}
|
||||
</span>
|
||||
/
|
||||
<br />
|
||||
<span data-name="shelf-info-label">
|
||||
{{ (item?.shelfInfos)[0]?.label }}
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-template #stockInfos>
|
||||
<ng-container *ngIf="item?.stockInfos && (item?.stockInfos)[0]?.compartment; else shelfInfos">
|
||||
{{ (item?.stockInfos)[0]?.compartment }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #shelfInfos>
|
||||
<ng-container *ngIf="item?.shelfInfos && (item?.shelfInfos)[0]?.label">
|
||||
<span data-name="shelf-info-label">{{ (item?.shelfInfos)[0]?.label }}</span>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="page-article-details__shelfinfo text-right" *ngIf="!(store.isDownload$ | async)">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||
else stockInfos2
|
||||
"
|
||||
>
|
||||
<span data-name="compartment">{{ (item?.stockInfos)[0]?.compartment }}</span>
|
||||
/
|
||||
<br />
|
||||
<span data-name="shelf-info-label">{{ (item?.shelfInfos)[0]?.label }}</span>
|
||||
</ng-container>
|
||||
<ng-template #stockInfos2>
|
||||
<ng-container *ngIf="item?.stockInfos && (item?.stockInfos)[0]?.compartment; else shelfInfos2">
|
||||
{{ (item?.stockInfos)[0]?.compartment }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #shelfInfos2>
|
||||
<ng-container *ngIf="item?.shelfInfos && (item?.shelfInfos)[0]?.label">
|
||||
{{ (item?.shelfInfos)[0]?.label }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-article-details__product-formats-container mt-3" *ngIf="item.family?.length > 0">
|
||||
<hr class="bg-[#E6EFF9] border-t-2" />
|
||||
<div class="pt-3">
|
||||
<div class="page-article-details__product-formats">
|
||||
<span class="mr-2">Auch verfügbar als</span>
|
||||
|
||||
<ui-slider [scrollDistance]="250">
|
||||
<a
|
||||
class="mr-4 text-[#0556B4] font-bold no-underline px-2"
|
||||
*ngFor="let format of item.family"
|
||||
[routerLink]="getDetailsPath(format.product.ean)"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<img
|
||||
class="mr-2"
|
||||
*ngIf="!!format.product?.format"
|
||||
[src]="'/assets/images/OF_Icon_' + format.product?.format + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
{{ format.product?.formatDetail }}
|
||||
<span class="ml-1">{{ format.catalogAvailability?.price?.value?.value | currency: '€' }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</ui-slider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="bg-[#E6EFF9] border-t-2 my-3" />
|
||||
<div #description class="page-article-details__product-description flex flex-col flex-grow mb-6" *ngIf="item.texts?.length > 0">
|
||||
<page-article-details-text class="block box-border" [text]="item.texts[0]"> </page-article-details-text>
|
||||
<div class="box-border">
|
||||
<button
|
||||
class="font-bold flex flex-row text-[#0556B4] items-center mt-2"
|
||||
*ngIf="!showMore && item?.texts?.length > 1"
|
||||
(click)="showMore = !showMore"
|
||||
>
|
||||
Mehr <ui-icon class="ml-2" size="15px" icon="arrow"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="showMore" class="page-article-details__product-description-text flex flex-col whitespace-pre-line break-words box-border">
|
||||
<span *ngFor="let text of item.texts | slice: 1">
|
||||
<h3 class="my-4 text-p2 font-bold">{{ text.label }}</h3>
|
||||
{{ text.value }}
|
||||
</span>
|
||||
|
||||
<button class="font-bold flex flex-row text-[#0556B4] items-center mt-2" (click)="showMore = !showMore">
|
||||
<ui-icon class="transform ml-0 mr-2 rotate-180" size="15px" icon="arrow"></ui-icon> Weniger
|
||||
</button>
|
||||
|
||||
<button class="page-article-details__scroll-top-cta" (click)="scrollTop(description)">
|
||||
<ui-icon class="text-[#0556B4]" icon="arrow" size="20px"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="h-28 box-border"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-recommendations relative">
|
||||
<button
|
||||
*ngIf="store.item$ | async; let item"
|
||||
class="shadow-[#dce2e9_0px_-2px_18px_0px] mb-5 border-none outline-none flex items-center px-5 h-14 min-h-[3.5rem] bg-white w-full"
|
||||
(click)="showRecommendations = true"
|
||||
>
|
||||
<span class="uppercase text-[#0556B4] font-bold text-p3">Empfehlungen</span>
|
||||
<img class="absolute right-5 -top-[0.125rem] h-12" src="assets/images/recommendation_tag.png" alt="recommendation icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="page-article-details__actions absolute bottom-32 left-1/2 -translate-x-1/2 whitespace-nowrap"
|
||||
*ngIf="store.item$ | async; let item"
|
||||
>
|
||||
<button
|
||||
*ngIf="!(store.isDownload$ | async)"
|
||||
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-[1.875rem]"
|
||||
(click)="showAvailabilities()"
|
||||
>
|
||||
Bestände in anderen Filialen
|
||||
</button>
|
||||
<button
|
||||
class="text-white bg-brand border-brand font-bold text-lg px-[1.375rem] py-4 rounded-full border-none no-underline"
|
||||
(click)="showPurchasingModal()"
|
||||
[disabled]="!(isAvailable$ | async) || (fetchingAvailabilities$ | async) || (item?.features && (item?.features)[0]?.key === 'PFO')"
|
||||
>
|
||||
In den Warenkorb
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__recommendations-overlay absolute top-0 inset-x-0" @slideYAnimation *ngIf="showRecommendations">
|
||||
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<ng-container *ngIf="!showRecommendations">
|
||||
<div #detailsContainer class="page-article-details__container px-5 relative">
|
||||
<ng-container *ngIf="store.item$ | async; let item">
|
||||
<div class="page-article-details__product-details mb-3">
|
||||
<div class="page-article-details__product-bookmark justify-self-end">
|
||||
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
||||
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
|
||||
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
|
||||
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
|
||||
@@ -17,7 +400,10 @@
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="showSubscriptionBadge$ | async">
|
||||
<button [uiOverlayTrigger]="subscribtionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||
<button
|
||||
[uiOverlayTrigger]="subscribtionTooltip"
|
||||
class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]"
|
||||
>
|
||||
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
|
||||
</button>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
|
||||
@@ -27,7 +413,7 @@
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
|
||||
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
|
||||
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
|
||||
Dieser Artikel befindet sich im Prämienkatalog.
|
||||
@@ -46,7 +432,7 @@
|
||||
alt="product image"
|
||||
/>
|
||||
<ui-icon
|
||||
class="absolute text-[#A7B9CB] inline-block bottom-[14px] right-[18px]"
|
||||
class="absolute text-[#A7B9CB] inline-block bottom-[0.875rem] right-[1.125rem]"
|
||||
*ngIf="imageLoaded$ | async"
|
||||
icon="search_add"
|
||||
size="25px"
|
||||
@@ -196,7 +582,7 @@
|
||||
*ngIf="showDeliveryTruck$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
>
|
||||
<ui-icon class="-mb-px-5 -mt-px-5 mx-1" icon="truck" size="30px"></ui-icon>
|
||||
<ui-icon class="-mb-[0.3125rem] -mt-[0.3125rem] mx-1" icon="truck" size="30px"></ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -209,7 +595,7 @@
|
||||
*ngIf="showDeliveryB2BTruck$ | async"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
>
|
||||
<ui-icon class="-mb-px-10 -mt-px-10 mx-1" icon="truck_b2b" size="30px"> </ui-icon>
|
||||
<ui-icon class="-mb-[0.625rem] -mt-[0.625rem] mx-1" icon="truck_b2b" size="30px"> </ui-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -222,7 +608,7 @@
|
||||
|
||||
<div class="page-article-details__shelf-ssc">
|
||||
<div class="page-article-details__ssc flex justify-end my-2 font-bold text-lg">
|
||||
<div class="w-52 h-px-20 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
|
||||
<div class="w-52 h-5 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
|
||||
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
|
||||
<div class="text-right" *ngIf="store.sscText$ | async; let sscText">
|
||||
{{ sscText }}
|
||||
@@ -294,7 +680,7 @@
|
||||
class="mr-4 text-[#0556B4] font-bold no-underline px-2"
|
||||
*ngFor="let format of item.family"
|
||||
[routerLink]="getDetailsPath(format.product.ean)"
|
||||
[queryParamsHandling]="!(isTablet$ | async) ? 'preserve' : ''"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<img
|
||||
@@ -350,7 +736,7 @@
|
||||
>
|
||||
<button
|
||||
*ngIf="!(store.isDownload$ | async)"
|
||||
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-px-30"
|
||||
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-[1.875rem]"
|
||||
(click)="showAvailabilities()"
|
||||
>
|
||||
Bestände in anderen Filialen
|
||||
@@ -382,4 +768,4 @@
|
||||
|
||||
<div class="page-article-details__recommendations-overlay absolute rounded-t" @slideYAnimation *ngIf="showRecommendations">
|
||||
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
:host {
|
||||
@apply box-border block h-split-screen-tablet desktop-small:h-split-screen-desktop;
|
||||
@apply box-border block relative;
|
||||
}
|
||||
|
||||
.page-article-details__wrapper {
|
||||
@apply grid grid-rows-[1fr_auto] h-split-screen-tablet max-h-split-screen-tablet desktop-small:h-split-screen-desktop desktop-small:max-h-split-screen-desktop;
|
||||
}
|
||||
|
||||
.page-article-details__container {
|
||||
@apply h-full w-full overflow-y-scroll overflow-hidden bg-white rounded shadow-card flex flex-col;
|
||||
@apply overflow-scroll bg-white rounded shadow-card flex flex-col;
|
||||
}
|
||||
|
||||
.page-article-details__product-details {
|
||||
@@ -84,12 +88,10 @@
|
||||
}
|
||||
|
||||
.page-article-details__scroll-top-cta {
|
||||
@apply flex items-center justify-center self-end border-none outline-none bg-white relative rounded p-0 mt-8 mr-4;
|
||||
@apply w-[3.625rem] h-[3.625rem] flex items-center justify-center self-end border-none outline-none bg-white relative rounded p-0 mt-8 mr-4;
|
||||
box-shadow: 0px 0px 20px 0px rgba(89, 100, 112, 0.5);
|
||||
transform: rotate(-90deg);
|
||||
border-radius: 100%;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
.page-article-details__actions {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef, ViewChild, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
@@ -20,7 +20,7 @@ import { DateAdapter } from '@ui/common';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService, CustomerSearchNavigation } from '@shared/services';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
@@ -33,6 +33,8 @@ import { Store } from '@ngrx/store';
|
||||
animations: [slideYAnimation],
|
||||
})
|
||||
export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
private _customerSearchNavigation = inject(CustomerSearchNavigation);
|
||||
|
||||
private readonly subscriptions = new Subscription();
|
||||
showRecommendations: boolean;
|
||||
|
||||
@@ -411,6 +413,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch() {
|
||||
const nav = this._customerSearchNavigation.defaultRoute({ processId: this.applicationService.activatedProcessId });
|
||||
try {
|
||||
const response = await this.customerFeatures$
|
||||
.pipe(
|
||||
@@ -420,11 +423,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
)
|
||||
.toPromise();
|
||||
this._router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search'], {
|
||||
this._router.navigate(nav.path, {
|
||||
queryParams: { filter_customertype: response.filter.customertype },
|
||||
});
|
||||
} catch (error) {
|
||||
this._router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search']);
|
||||
this._router.navigate(nav.path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
class="article"
|
||||
*ngFor="let recommendation of store.recommendations$ | async"
|
||||
[routerLink]="getDetailsPath(recommendation.product.ean)"
|
||||
[queryParams]="{ main_qs: recommendation.product.ean }"
|
||||
[queryParams]="{ main_qs: recommendation.product.ean, filter_format: '' }"
|
||||
(click)="close.emit()"
|
||||
>
|
||||
<img [src]="recommendation.product?.ean | productImage: 195:315:true" alt="product-image" />
|
||||
|
||||
@@ -7,7 +7,6 @@ import { ArticleSearchService } from './article-search.store';
|
||||
import { FocusSearchboxEvent } from './focus-searchbox.event';
|
||||
import { ArticleSearchMainAutocompleteProvider } from './providers';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { FilterAutocompleteProvider } from '@shared/components/filter';
|
||||
|
||||
@Component({
|
||||
@@ -28,16 +27,11 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
private _processId$: Observable<number>;
|
||||
|
||||
get isTablet() {
|
||||
return this._environmentService.matchTablet();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _articleSearch: ArticleSearchService,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _environmentService: EnvironmentService
|
||||
private _navigationService: ProductCatalogNavigationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -49,26 +43,6 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
this.removeBreadcrumbs(processId);
|
||||
this.addOrUpdateBreadcrumbs(processId, queryParams);
|
||||
});
|
||||
|
||||
this._articleSearch.searchCompleted
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
.subscribe(async ([searchCompleted, processId]) => {
|
||||
if (searchCompleted.state.searchState === '') {
|
||||
const params = searchCompleted.state.filter.getQueryParams();
|
||||
if (searchCompleted.state.hits === 1) {
|
||||
const item = searchCompleted.state.items.find((f) => f);
|
||||
await this._navigationService
|
||||
.getArticleDetailsPath({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
extras: { queryParams: this.isTablet ? undefined : params },
|
||||
})
|
||||
.navigate();
|
||||
} else {
|
||||
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
|
||||
@@ -16,7 +16,6 @@ export interface ArticleSearchState {
|
||||
hits: number;
|
||||
selectedBranch: BranchDTO;
|
||||
selectedItemIds: number[];
|
||||
scrollPosition: number;
|
||||
defaultSettings?: UISettingsDTO;
|
||||
}
|
||||
|
||||
@@ -44,12 +43,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
return this.get((s) => s.items);
|
||||
}
|
||||
|
||||
scrollPosition$ = this.select((s) => s.scrollPosition);
|
||||
|
||||
get scrollPosition() {
|
||||
return this.get((s) => s.scrollPosition);
|
||||
}
|
||||
|
||||
selectedBranch$ = this.select((s) => s.selectedBranch);
|
||||
|
||||
get selectedBranch() {
|
||||
@@ -92,7 +85,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
searchState: '',
|
||||
selectedItemIds: [],
|
||||
selectedBranch: undefined,
|
||||
scrollPosition: 0,
|
||||
});
|
||||
this.setDefaultFilter();
|
||||
}
|
||||
@@ -113,10 +105,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
this.patchState({ selectedBranch });
|
||||
}
|
||||
|
||||
setScrollPosition(scrollPosition: number) {
|
||||
this.patchState({ scrollPosition });
|
||||
}
|
||||
|
||||
async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
|
||||
const defaultSettings = await this.catalog.getSettings().toPromise();
|
||||
|
||||
@@ -160,7 +148,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
}
|
||||
}
|
||||
|
||||
search = this.effect((options$: Observable<{ clear?: boolean; orderBy?: boolean }>) =>
|
||||
search = this.effect((options$: Observable<{ clear?: boolean; orderBy?: boolean; doNotTrack?: boolean }>) =>
|
||||
options$.pipe(
|
||||
tap((options) => {
|
||||
this.searchStarted.next({ clear: options?.clear });
|
||||
@@ -178,6 +166,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
take: 25,
|
||||
friendlyName: this.friendlyName,
|
||||
stockId: selectedBranch?.id,
|
||||
doNotTrack: options?.doNotTrack,
|
||||
}).pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Filter, FilterComponent, FilterInput } from 'apps/shared/components/filter/src/lib';
|
||||
import { Filter, FilterComponent } from 'apps/shared/components/filter/src/lib';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-search-filter',
|
||||
@@ -28,10 +28,6 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
|
||||
showFilter: boolean = false;
|
||||
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
@@ -73,6 +69,7 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.showFilter = this.sideOutlet !== 'search';
|
||||
this._activatedRoute.queryParams
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe(async (queryParams) => await this.articleSearch.setDefaultFilter(queryParams));
|
||||
@@ -91,6 +88,26 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
|
||||
await this.articleSearch.setDefaultFilter(queryParams);
|
||||
});
|
||||
|
||||
this.articleSearch.searchCompleted
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
.subscribe(async ([searchCompleted, processId]) => {
|
||||
if (searchCompleted.state.searchState === '') {
|
||||
const params = searchCompleted.state.filter.getQueryParams();
|
||||
if (searchCompleted.state.hits === 1) {
|
||||
const item = searchCompleted.state.items.find((f) => f);
|
||||
await this._navigationService
|
||||
.getArticleDetailsPath({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
extras: { queryParams: params },
|
||||
})
|
||||
.navigate();
|
||||
} else {
|
||||
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -105,17 +122,17 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
.subscribe(async ([searchCompleted, processId]) => {
|
||||
if (searchCompleted.state.searchState === '') {
|
||||
// Check if desktop is necessary, otherwise it would trigger navigation twice (Inside Article-Search.component and here)
|
||||
if (searchCompleted.state.hits === 1 && !this.isDesktop) {
|
||||
const params = searchCompleted.state.filter.getQueryParams();
|
||||
if (searchCompleted.state.hits === 1) {
|
||||
const item = searchCompleted.state.items.find((f) => f);
|
||||
await this._navigationService
|
||||
.getArticleDetailsPath({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
extras: { queryParams: params },
|
||||
})
|
||||
.navigate();
|
||||
} else if (!this.isDesktop) {
|
||||
const params = searchCompleted.state.filter.getQueryParams();
|
||||
} else {
|
||||
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { combineLatest, NEVER, Subscription } from 'rxjs';
|
||||
import { catchError, debounceTime, first, switchMap, map } from 'rxjs/operators';
|
||||
import { catchError, debounceTime, first, switchMap, map, tap } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { isEqual } from 'lodash';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-search-main',
|
||||
@@ -17,7 +18,10 @@ import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/fi
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
readonly history$ = this.catalog.getSearchHistory({ take: 7 }).pipe(catchError(() => NEVER));
|
||||
readonly history$ = this.catalog.getSearchHistory({ take: 7 }).pipe(
|
||||
map((history) => history.filter((h) => !!h.friendlyName)),
|
||||
catchError(() => NEVER)
|
||||
);
|
||||
|
||||
fetching$ = this.searchService.fetching$;
|
||||
|
||||
@@ -49,7 +53,8 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
private route: ActivatedRoute,
|
||||
private application: ApplicationService,
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private _environment: EnvironmentService
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -77,6 +82,14 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.removeResultsAndDetailsBreadcrumbs(processId);
|
||||
|
||||
// #4519 und #4520 Durch den Performance Umbau, wird auf allen größen kleiner als der Splitscreen die Article-Search Komponente nicht geladen
|
||||
// Stattdessen werden die Komponenten search-main und search-filter geladen. Einige Funktionen von Article-Search müssen trotzdem aufgerufen werden
|
||||
if (!this._environment.matchDesktopLarge()) {
|
||||
this.resetFilter(queryParams);
|
||||
this.removeCheckoutBreadcrumb(processId);
|
||||
this.addOrUpdateBreadcrumbs(processId, queryParams);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -160,6 +173,27 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
resetFilter(queryParams: Record<string, string>) {
|
||||
if (Object.keys(queryParams).length === 0) {
|
||||
this.searchService.resetFilter();
|
||||
}
|
||||
}
|
||||
|
||||
async removeCheckoutBreadcrumb(processId: number) {
|
||||
this.breadcrumb.removeBreadcrumbsByKeyAndTags(processId, ['checkout']);
|
||||
}
|
||||
|
||||
async addOrUpdateBreadcrumbs(processId: number, queryParams: Record<string, string>) {
|
||||
await this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name: 'Artikelsuche',
|
||||
path: this._navigationService.getArticleSearchBasePath(processId).path,
|
||||
params: queryParams,
|
||||
tags: ['catalog', 'main'],
|
||||
section: 'customer',
|
||||
});
|
||||
}
|
||||
|
||||
async removeResultsAndDetailsBreadcrumbs(processId: number) {
|
||||
const resultsCrumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'results']).pipe(first()).toPromise();
|
||||
const detailCrumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'details']).pipe(first()).toPromise();
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<a
|
||||
<div
|
||||
class="page-search-result-item__item-card hover p-5 desktop-small:px-4 desktop-small:py-[0.625rem] h-[13.25rem] desktop-small:h-[11.3125rem] bg-white border border-solid border-transparent rounded"
|
||||
[class.page-search-result-item__item-card-primary]="primaryOutletActive"
|
||||
[routerLink]="detailsPath"
|
||||
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
|
||||
[queryParamsHandling]="!isTablet ? 'preserve' : ''"
|
||||
(click)="isDesktop ? scrollIntoView() : ''"
|
||||
[class.active]="isActive"
|
||||
>
|
||||
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[50px] h-[79px]">
|
||||
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
|
||||
<img
|
||||
class="page-search-result-item__item-image w-[50px] h-[79px]"
|
||||
class="page-search-result-item__item-image w-[3.125rem] h-[4.9375rem]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
|
||||
[src]="thumbnailUrl"
|
||||
@@ -122,4 +119,4 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding, ElementRef } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
|
||||
@@ -54,6 +54,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
@Input()
|
||||
primaryOutletActive?: boolean = false;
|
||||
|
||||
@Input() isActive: boolean;
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<ItemDTO>();
|
||||
|
||||
@@ -78,13 +80,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
|
||||
get detailsPath() {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: this.item?.id })
|
||||
.path;
|
||||
get isDesktopLarge() {
|
||||
return this._environment.matchDesktopLarge();
|
||||
}
|
||||
|
||||
get resultsPath() {
|
||||
@@ -141,7 +138,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _elRef: ElementRef<HTMLElement>,
|
||||
private _store: Store
|
||||
) {
|
||||
super({
|
||||
@@ -150,18 +146,9 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
});
|
||||
}
|
||||
|
||||
scrollIntoView() {
|
||||
this._elRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
setSelected() {
|
||||
const isSelected = this._articleSearchService.selectedItemIds.includes(this.item?.id);
|
||||
this._articleSearchService.setSelected({ selected: !isSelected, itemId: this.item?.id });
|
||||
|
||||
// #4135 Code auskommentiert bis zur Klärung
|
||||
// if (!this.isTablet) {
|
||||
// this.selectedChange.emit(this.item);
|
||||
// }
|
||||
}
|
||||
|
||||
async showTooltip() {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
[class.pb-4]="!(primaryOutletActive$ | async)"
|
||||
[class.flex-col]="!(primaryOutletActive$ | async)"
|
||||
>
|
||||
<div class="flex flex-row w-full desktop-small:w-min" [class.desktop-large:w-full]="!(primaryOutletActive$ | async)">
|
||||
<div class="flex flex-row w-full desktop:w-min" [class.desktop-large:w-full]="!(primaryOutletActive$ | async)">
|
||||
<shared-filter-input-group-main
|
||||
*ngIf="filter$ | async; let filter"
|
||||
class="block mr-3 w-full desktop-small:w-[23.5rem]"
|
||||
class="block mr-3 w-full desktop:w-[23.5rem]"
|
||||
[class.desktop-large:w-full]="!(primaryOutletActive$ | async)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-search-results__order-by" [class.page-search-results__order-by-primary]="primaryOutletActive$ | async">
|
||||
<div class="page-search-results__order-by mb-[0.125rem]" [class.page-search-results__order-by-primary]="primaryOutletActive$ | async">
|
||||
<shared-order-by-filter
|
||||
[orderBy]="(filter$ | async)?.orderBy"
|
||||
(selectedOrderByChange)="search({ clear: true, orderBy: true }); updateBreadcrumbs()"
|
||||
@@ -46,29 +46,27 @@
|
||||
</shared-order-by-filter>
|
||||
</div>
|
||||
|
||||
<div class="h-full relative">
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="product-list h-full"
|
||||
[itemSize]="(primaryOutletActive$ | async) ? 98 : 181"
|
||||
minBufferPx="1200"
|
||||
[maxBufferPx]="maxBufferCdkScrollContainer$ | async"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
[class.page-search-results__result-item-primary]="primaryOutletActive$ | async"
|
||||
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selected]="isSelected(item)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[primaryOutletActive]="primaryOutletActive$ | async"
|
||||
></search-result-item>
|
||||
<page-search-result-item-loading
|
||||
[primaryOutletActive]="primaryOutletActive$ | async"
|
||||
*ngIf="fetching$ | async"
|
||||
></page-search-result-item-loading>
|
||||
<ng-container *ngIf="primaryOutletActive$ | async; else sideOutlet">
|
||||
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="106 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
|
||||
<a
|
||||
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
|
||||
[routerLink]="getDetailsPath(item.id)"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
queryParamsHandling="preserve"
|
||||
(click)="scrollToItem(i)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item page-search-results__result-item-primary"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selected]="isSelected(item)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[primaryOutletActive]="true"
|
||||
[isActive]="rla.isActive"
|
||||
></search-result-item>
|
||||
</a>
|
||||
<page-search-result-item-loading [primaryOutletActive]="true" *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
<button
|
||||
@@ -80,4 +78,38 @@
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #sideOutlet>
|
||||
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="222 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
|
||||
<a
|
||||
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
|
||||
[routerLink]="getDetailsPath(item.id)"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
queryParamsHandling="preserve"
|
||||
(click)="scrollToItem(i)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selected]="isSelected(item)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[primaryOutletActive]="false"
|
||||
[isActive]="rla.isActive"
|
||||
></search-result-item>
|
||||
</a>
|
||||
<page-search-result-item-loading [primaryOutletActive]="false" *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
:host {
|
||||
@apply box-border grid h-split-screen-tablet desktop-small:h-split-screen-desktop;
|
||||
@apply box-border grid h-split-screen-tablet desktop-small:h-split-screen-desktop relative;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
}
|
||||
|
||||
.product-list {
|
||||
@apply m-0 p-0 mt-px-2;
|
||||
@apply m-0 p-0;
|
||||
}
|
||||
|
||||
.page-search-results__result-item {
|
||||
@apply mb-px-10;
|
||||
@apply mb-[0.625rem];
|
||||
}
|
||||
|
||||
.page-search-results__result-item-primary {
|
||||
@apply mb-[5px];
|
||||
@apply mb-[0.3125rem];
|
||||
}
|
||||
|
||||
.page-search-results__order-by {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
QueryList,
|
||||
TrackByFunction,
|
||||
AfterViewInit,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
@@ -19,7 +20,7 @@ import { ItemDTO } from '@swagger/cat';
|
||||
import { AddToShoppingCartDTO } from '@swagger/checkout';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { CacheService } from 'apps/core/cache/src/public-api';
|
||||
import { isEqual } from 'lodash';
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
@@ -28,6 +29,8 @@ import { SearchResultItemComponent } from './search-result-item.component';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
|
||||
import { DomainAvailabilityService, ItemData } from '@domain/availability';
|
||||
import { asapScheduler } from 'rxjs';
|
||||
import { ShellService } from '@shared/shell';
|
||||
|
||||
@Component({
|
||||
selector: 'page-search-results',
|
||||
@@ -37,7 +40,7 @@ import { DomainAvailabilityService, ItemData } from '@domain/availability';
|
||||
})
|
||||
export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
@ViewChildren(SearchResultItemComponent) listItems: QueryList<SearchResultItemComponent>;
|
||||
@ViewChild('scrollContainer', { static: true })
|
||||
@ViewChild(CdkVirtualScrollViewport, { static: false })
|
||||
scrollContainer: CdkVirtualScrollViewport;
|
||||
|
||||
@ViewChild(FilterInputGroupMainComponent, { static: false })
|
||||
@@ -59,6 +62,10 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
})
|
||||
);
|
||||
|
||||
getProcessId(): number {
|
||||
return this.application.activatedProcessId;
|
||||
}
|
||||
|
||||
loading$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private subscriptions = new Subscription();
|
||||
@@ -69,8 +76,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
get isDesktopLarge() {
|
||||
return this._environment.matchDesktopLarge();
|
||||
}
|
||||
|
||||
hasFilter$ = combineLatest([this.searchService.filter$, this.searchService.defaultSettings$]).pipe(
|
||||
@@ -89,23 +96,14 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
|
||||
get primaryOutletActive$() {
|
||||
return this._environment.matchTablet$.pipe(map((matches) => !matches && this.route.outlet === 'primary'));
|
||||
return this._environment.matchDesktop$.pipe(map((matches) => matches && this.route.outlet === 'primary'));
|
||||
}
|
||||
|
||||
// Ticket #4169 Splitscreen
|
||||
// Render genug Artikel um bei Navigation auf Trefferliste | PDP zum angewählten Artikel zu Scrollen
|
||||
maxBufferCdkScrollContainer$ = this.results$.pipe(
|
||||
withLatestFrom(this.primaryOutletActive$),
|
||||
map(([results, primaryOutlet]) => {
|
||||
if (!primaryOutlet && results?.length > 0) {
|
||||
// Splitscreen mode: Items Length * Item Pixel Height
|
||||
const maxBufferSize = results.length * 181;
|
||||
return maxBufferSize >= 1200 ? maxBufferSize : 1200;
|
||||
} else {
|
||||
return 1200;
|
||||
}
|
||||
})
|
||||
);
|
||||
private readonly SCROLL_INDEX_TOKEN = 'CATALOG_RESULTS_LIST_SCROLL_INDEX';
|
||||
|
||||
shellService = inject(ShellService);
|
||||
|
||||
scale$ = this.shellService.scale$;
|
||||
|
||||
constructor(
|
||||
public searchService: ArticleSearchService,
|
||||
@@ -156,9 +154,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||
|
||||
// Scroll to scroll_position in great result list
|
||||
if (!!queryParams?.scroll_position && this.route.outlet === 'primary') {
|
||||
this.scrollTop(Number(queryParams.scroll_position ?? 0));
|
||||
if (this.route.outlet === 'primary' && processChanged) {
|
||||
this.scrollToItem(this._getScrollIndexFromCache());
|
||||
}
|
||||
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
||||
@@ -174,11 +171,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
) {
|
||||
this.search({ clear: true });
|
||||
} else {
|
||||
if (!this.isDesktop || this.route.outlet === 'primary') {
|
||||
this.scrollTop(Number(queryParams.scroll_position ?? 0));
|
||||
} else {
|
||||
this.scrollItemIntoView();
|
||||
}
|
||||
const selectedItemIds: Array<string> = queryParams?.selected_item_ids?.split(',') ?? [];
|
||||
for (const id of selectedItemIds) {
|
||||
if (id) {
|
||||
@@ -194,7 +186,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
await this.createBreadcrumb(processId, queryParams);
|
||||
}
|
||||
|
||||
if (!this.isDesktop || this.route?.outlet === 'primary') {
|
||||
if (this.route?.outlet === 'primary') {
|
||||
await this.removeDetailsBreadcrumb(processId);
|
||||
}
|
||||
})
|
||||
@@ -224,7 +216,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
.getArticleDetailsPathByEan({
|
||||
processId,
|
||||
ean,
|
||||
extras: { queryParams: this.isTablet ? undefined : params },
|
||||
extras: { queryParams: params },
|
||||
})
|
||||
.navigate();
|
||||
} else {
|
||||
@@ -232,12 +224,24 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
.getArticleDetailsPath({
|
||||
processId,
|
||||
itemId,
|
||||
extras: { queryParams: this.isTablet ? undefined : params },
|
||||
extras: { queryParams: params },
|
||||
})
|
||||
.navigate();
|
||||
}
|
||||
} else if ((searchCompleted?.clear || this.route.outlet === 'primary') && !this.isTablet) {
|
||||
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
|
||||
} else if (searchCompleted?.clear || this.route.outlet === 'primary') {
|
||||
const ean = this.route?.snapshot?.params?.ean;
|
||||
|
||||
if (ean) {
|
||||
await this._navigationService
|
||||
.getArticleDetailsPathByEan({
|
||||
processId,
|
||||
ean,
|
||||
extras: { queryParams: params },
|
||||
})
|
||||
.navigate();
|
||||
} else {
|
||||
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -259,7 +263,41 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.scrollItemIntoView();
|
||||
this.scrollToItem(this._getScrollIndexFromCache());
|
||||
}
|
||||
|
||||
private _addScrollIndexToCache(index: number): void {
|
||||
this.cache.set<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }, index);
|
||||
}
|
||||
|
||||
private _getScrollIndexFromCache(): number {
|
||||
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN });
|
||||
}
|
||||
|
||||
scrollToItem(i?: number) {
|
||||
let index = i;
|
||||
|
||||
if (!index) {
|
||||
index = this._getScrollIndexFromCache();
|
||||
} else {
|
||||
this._addScrollIndexToCache(index);
|
||||
}
|
||||
|
||||
asapScheduler.schedule(() => {
|
||||
this.scrollContainer.scrollToIndex(index, 'smooth');
|
||||
}, 150);
|
||||
}
|
||||
|
||||
scrolledIndexChange(index: number) {
|
||||
const completeListFetched = this.searchService.items.length === this.searchService.hits;
|
||||
|
||||
if (index && !completeListFetched && this.searchService.items.length <= this.scrollContainer?.getRenderedRange()?.end) {
|
||||
this.search({ clear: false });
|
||||
}
|
||||
|
||||
if (this.getProcessId() === this.searchService.processId) {
|
||||
this._addScrollIndexToCache(index);
|
||||
}
|
||||
}
|
||||
|
||||
async ngOnDestroy() {
|
||||
@@ -290,42 +328,17 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||
}
|
||||
|
||||
this.searchService.search({ clear, orderBy });
|
||||
this.searchService.search({ clear, orderBy, doNotTrack: true });
|
||||
}
|
||||
|
||||
scrollTop(scrollPos: number) {
|
||||
setTimeout(() => this.scrollContainer.scrollTo({ top: scrollPos }), 0);
|
||||
}
|
||||
|
||||
scrollItemIntoView() {
|
||||
setTimeout(() => {
|
||||
const item = this.listItems?.find((item) => item.item.id === Number(this.route?.snapshot?.params?.id));
|
||||
item?.scrollIntoView();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
async scrolledIndexChange(index: number) {
|
||||
const results = await this.results$.pipe(first()).toPromise();
|
||||
const hits = await this.hits$.pipe(first()).toPromise();
|
||||
|
||||
if (results.length >= hits) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isDesktop || this.route.outlet === 'primary') {
|
||||
this.searchService.setScrollPosition(this.scrollContainer.measureScrollOffset('top'));
|
||||
}
|
||||
|
||||
if (index >= results.length - 20 && results.length - 20 > 0) {
|
||||
this.search({ clear: false });
|
||||
}
|
||||
getDetailsPath(itemId: number) {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.application.activatedProcessId, itemId }).path;
|
||||
}
|
||||
|
||||
async updateBreadcrumbs(
|
||||
processId: number = this.searchService.processId,
|
||||
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams()
|
||||
) {
|
||||
const scroll_position = this.searchService.scrollPosition;
|
||||
const selected_item_ids = this.searchService?.selectedItemIds?.toString();
|
||||
|
||||
if (queryParams) {
|
||||
@@ -335,7 +348,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
.toPromise();
|
||||
|
||||
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
|
||||
const params = { ...queryParams, scroll_position, selected_item_ids };
|
||||
const params = { ...queryParams, selected_item_ids };
|
||||
|
||||
for (const crumb of crumbs) {
|
||||
this.breadcrumb.patchBreadcrumb(crumb.id, {
|
||||
@@ -388,7 +401,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
delete clean['scroll_position'];
|
||||
delete clean['selected_item_ids'];
|
||||
|
||||
for (const key in clean) {
|
||||
@@ -480,6 +492,9 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
// #4180 Für Download Artikel muss hier immer zwingend der logistician gesetzt werden, da diese Artikel direkt zugeordnet dem Warenkorb hinzugefügt werden
|
||||
const downloadAvailability = await this._availability.getDownloadAvailability({ item: downloadItem }).pipe(first()).toPromise();
|
||||
shoppingCartItem.destination = { data: { target: 16, logistician: downloadAvailability?.logistician } };
|
||||
if (downloadAvailability) {
|
||||
shoppingCartItem.availability = { ...shoppingCartItem.availability, ...downloadAvailability };
|
||||
}
|
||||
canAddItemsPayload.push({
|
||||
availabilities: [{ ...item.catalogAvailability, format: 'DL' }],
|
||||
id: item.product.catalogProductNumber,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<shared-breadcrumb class="mb-5 desktop-small:mb-9" [key]="activatedProcessId$ | async" [tags]="['catalog']">
|
||||
<shared-breadcrumb [key]="activatedProcessId$ | async" [tags]="['catalog']">
|
||||
<shared-branch-selector
|
||||
[uiOverlayTrigger]="tooltip"
|
||||
[overlayTriggerDisabled]="stockTooltipDisabled"
|
||||
|
||||
@@ -31,7 +31,7 @@ shared-branch-selector.shared-branch-selector-opend {
|
||||
shared-branch-selector.shared-branch-selector-opend
|
||||
.shared-branch-selector-input-container
|
||||
.shared-branch-selector-input-icon {
|
||||
@apply pl-0 border-l-0;
|
||||
@apply border-l-0;
|
||||
}
|
||||
|
||||
::ng-deep .tablet page-catalog shared-breadcrumb:focus-within .shared-breadcrumb__suffix {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<page-checkout-review-details></page-checkout-review-details>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let group of groupedItems$ | async; let lastGroup = last">
|
||||
<ng-container *ngFor="let group of groupedItems$ | async; let lastGroup = last; trackBy: trackByGroupedItems">
|
||||
<ng-container *ngIf="group?.orderType !== undefined">
|
||||
<hr />
|
||||
<div class="row item-group-header bg-[#F5F7FA]">
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
QueryList,
|
||||
AfterViewInit,
|
||||
TrackByFunction,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
@@ -16,7 +17,7 @@ import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
||||
import { delay, first, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { Subject, NEVER, combineLatest, BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
@@ -29,6 +30,7 @@ import { EnvironmentService } from '@core/environment';
|
||||
import { CheckoutReviewStore } from './checkout-review.store';
|
||||
import { ToasterService } from '@shared/shell';
|
||||
import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-item.component';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-review',
|
||||
@@ -37,6 +39,9 @@ import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-it
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
private _customerSearchNavigation = inject(CustomerSearchNavigation);
|
||||
checkingOla$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
payer$ = this._store.payer$;
|
||||
@@ -54,6 +59,9 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
map((items) => items?.filter((item) => item?.features?.orderType === undefined))
|
||||
);
|
||||
|
||||
trackByGroupedItems: TrackByFunction<{ orderType: string; destination: DestinationDTO; items: ShoppingCartItemDTO[] }> = (_, item) =>
|
||||
item?.orderType + item?.destination?.id;
|
||||
|
||||
groupedItems$ = this._store.shoppingCartItems$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
map((items) =>
|
||||
@@ -164,6 +172,7 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
showOrderButtonSpinner: boolean;
|
||||
|
||||
checkoutIsInValid$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
switchMap((processId) => this.domainCheckoutService.checkoutIsValid({ processId })),
|
||||
map((valid) => !valid)
|
||||
);
|
||||
@@ -176,8 +185,6 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
return this._environmentService.matchDesktopLarge$;
|
||||
}
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
@ViewChildren(ShoppingCartItemComponent)
|
||||
private _shoppingCartItems: QueryList<ShoppingCartItemComponent>;
|
||||
|
||||
@@ -224,13 +231,15 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
this.resetControl();
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
this.checkingOla$.complete();
|
||||
this.quantityError$.complete();
|
||||
this.olaCheckSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
registerOlaCechk() {
|
||||
this.olaCheckSubscription?.unsubscribe();
|
||||
this.olaCheckSubscription = this.applicationService.activatedProcessId$
|
||||
.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
delay(250),
|
||||
switchMap((processId) =>
|
||||
this.domainCheckoutService.validateOlaStatus({
|
||||
@@ -250,7 +259,7 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
|
||||
for (let itemComp of this._shoppingCartItems.toArray()) {
|
||||
await itemComp.refreshAvailability();
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
this.checkingOla$.next(false);
|
||||
}
|
||||
@@ -467,6 +476,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch(processId: number) {
|
||||
const nav = this._customerSearchNavigation.defaultRoute({ processId });
|
||||
|
||||
try {
|
||||
const response = await this.customerFeatures$
|
||||
.pipe(
|
||||
@@ -477,11 +488,11 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search'], {
|
||||
this.router.navigate(nav.path, {
|
||||
queryParams: { filter_customertype: response.filter.customertype },
|
||||
});
|
||||
} catch (error) {
|
||||
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search']);
|
||||
this.router.navigate(nav.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +512,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
|
||||
return;
|
||||
}
|
||||
const customerId = customer.source;
|
||||
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', `${customerId}`]);
|
||||
const nav = this._customerSearchNavigation.detailsRoute({ processId, customerId });
|
||||
this.router.navigate(nav.path);
|
||||
}
|
||||
|
||||
async order() {
|
||||
|
||||
@@ -20,7 +20,7 @@ import { CheckoutReviewDetailsComponent } from './details/checkout-review-detail
|
||||
import { CheckoutReviewStore } from './checkout-review.store';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
import { LoaderComponent } from '@shared/components/loader';
|
||||
import { LoaderComponent, SkeletonLoaderComponent } from '@shared/components/loader';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -41,6 +41,7 @@ import { LoaderComponent } from '@shared/components/loader';
|
||||
SharedNotificationChannelControlModule,
|
||||
TextFieldModule,
|
||||
LoaderComponent,
|
||||
SkeletonLoaderComponent,
|
||||
],
|
||||
exports: [CheckoutReviewComponent, CheckoutReviewDetailsComponent],
|
||||
declarations: [CheckoutReviewComponent, SpecialCommentComponent, ShoppingCartItemComponent, CheckoutReviewDetailsComponent],
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<ng-container *ngIf="payer$ | async; let payer">
|
||||
<div *ngIf="showAddresses$ | async" class="flex flex-row items-start justify-between p-5 pt-0">
|
||||
<div class="flex flex-row flex-wrap pr-4">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Rechnungsadresse">
|
||||
<div class="mr-3">Rechnungsadresse</div>
|
||||
<div class="font-bold">
|
||||
{{ payer | payerAddress }}
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<ng-container *ngIf="payer$ | async; let payer">
|
||||
<div *ngIf="showAddresses$ | async" class="flex flex-row items-start justify-between px-5">
|
||||
<div class="flex flex-row flex-wrap pr-4">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Lieferadresse">
|
||||
<div class="mr-3">Lieferadresse</div>
|
||||
<div class="font-bold">
|
||||
{{ shippingAddress$ | async | shippingAddress }}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { NotificationChannel } from '@swagger/checkout';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewDetailsComponent implements OnInit {
|
||||
control = this._store.notificationsControl;
|
||||
control: UntypedFormGroup;
|
||||
|
||||
customerFeatures$ = this._store.customerFeatures$;
|
||||
|
||||
@@ -96,15 +96,18 @@ export class CheckoutReviewDetailsComponent implements OnInit {
|
||||
selectedNotificationChannel = 1;
|
||||
}
|
||||
|
||||
this.control = fb.group({
|
||||
notificationChannel: new UntypedFormGroup({
|
||||
selected: new UntypedFormControl(selectedNotificationChannel),
|
||||
email: new UntypedFormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
|
||||
mobile: new UntypedFormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
|
||||
}),
|
||||
});
|
||||
|
||||
this._store.notificationsControl = this.control;
|
||||
if (!this._store.notificationsControl) {
|
||||
this.control = fb.group({
|
||||
notificationChannel: new UntypedFormGroup({
|
||||
selected: new UntypedFormControl(selectedNotificationChannel),
|
||||
email: new UntypedFormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
|
||||
mobile: new UntypedFormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
|
||||
}),
|
||||
});
|
||||
this._store.notificationsControl = this.control;
|
||||
} else {
|
||||
this.control = this._store.notificationsControl;
|
||||
}
|
||||
}
|
||||
|
||||
setAgentComment(agentComment: string) {
|
||||
|
||||
@@ -43,13 +43,16 @@
|
||||
<div *ngIf="notAvailable$ | async">
|
||||
<span class="text-brand item-date">Nicht verfügbar</span>
|
||||
</div>
|
||||
<ui-spinner class="ava-loader" [show]="refreshingAvailabilit$ | async">
|
||||
<div class="item-date" [class.ssc-changed]="sscChanged$ | async" *ngIf="orderType === 'Abholung'">
|
||||
|
||||
<shared-skeleton-loader class="w-40" *ngIf="refreshingAvailabilit$ | async; else avaTmplt"> </shared-skeleton-loader>
|
||||
|
||||
<ng-template #avaTmplt>
|
||||
<div class="item-date" [class.availability-changed]="estimatedShippingDateChanged$ | async" *ngIf="orderType === 'Abholung'">
|
||||
Abholung ab {{ item?.availability?.estimatedShippingDate | date }}
|
||||
</div>
|
||||
<div
|
||||
class="item-date"
|
||||
[class.ssc-changed]="sscChanged$ | async"
|
||||
[class.availability-changed]="estimatedShippingDateChanged$ | async"
|
||||
*ngIf="orderType === 'Versand' || orderType === 'B2B-Versand' || orderType === 'DIG-Versand'"
|
||||
>
|
||||
<ng-container *ngIf="item?.availability?.estimatedDelivery; else estimatedShippingDate">
|
||||
@@ -59,7 +62,7 @@
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate> Versand {{ item?.availability?.estimatedShippingDate | date }} </ng-template>
|
||||
</div>
|
||||
</ui-spinner>
|
||||
</ng-template>
|
||||
|
||||
<div class="item-availability-message" *ngIf="olaError$ | async">
|
||||
Artikel nicht verfügbar
|
||||
|
||||
@@ -101,7 +101,7 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.ssc-changed {
|
||||
.availability-changed {
|
||||
@apply text-dark-goldenrod;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ComponentStore } from '@ngrx/component-store';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { ItemType, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
|
||||
@@ -18,6 +19,7 @@ export interface ShoppingCartItemComponentState {
|
||||
refreshingAvailability: boolean;
|
||||
sscChanged: boolean;
|
||||
sscTextChanged: boolean;
|
||||
estimatedShippingDateChanged: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -137,6 +139,8 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
||||
|
||||
sscChanged$ = this.select((s) => s.sscChanged || s.sscTextChanged);
|
||||
|
||||
estimatedShippingDateChanged$ = this.select((s) => s.estimatedShippingDateChanged);
|
||||
|
||||
notAvailable$ = this.item$.pipe(
|
||||
map((item) => {
|
||||
const availability = item?.availability;
|
||||
@@ -161,7 +165,14 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
||||
private _environment: EnvironmentService,
|
||||
private _cdr: ChangeDetectorRef
|
||||
) {
|
||||
super({ item: undefined, orderType: '', refreshingAvailability: false, sscChanged: false, sscTextChanged: false });
|
||||
super({
|
||||
item: undefined,
|
||||
orderType: '',
|
||||
refreshingAvailability: false,
|
||||
sscChanged: false,
|
||||
sscTextChanged: false,
|
||||
estimatedShippingDateChanged: false,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
@@ -192,6 +203,11 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
||||
if (currentAvailability.sscText !== availability.sscText) {
|
||||
this.ssctextChanged();
|
||||
}
|
||||
if (
|
||||
moment(currentAvailability.estimatedShippingDate).startOf('day').diff(moment(availability.estimatedShippingDate).startOf('day'))
|
||||
) {
|
||||
this.estimatedShippingDateChanged();
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
this.patchRefreshingAvailability(false);
|
||||
@@ -206,16 +222,17 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
||||
}
|
||||
|
||||
ssctextChanged() {
|
||||
this._zone.run(() => {
|
||||
this.patchState({ sscTextChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
});
|
||||
this.patchState({ sscTextChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
sscChanged() {
|
||||
this._zone.run(() => {
|
||||
this.patchState({ sscChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
});
|
||||
this.patchState({ sscChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
estimatedShippingDateChanged() {
|
||||
this.patchState({ estimatedShippingDateChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,202 +1,201 @@
|
||||
<div class="flex flex-col bg-white rounded pt-10">
|
||||
<div class="rounded-[50%] bg-[#26830C] w-8 h-8 flex items-center justify-center self-center">
|
||||
<shared-icon class="text-white" icon="done" [size]="24"></shared-icon>
|
||||
</div>
|
||||
<div class="summary-wrapper">
|
||||
<div class="flex flex-col bg-white rounded pt-10 mb-24">
|
||||
<div class="rounded-[50%] bg-[#26830C] w-8 h-8 flex items-center justify-center self-center">
|
||||
<shared-icon class="text-white" icon="done" [size]="24"></shared-icon>
|
||||
</div>
|
||||
|
||||
<h1 class="text-center text-h2 my-1 font-bold">Bestellbestätigung</h1>
|
||||
<p class="text-center text-p1 mb-10">
|
||||
Nachfolgend erhalten Sie die Übersicht Ihrer Bestellung.
|
||||
</p>
|
||||
<h1 class="text-center text-h2 my-1 font-bold">Bestellbestätigung</h1>
|
||||
<p class="text-center text-p1 mb-10">
|
||||
Nachfolgend erhalten Sie die Übersicht Ihrer Bestellung.
|
||||
</p>
|
||||
|
||||
<ng-container *ngFor="let displayOrder of displayOrders$ | async; let i = index; let orderLast = last">
|
||||
<ng-container *ngIf="i === 0">
|
||||
<div class="flex flex-row items-center bg-white shadow-card min-h-[3.3125rem]">
|
||||
<div class="text-h3 font-bold px-5 py-[0.875rem]">
|
||||
{{ displayOrder?.buyer | buyerName }}
|
||||
<ng-container *ngFor="let displayOrder of displayOrders$ | async; let i = index; let orderLast = last">
|
||||
<ng-container *ngIf="i === 0">
|
||||
<div class="flex flex-row items-center bg-white shadow-card min-h-[3.3125rem]">
|
||||
<div class="text-h3 font-bold px-5 py-[0.875rem]">
|
||||
{{ displayOrder?.buyer | buyerName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
</ng-container>
|
||||
|
||||
<div class="flex flex-row items-center bg-[#F5F7FA] min-h-[3.3125rem]">
|
||||
<div class="flex flex-row items-center justify-center px-5 py-[0.875rem]">
|
||||
<shared-icon
|
||||
*ngIf="(displayOrder?.items)[0]?.features?.orderType !== 'Dummy'"
|
||||
class="mr-2"
|
||||
[size]="(displayOrder?.items)[0]?.features?.orderType === 'B2B-Versand' ? 36 : 24"
|
||||
[icon]="(displayOrder?.items)[0]?.features?.orderType"
|
||||
></shared-icon>
|
||||
<p class="text-p1 font-bold mr-3">{{ (displayOrder?.items)[0]?.features?.orderType }}</p>
|
||||
<div
|
||||
*ngIf="
|
||||
(displayOrder?.items)[0]?.features?.orderType === 'Abholung' || (displayOrder?.items)[0]?.features?.orderType === 'Rücklage';
|
||||
else shippingAddress
|
||||
"
|
||||
>
|
||||
{{ displayOrder.targetBranch?.name }}, {{ displayOrder.targetBranch | branchAddress }}
|
||||
</div>
|
||||
<ng-template #shippingAddress>
|
||||
{{ displayOrder.shippingAddress | branchAddress }}
|
||||
</ng-template>
|
||||
<div *ngIf="(displayOrder?.items)[0]?.features?.orderType === 'Download'">
|
||||
| {{ displayOrder.buyer?.communicationDetails?.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
</ng-container>
|
||||
|
||||
<div class="flex flex-row items-center bg-[#F5F7FA] min-h-[3.3125rem]">
|
||||
<div class="flex flex-row items-center justify-center px-5 py-[0.875rem]">
|
||||
<shared-icon
|
||||
*ngIf="(displayOrder?.items)[0]?.features?.orderType !== 'Dummy'"
|
||||
class="mr-2"
|
||||
[size]="(displayOrder?.items)[0]?.features?.orderType === 'B2B-Versand' ? 36 : 24"
|
||||
[icon]="(displayOrder?.items)[0]?.features?.orderType"
|
||||
></shared-icon>
|
||||
<p class="text-p1 font-bold mr-3">{{ (displayOrder?.items)[0]?.features?.orderType }}</p>
|
||||
<div
|
||||
*ngIf="
|
||||
(displayOrder?.items)[0]?.features?.orderType === 'Abholung' || (displayOrder?.items)[0]?.features?.orderType === 'Rücklage';
|
||||
else shippingAddress
|
||||
"
|
||||
>
|
||||
{{ displayOrder.targetBranch?.name }}, {{ displayOrder.targetBranch | branchAddress }}
|
||||
</div>
|
||||
<ng-template #shippingAddress>
|
||||
{{ displayOrder.shippingAddress | branchAddress }}
|
||||
</ng-template>
|
||||
<div *ngIf="(displayOrder?.items)[0]?.features?.orderType === 'Download'">
|
||||
| {{ displayOrder.buyer?.communicationDetails?.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="flex flex-col px-5 pt-7 pb-[1.875rem] bg-white">
|
||||
<div class="flex flex-row justify-between items-center mb-[0.375rem]">
|
||||
<div class="flex flex-row">
|
||||
<span class="w-32">Vorgangs-ID</span>
|
||||
<ng-container *ngIf="customer$ | async; let customer">
|
||||
<a
|
||||
*ngIf="customer$ | async; let customer"
|
||||
class="font-bold text-[#0556B4] no-underline"
|
||||
[routerLink]="['/kunde', processId, 'customer', 'search', customer?.id, 'orders', displayOrder.id]"
|
||||
[queryParams]="{ main_qs: customer?.customerNumber, filter_customertype: '' }"
|
||||
>{{ displayOrder.orderNumber }}</a
|
||||
>
|
||||
</ng-container>
|
||||
<ui-spinner class="text-[#0556B4] h-4 w-4" [show]="!(customer$ | async)"> </ui-spinner>
|
||||
</div>
|
||||
<button
|
||||
*ifRole="'Store'"
|
||||
type="button"
|
||||
class="cta-print bg-transparent text-brand font-bold text-p1 outline-none border-none pr-0"
|
||||
(click)="openPrintModal(displayOrder.id)"
|
||||
>
|
||||
Drucken
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="flex flex-row">
|
||||
<span class="w-32">Bestelldatum</span>
|
||||
<strong>{{ displayOrder.orderDate | date }}</strong>
|
||||
<div class="flex flex-col px-5 py-4 bg-white" [attr.data-order-type]="(displayOrder?.items)[0]?.features?.orderType">
|
||||
<div class="flex flex-row justify-between items-center mb-[0.375rem]">
|
||||
<div class="flex flex-row">
|
||||
<span class="w-32">Vorgangs-ID</span>
|
||||
<ng-container *ngIf="customer$ | async; let customer">
|
||||
<a
|
||||
*ngIf="customer$ | async; let customer"
|
||||
class="font-bold text-[#0556B4] no-underline"
|
||||
[routerLink]="['/kunde', processId, 'customer', 'search', customer?.id, 'orders', displayOrder.id]"
|
||||
[queryParams]="{ main_qs: customer?.customerNumber, filter_customertype: '' }"
|
||||
>{{ displayOrder.orderNumber }}</a
|
||||
>
|
||||
</ng-container>
|
||||
<ui-spinner class="text-[#0556B4] h-4 w-4" [show]="!(customer$ | async)"> </ui-spinner>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="flex flex-row">
|
||||
<span class="w-32">Bestelldatum</span>
|
||||
<strong>{{ displayOrder.orderDate | date }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mr-4">
|
||||
<button
|
||||
(click)="expanded[i] = !expanded[i]"
|
||||
type="button"
|
||||
class="text-[#0556B4] font-bold flex flex-row items-center justify-center"
|
||||
[class.flex-row-reverse]="!expanded[i]"
|
||||
>
|
||||
<shared-icon
|
||||
class="mr-1"
|
||||
icon="arrow-back"
|
||||
[size]="20"
|
||||
[class.ml-1]="!expanded[i]"
|
||||
[class.rotate-180]="!expanded[i]"
|
||||
></shared-icon>
|
||||
{{ expanded[i] ? 'Weniger' : 'Mehr' }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
(click)="expanded[i] = !expanded[i]"
|
||||
type="button"
|
||||
class="text-[#0556B4] font-bold flex flex-row items-center justify-center"
|
||||
[class.flex-row-reverse]="!expanded[i]"
|
||||
>
|
||||
<shared-icon
|
||||
class="mr-1"
|
||||
icon="arrow-back"
|
||||
[size]="20"
|
||||
[class.ml-1]="!expanded[i]"
|
||||
[class.rotate-180]="!expanded[i]"
|
||||
></shared-icon>
|
||||
{{ expanded[i] ? 'Weniger' : 'Mehr' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngFor="let order of displayOrder.items; let last = last">
|
||||
<ng-container *ngIf="expanded[i]">
|
||||
<div
|
||||
class="page-checkout-summary__items-tablet px-5 pb-[1.875rem] bg-white"
|
||||
[class.page-checkout-summary__items]="isDesktop$ | async"
|
||||
[class.last]="last"
|
||||
>
|
||||
<div class="page-checkout-summary__items-thumbnail flex flex-row">
|
||||
<a [routerLink]="getProductSearchDetailsPath(order?.product?.ean)" [queryParams]="getProductSearchDetailsQueryParams(order)">
|
||||
<img class="w-[3.125rem] h-20 mr-2" [src]="order.product?.ean | productImage: 195:315:true" />
|
||||
</a>
|
||||
</div>
|
||||
<ng-container *ngFor="let order of displayOrder.items; let last = last">
|
||||
<ng-container *ngIf="expanded[i]">
|
||||
<div
|
||||
class="page-checkout-summary__items-tablet px-5 pb-[1.875rem] bg-white"
|
||||
[class.page-checkout-summary__items]="isDesktop$ | async"
|
||||
[class.last]="last"
|
||||
>
|
||||
<div class="page-checkout-summary__items-thumbnail flex flex-row">
|
||||
<a [routerLink]="getProductSearchDetailsPath(order?.product?.ean)" [queryParams]="getProductSearchDetailsQueryParams(order)">
|
||||
<img class="w-[3.125rem] h-20 mr-2" [src]="order.product?.ean | productImage: 195:315:true" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-title whitespace-nowrap overflow-ellipsis overflow-hidden">
|
||||
<a
|
||||
class="font-bold no-underline text-[#0556B4]"
|
||||
[routerLink]="getProductSearchDetailsPath(order?.product?.ean)"
|
||||
[queryParams]="getProductSearchDetailsQueryParams(order)"
|
||||
>{{ order?.product?.name }}</a
|
||||
>
|
||||
</div>
|
||||
<div class="page-checkout-summary__items-title whitespace-nowrap overflow-ellipsis overflow-hidden">
|
||||
<a
|
||||
class="font-bold no-underline text-[#0556B4]"
|
||||
[routerLink]="getProductSearchDetailsPath(order?.product?.ean)"
|
||||
[queryParams]="getProductSearchDetailsQueryParams(order)"
|
||||
>{{ order?.product?.name }}</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-ssc" *ngIf="(order?.subsetItems)[0]; let subsetItem">
|
||||
<span class="mr-2">{{ subsetItem.supplierName }}</span>
|
||||
<span *ngIf="subsetItem?.ssc && subsetItem?.sscText" class="font-bold border-l border-black pl-2"
|
||||
>{{ subsetItem.ssc }} - {{ subsetItem.sscText }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="page-checkout-summary__items-ssc" *ngIf="(order?.subsetItems)[0]; let subsetItem">
|
||||
<span class="mr-2">{{ subsetItem.supplierName }}</span>
|
||||
<span *ngIf="subsetItem?.ssc && subsetItem?.sscText" class="font-bold border-l border-black pl-2"
|
||||
>{{ subsetItem.ssc }} - {{ subsetItem.sscText }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-quantity font-bold justify-self-end">
|
||||
<span> {{ order.quantity }}x </span>
|
||||
</div>
|
||||
<div class="page-checkout-summary__items-quantity font-bold justify-self-end">
|
||||
<span> {{ order.quantity }}x </span>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-price font-bold justify-self-end">
|
||||
<span> {{ order.price?.value?.value | currency: ' ' }} {{ order.price?.value?.currency }} </span>
|
||||
</div>
|
||||
<div class="page-checkout-summary__items-price font-bold justify-self-end">
|
||||
<span> {{ order.price?.value?.value | currency: ' ' }} {{ order.price?.value?.currency }} </span>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-delivery product-details">
|
||||
<div class="delivery-row" [ngSwitch]="order?.features?.orderType">
|
||||
<ng-container *ngSwitchCase="'Abholung'">
|
||||
<span class="order-type"
|
||||
>Abholung ab {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}
|
||||
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"> </ng-container>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Rücklage'">
|
||||
<span class="order-type"
|
||||
>{{ order?.features?.orderType }}
|
||||
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"> </ng-container>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(order?.features?.orderType) > -1">
|
||||
<ng-container *ngIf="(order?.subsetItems)[0]?.estimatedDelivery; else estimatedShippingDate">
|
||||
<div class="page-checkout-summary__items-delivery product-details">
|
||||
<div class="delivery-row" [ngSwitch]="order?.features?.orderType">
|
||||
<ng-container *ngSwitchCase="'Abholung'">
|
||||
<span class="order-type"
|
||||
>Zustellung zwischen
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}</span
|
||||
>
|
||||
>Abholung ab {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}
|
||||
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"> </ng-container>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate>
|
||||
<span class="order-type">Versanddatum {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}</span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<span class="order-type">{{ order?.features?.orderType }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Rücklage'">
|
||||
<span class="order-type"
|
||||
>{{ order?.features?.orderType }}
|
||||
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"> </ng-container>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(order?.features?.orderType) > -1">
|
||||
<ng-container *ngIf="(order?.subsetItems)[0]?.estimatedDelivery; else estimatedShippingDate">
|
||||
<span class="order-type"
|
||||
>Zustellung zwischen
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}</span
|
||||
>
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate>
|
||||
<span class="order-type">Versanddatum {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}</span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<span class="order-type">{{ order?.features?.orderType }}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<hr *ngIf="last" />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="orderLast">
|
||||
<div class="flex flex-row justify-between items-center min-h-[3.3125rem] bg-white px-5 py-4 rounded-b">
|
||||
<span *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="text-p2 font-bold"
|
||||
>{{ totalItemCount$ | async }} Artikel | {{ totalReadingPoints }} Lesepunkte</span
|
||||
>
|
||||
|
||||
<div class="flex flex-row items-center justify-center">
|
||||
<div class="text-p1 font-bold flex flex-row items-center">
|
||||
<div class="mr-1">Gesamtsumme {{ totalPrice$ | async | currency: ' ' }} {{ totalPriceCurrency$ | async }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bg-brand text-white font-bold text-p1 outline-none border-none rounded-full px-6 py-3 ml-2"
|
||||
*ngIf="(takeNowOrders$ | async)?.length === 1 && (isB2BCustomer$ | async)"
|
||||
>
|
||||
<button class="cta-goods-out" (click)="navigateToShelfOut()">Zur Warenausgabe</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<hr *ngIf="last" />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="orderLast">
|
||||
<div class="flex flex-row justify-between items-center min-h-[3.3125rem] bg-white px-5 py-4 rounded-b">
|
||||
<span *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="text-p2 font-bold"
|
||||
>{{ totalItemCount$ | async }} Artikel | {{ totalReadingPoints }} Lesepunkte</span
|
||||
>
|
||||
|
||||
<div class="flex flex-row items-center justify-center">
|
||||
<div class="text-p1 font-bold flex flex-row items-center">
|
||||
<div class="mr-1">Gesamtsumme {{ totalPrice$ | async | currency: ' ' }} {{ totalPriceCurrency$ | async }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bg-brand text-white font-bold text-p1 outline-none border-none rounded-full px-6 py-3 ml-2"
|
||||
*ngIf="(takeNowOrders$ | async)?.length === 1 && (isB2BCustomer$ | async)"
|
||||
>
|
||||
<button class="cta-goods-out" (click)="navigateToGoodsOut()">Zur Warenausgabe</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #abholfrist let-order="order">
|
||||
<div *ngIf="!(updatingPreferredPickUpDate$ | async)[(order?.subsetItems)[0].id]" class="inline-flex">
|
||||
<button [uiOverlayTrigger]="deadlineDatepicker" #deadlineDatepickerTrigger="uiOverlayTrigger" class="flex flex-row items-center">
|
||||
<span class="mx-[0.625rem] font-normal">bis</span>
|
||||
<strong class="border-r border-[#AEB7C1] pr-4 text-[#AEB7C1]">
|
||||
{{ ((order?.subsetItems)[0]?.preferredPickUpDate | date: 'dd.MM.yy') || 'Auswählen' }}
|
||||
<strong class="border-r border-[#AEB7C1] pr-4">
|
||||
{{ ((order?.subsetItems)[0]?.preferredPickUpDate | date: 'dd.MM.yy') || 'TT.MM.JJJJ' }}
|
||||
</strong>
|
||||
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
@@ -222,3 +221,25 @@
|
||||
</div>
|
||||
<div class="fetching" *ngIf="!!(updatingPreferredPickUpDate$ | async)[(order?.subsetItems)[0].id]"></div>
|
||||
</ng-template>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute left-1/2 bottom-10 inline-grid grid-flow-col gap-4 justify-center transform -translate-x-1/2">
|
||||
<button
|
||||
*ifRole="'Store'"
|
||||
type="button"
|
||||
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14"
|
||||
(click)="printOrderConfirmation()"
|
||||
>
|
||||
Bestellbestätigung drucken
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="hasAbholung$ | async"
|
||||
type="button"
|
||||
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14"
|
||||
(click)="sendOrderConfirmation()"
|
||||
>
|
||||
Bestellbestätigung senden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
:host {
|
||||
@apply box-border bg-transparent rounded overflow-scroll h-split-screen-tablet desktop-small:h-split-screen-desktop flex flex-col justify-between;
|
||||
@apply box-border bg-transparent relative;
|
||||
}
|
||||
|
||||
.summary-wrapper {
|
||||
@apply rounded overflow-scroll h-split-screen-tablet desktop-small:h-split-screen-desktop flex flex-col justify-between;
|
||||
}
|
||||
|
||||
.fetching {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { PrintModalComponent, PrintModalData } from '@modal/printer';
|
||||
@@ -13,8 +13,11 @@ import { ApplicationService } from '@core/application';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
import { BehaviorSubject, combineLatest, NEVER, Subject } from 'rxjs';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { CheckoutNavigationService, PickUpShelfOutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { SendOrderConfirmationModalService } from '@shared/modals/send-order-confirmation-modal';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { ToasterService } from '@shared/shell';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-summary',
|
||||
@@ -23,6 +26,14 @@ import { EnvironmentService } from '@core/environment';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
private _injector = inject(Injector);
|
||||
|
||||
get sendOrderConfirmationModalService() {
|
||||
return this._injector.get(SendOrderConfirmationModalService);
|
||||
}
|
||||
|
||||
private _toaster = inject(ToasterService);
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
processId = Date.now();
|
||||
selectedDate = this.dateAdapter.today();
|
||||
@@ -54,11 +65,16 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
const itemsWithDifferentOrderFeature = orderWithMultipleFeatures.items.filter(
|
||||
(item) => item.features.orderType !== orderWithMultipleFeatures.features.orderType
|
||||
);
|
||||
filteredOrders = [
|
||||
...filteredOrders.filter((order) => order.id !== orderWithMultipleFeatures.id),
|
||||
{ ...orderWithMultipleFeatures, items: itemsWithOrderFeature },
|
||||
{ ...orderWithMultipleFeatures, items: itemsWithDifferentOrderFeature },
|
||||
];
|
||||
|
||||
filteredOrders = [...filteredOrders.filter((order) => order.id !== orderWithMultipleFeatures.id)];
|
||||
|
||||
if (itemsWithOrderFeature?.length > 0) {
|
||||
filteredOrders = [...filteredOrders, { ...orderWithMultipleFeatures, items: itemsWithOrderFeature }];
|
||||
}
|
||||
|
||||
if (itemsWithDifferentOrderFeature?.length > 0) {
|
||||
filteredOrders = [...filteredOrders, { ...orderWithMultipleFeatures, items: itemsWithDifferentOrderFeature }];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +89,10 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
hasAbholung$ = this.displayOrders$.pipe(
|
||||
map((displayOrders) => displayOrders.filter((order) => order.features?.orderType === 'Abholung')?.length > 0)
|
||||
);
|
||||
|
||||
totalItemCount$ = this.displayOrders$.pipe(
|
||||
map((displayOrders) =>
|
||||
displayOrders.reduce(
|
||||
@@ -157,6 +177,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
private dateAdapter: DateAdapter,
|
||||
private _navigation: CheckoutNavigationService,
|
||||
private _productNavigationService: ProductCatalogNavigationService,
|
||||
private _shelfOutNavigationService: PickUpShelfOutNavigationService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _cdr: ChangeDetectorRef
|
||||
) {
|
||||
@@ -216,20 +237,6 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
return { main_qs: item?.product?.ean, filter_format: item?.features?.orderType === 'Download' ? 'eb;dl' : undefined };
|
||||
}
|
||||
|
||||
openPrintModal(id: number) {
|
||||
this.uiModal.open({
|
||||
content: PrintModalComponent,
|
||||
data: {
|
||||
printerType: 'Label',
|
||||
print: (printer) => this.domainPrinterService.printOrder({ orderIds: [id], printer }).toPromise(),
|
||||
} as PrintModalData,
|
||||
config: {
|
||||
panelClass: [],
|
||||
showScrollbarY: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async updatePreferredPickUpDate(item: DisplayOrderItemDTO, date: Date) {
|
||||
const data: Record<string, string> = {};
|
||||
|
||||
@@ -279,7 +286,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
this.domainCheckoutService.updateOrderItem(item);
|
||||
}
|
||||
|
||||
async navigateToGoodsOut() {
|
||||
async navigateToShelfOut() {
|
||||
let takeNowOrders = await this.takeNowOrders$.pipe(first()).toPromise();
|
||||
if (takeNowOrders.length != 1) return;
|
||||
|
||||
@@ -294,9 +301,45 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
this.router.navigate(['/kunde', 'goods', 'out', 'details', 'order', takeNowOrders[0].orderNumber, 128]);
|
||||
await this.router.navigate(
|
||||
this._shelfOutNavigationService.detailRoute({
|
||||
processId: Date.now(),
|
||||
item: { orderId: takeNowOrders[0].id, orderNumber: takeNowOrders[0].orderNumber, processingStatus: 128 },
|
||||
}).path
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async sendOrderConfirmation() {
|
||||
const orders = await this.displayOrders$.pipe(first()).toPromise();
|
||||
|
||||
await this.sendOrderConfirmationModalService.open(orders);
|
||||
}
|
||||
|
||||
async printOrderConfirmation() {
|
||||
const orders = await this.displayOrders$.pipe(first()).toPromise();
|
||||
await this.uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
data: {
|
||||
printerType: 'Label',
|
||||
print: async (printer) => {
|
||||
try {
|
||||
const result = await this.domainPrinterService.printOrder({ orderIds: orders.map((o) => o.id), printer }).toPromise();
|
||||
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
|
||||
return result;
|
||||
} catch (error) {
|
||||
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
|
||||
}
|
||||
},
|
||||
} as PrintModalData,
|
||||
config: {
|
||||
panelClass: [],
|
||||
showScrollbarY: false,
|
||||
},
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<shared-breadcrumb class="mb-5 desktop-small:mb-9" [key]="breadcrumbKey$ | async" [tags]="['checkout']"></shared-breadcrumb>
|
||||
<shared-breadcrumb [key]="breadcrumbKey$ | async" [tags]="['checkout']"></shared-breadcrumb>
|
||||
|
||||
<shared-splitscreen></shared-splitscreen>
|
||||
|
||||
@@ -295,7 +295,7 @@
|
||||
[disabled]="(!isKulturpass && !!orderItem?.features?.paid) || (changeDateDisabled$ | async)"
|
||||
class="cta-pickup-preferred"
|
||||
>
|
||||
<strong *ngIf="preferredPickUpDate$ | async; let pickUpDate; else: selectTemplate">
|
||||
<strong class="border-r border-[#AEB7C1] pr-4" *ngIf="preferredPickUpDate$ | async; let pickUpDate; else: selectTemplate">
|
||||
{{ pickUpDate | date: 'dd.MM.yy' }}
|
||||
</strong>
|
||||
<ng-template #selectTemplate>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<div class="label">ISBN/EAN</div>
|
||||
<div class="value">{{ orderItem.product?.ean }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.price">
|
||||
<div class="detail" *ngIf="orderItem.price !== undefined">
|
||||
<div class="label">Preis</div>
|
||||
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, ContentChildren, OnDestroy, OnInit, QueryList } from '@angular/core';
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ContentChild,
|
||||
ContentChildren,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
QueryList,
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
@@ -13,6 +22,7 @@ import { CustomerOrderDetailsStore } from './customer-order-details.store';
|
||||
import { CustomerOrderDetailsItemComponent } from './customer-order-details-item';
|
||||
import { unionBy } from 'lodash';
|
||||
import { CommandService } from '@core/command';
|
||||
import { CustomerOrderDetailsTagsComponent } from './customer-order-details-tags';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details',
|
||||
@@ -21,10 +31,13 @@ import { CommandService } from '@core/command';
|
||||
providers: [provideComponentStore(CustomerOrderDetailsStore)],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
|
||||
export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@ContentChildren(CustomerOrderDetailsItemComponent)
|
||||
customerOrderDetailsItemComponents: QueryList<CustomerOrderDetailsItemComponent>;
|
||||
|
||||
@ContentChild(CustomerOrderDetailsTagsComponent, { static: false })
|
||||
customerOrderDetailsTags: CustomerOrderDetailsTagsComponent;
|
||||
|
||||
buyerNumber$ = this._store.buyerNumber$;
|
||||
|
||||
compartmentCode$ = this._store.compartmentCode$;
|
||||
@@ -146,10 +159,23 @@ export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
|
||||
this.removeBreadcrumbs();
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this._registerCustomerOrderDetailsTagsComponentChanges();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
private _registerCustomerOrderDetailsTagsComponentChanges() {
|
||||
this.subscriptions.add(
|
||||
this.items$.subscribe((_) => {
|
||||
this.customerOrderDetailsTags?.writeValue(this.compartmentInfo);
|
||||
this.customerOrderDetailsTags?.registerOnChange((compartmentInfo) => (this._store.compartmentInfo = compartmentInfo));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
itemsSelectable(items: OrderItemListItemDTO[]) {
|
||||
this._store.patchState({
|
||||
itemsSelectable: items?.some((item) => !!item?.actions && item?.actions?.length > 0),
|
||||
|
||||
@@ -4,7 +4,7 @@ import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainCustomerOrderService, DomainGoodsService, DomainOmsService } from '@domain/oms';
|
||||
import { CustomerOrdersNavigationService } from '@shared/services';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { DBHOrderItemListItemDTO, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { map, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
@@ -87,10 +87,10 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
async navigateToDetailsPage({ options }: { options?: { processingStatus?: number } }) {
|
||||
async navigateToDetailsPage(changes?: Partial<DBHOrderItemListItemDTO>) {
|
||||
const orderId = this._activatedRoute.snapshot.params.orderId;
|
||||
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
|
||||
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
|
||||
const processingStatus = changes?.processingStatus ? changes.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
|
||||
|
||||
await this._navigation
|
||||
.getCustomerOrdersDetailsPath({
|
||||
|
||||
@@ -83,6 +83,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.showFilter = this.sideOutlet !== 'search';
|
||||
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((queryParams) => {
|
||||
this._customerOrdersSearchStore.setQueryParams(queryParams);
|
||||
});
|
||||
@@ -102,6 +103,33 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
this._customerOrdersSearchStore.setQueryParams(queryParams);
|
||||
});
|
||||
|
||||
this._customerOrdersSearchStore.searchResultSubject.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
|
||||
if (result.results.error) {
|
||||
} else {
|
||||
if (result.results.hits > 0) {
|
||||
const queryParams = this._customerOrdersSearchStore.filter.getQueryParams();
|
||||
if (result.results.hits === 1) {
|
||||
const orderItem = result.results.result[0];
|
||||
await this._navigationService
|
||||
.getCustomerOrdersDetailsPath({
|
||||
processId: this.processId,
|
||||
processingStatus: orderItem?.processingStatus,
|
||||
compartmentCode: orderItem?.compartmentCode ? encodeURIComponent(orderItem.compartmentCode) : undefined,
|
||||
orderId: orderItem?.orderId ? orderItem.orderId : undefined,
|
||||
extras: { queryParams },
|
||||
})
|
||||
.navigate();
|
||||
} else {
|
||||
await this._navigationService.getCustomerOrdersResultsPath(this.processId, { queryParams }).navigate();
|
||||
}
|
||||
} else {
|
||||
this._customerOrdersSearchStore.setMessage('keine Suchergebnisse');
|
||||
}
|
||||
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
this._initSettings();
|
||||
this._initLoading$();
|
||||
}
|
||||
@@ -132,34 +160,6 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
const queryParams = filter.getQueryParams();
|
||||
this._customerOrdersSearchStore.setQueryParams(queryParams);
|
||||
await this.updateQueryParams(queryParams);
|
||||
|
||||
this._customerOrdersSearchStore.searchResultSubject.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
|
||||
if (result.results.error) {
|
||||
} else {
|
||||
if (result.results.hits > 0) {
|
||||
const queryParams = this._customerOrdersSearchStore.filter.getQueryParams();
|
||||
if (result.results.hits === 1) {
|
||||
const orderItem = result.results.result[0];
|
||||
await this._navigationService
|
||||
.getCustomerOrdersDetailsPath({
|
||||
processId: this.processId,
|
||||
processingStatus: orderItem?.processingStatus,
|
||||
compartmentCode: orderItem?.compartmentCode ? encodeURIComponent(orderItem.compartmentCode) : undefined,
|
||||
orderId: orderItem?.orderId ? orderItem.orderId : undefined,
|
||||
extras: { queryParams },
|
||||
})
|
||||
.navigate();
|
||||
} else {
|
||||
await this._navigationService.getCustomerOrdersResultsPath(this.processId, { queryParams }).navigate();
|
||||
}
|
||||
} else {
|
||||
this._customerOrdersSearchStore.setMessage('keine Suchergebnisse');
|
||||
}
|
||||
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
this._customerOrdersSearchStore.search({ clear: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -174,8 +174,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
this.setSearchHistory(searchHistory?.slice(0, 7));
|
||||
}
|
||||
|
||||
getCachedData() {
|
||||
const queryToken = { ...this.filter?.getQueryToken(), processId: this.processId, branchId: String(this.selectedBranch?.id) } ?? {};
|
||||
getCachedData({ queryToken }: { queryToken: Record<string, any> }) {
|
||||
return (
|
||||
this._cache.get<{
|
||||
hits: number;
|
||||
@@ -185,6 +184,14 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
);
|
||||
}
|
||||
|
||||
setCache({ queryToken, hits, results }: { queryToken: Record<string, any>; hits: number; results: OrderItemListItemDTO[] }) {
|
||||
this._cache.set(queryToken, {
|
||||
hits,
|
||||
results,
|
||||
fetching: false,
|
||||
});
|
||||
}
|
||||
|
||||
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
|
||||
options$.pipe(
|
||||
tap((_) => {
|
||||
@@ -201,7 +208,6 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
|
||||
if (options?.clear) {
|
||||
this.searchResultClearedSubject.next();
|
||||
this._cache.delete(filter?.getQueryToken());
|
||||
}
|
||||
}),
|
||||
switchMap(([options, results, filter, branch]) => {
|
||||
@@ -227,7 +233,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
queryToken.take = 50;
|
||||
}
|
||||
|
||||
if (branch?.id) {
|
||||
if (branch?.id && !!queryToken.filter) {
|
||||
queryToken.filter['branch_id'] = String(branch?.id);
|
||||
}
|
||||
|
||||
@@ -250,18 +256,10 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
silentFetching: false,
|
||||
});
|
||||
|
||||
const queryToken = { ...filter?.getQueryToken(), processId: this.processId, branchId: String(branch?.id) };
|
||||
|
||||
if (res?.hits > 0 && options?.clear) {
|
||||
this.saveSearchHistoryToSessionStorage();
|
||||
}
|
||||
|
||||
this._cache.set(queryToken, {
|
||||
hits: res.hits,
|
||||
results: _results,
|
||||
fetching: false,
|
||||
});
|
||||
|
||||
this.searchResultSubject.next({ results: res, cached, clear: options?.clear });
|
||||
|
||||
if (res?.hits === 0) {
|
||||
|
||||
@@ -62,15 +62,12 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Clear scroll position
|
||||
localStorage.removeItem(`SCROLL_POSITION_${this.processId}`);
|
||||
|
||||
this._subscriptions.add(
|
||||
combineLatest([this.processId$, this._activatedRoute.queryParams])
|
||||
.pipe(debounceTime(50))
|
||||
.subscribe(([processId, queryParams]) => {
|
||||
this.removeBreadcrumbs(processId);
|
||||
this.updateBreadcrumb(processId, queryParams);
|
||||
this.updateBreadcrumb(queryParams);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -88,17 +85,19 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this._subscriptions.add(
|
||||
this._customerOrderSearchStore.searchStarted.subscribe(async (_) => {
|
||||
const queryParams = {
|
||||
...this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()),
|
||||
main_qs: this.filterInputGroup?.uiInput?.value,
|
||||
};
|
||||
// Im Zuge des Tickets #4256 auskommentiert, da es zu Problemen geführt hat
|
||||
// In der Filter Komponente wird dies schon gemacht
|
||||
|
||||
this._customerOrderSearchStore.setQueryParams(queryParams);
|
||||
})
|
||||
);
|
||||
// // #4143 To make Splitscreen Search and Filter work combined
|
||||
// this._subscriptions.add(
|
||||
// this._customerOrderSearchStore.searchStarted.subscribe(async (_) => {
|
||||
// const queryParams = {
|
||||
// ...this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()),
|
||||
// main_qs: this.filterInputGroup?.uiInput?.value,
|
||||
// };
|
||||
// this._customerOrderSearchStore.setQueryParams(queryParams);
|
||||
// })
|
||||
// );
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -147,15 +146,19 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
async search(filter: Filter) {
|
||||
this._customerOrderSearchStore.setMessage('');
|
||||
this.filterInputGroup?.cancelAutocomplete();
|
||||
|
||||
const queryParams = filter.getQueryParams();
|
||||
this._customerOrderSearchStore.setQueryParams(queryParams);
|
||||
await this.updateQueryParams(queryParams);
|
||||
|
||||
this._customerOrderSearchStore.search({ clear: true });
|
||||
await this.updateQueryParams(this.processId);
|
||||
}
|
||||
|
||||
async updateBreadcrumb(processId: number, params: Record<string, string>) {
|
||||
async updateBreadcrumb(params: Record<string, string>) {
|
||||
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
key: this.processId,
|
||||
name: 'Kundenbestellung',
|
||||
path: this._navigationService.getCustomerOrdersBasePath(processId).path,
|
||||
path: this._navigationService.getCustomerOrdersBasePath(this.processId).path,
|
||||
tags: ['customer-order', 'main', 'filter'],
|
||||
section: 'customer',
|
||||
params,
|
||||
@@ -164,12 +167,12 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
|
||||
setQueryHistory(filter: Filter, query: string) {
|
||||
filter.fromQueryParams({ main_qs: query });
|
||||
const queryParams = filter.getQueryParams();
|
||||
this._customerOrderSearchStore.setQueryParams(queryParams);
|
||||
}
|
||||
|
||||
async updateQueryParams(processId: number) {
|
||||
const queryParams = { ...this._customerOrderSearchStore.filter?.getQueryParams() };
|
||||
queryParams.main_qs = queryParams.main_qs ?? '';
|
||||
async updateQueryParams(queryParams: Record<string, string>) {
|
||||
await this._router.navigate([], { queryParams });
|
||||
this.updateBreadcrumb(processId, queryParams);
|
||||
await this.updateBreadcrumb(queryParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[routerLink]="detailsLink"
|
||||
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
|
||||
[queryParams]="queryParams"
|
||||
(click)="isDesktop ? scrollIntoView() : ''"
|
||||
(click)="isDesktopLarge ? scrollIntoView() : ''"
|
||||
>
|
||||
<div
|
||||
class="page-customer-order-item__item-grid-container"
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
grid-area: ordernumber;
|
||||
}
|
||||
|
||||
.page-customer-order-item__item-special-comment {
|
||||
grid-area: comment;
|
||||
}
|
||||
|
||||
.page-customer-order-item__item-order-number-main {
|
||||
@apply justify-self-start self-center;
|
||||
}
|
||||
@@ -82,25 +86,7 @@
|
||||
grid-area: status;
|
||||
}
|
||||
|
||||
.page-customer-order-item__item-select-bullet {
|
||||
grid-area: select;
|
||||
}
|
||||
|
||||
.active,
|
||||
.hover:hover {
|
||||
@apply bg-[#D8DFE5] border border-solid border-[#0556B4];
|
||||
|
||||
.page-customer-order-item__item-select-bullet {
|
||||
.isa-select-bullet::before {
|
||||
@apply bg-[#fff];
|
||||
}
|
||||
|
||||
.isa-select-bullet:checked:before {
|
||||
@apply bg-[#596470];
|
||||
}
|
||||
|
||||
.isa-select-bullet:hover::before {
|
||||
@apply bg-[#778490];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ElementRef } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input, ElementRef } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
@@ -10,8 +10,6 @@ import { map } from 'rxjs/operators';
|
||||
|
||||
export interface CustomerOrderItemComponentState {
|
||||
item?: OrderItemListItemDTO;
|
||||
selected: boolean;
|
||||
selectable: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -33,30 +31,12 @@ export class CustomerOrderItemComponent extends ComponentStore<CustomerOrderItem
|
||||
|
||||
readonly item$ = this.select((s) => s.item);
|
||||
|
||||
@Input()
|
||||
selected: boolean;
|
||||
|
||||
selected$ = this.select((s) => s.selected);
|
||||
|
||||
@Input()
|
||||
get selectable() {
|
||||
return this.get((s) => s.selectable);
|
||||
}
|
||||
set selectable(selectable: boolean) {
|
||||
if (this.selectable !== selectable) {
|
||||
this.patchState({ selectable });
|
||||
}
|
||||
}
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<boolean>();
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
get isDesktopLarge() {
|
||||
return this._environment.matchDesktopLarge();
|
||||
}
|
||||
|
||||
get queryParams() {
|
||||
@@ -98,7 +78,7 @@ export class CustomerOrderItemComponent extends ComponentStore<CustomerOrderItem
|
||||
private _applicationService: ApplicationService,
|
||||
private _elRef: ElementRef
|
||||
) {
|
||||
super({ selected: false, selectable: false });
|
||||
super({});
|
||||
}
|
||||
|
||||
scrollIntoView() {
|
||||
|
||||
@@ -72,10 +72,7 @@
|
||||
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
|
||||
class="page-customer-orders-results__result-item mb-[0.625rem]"
|
||||
[class.page-customer-orders-results__result-item-main]="primaryOutletActive$ | async"
|
||||
[selectable]="item | goodsOutItemSelectable: selectionRules:selectedItems"
|
||||
[selected]="item | goodsOutItemSelected: selectedOrderItemSubsetIds"
|
||||
[item]="item"
|
||||
(selectedChange)="setSelectedItem(item, $event)"
|
||||
[primaryOutletActive]="primaryOutletActive$ | async"
|
||||
></page-customer-order-item>
|
||||
</ng-container>
|
||||
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
ViewChildren,
|
||||
QueryList,
|
||||
AfterViewInit,
|
||||
inject,
|
||||
DestroyRef,
|
||||
} from '@angular/core';
|
||||
import { debounceTime, filter, first, map, shareReplay, switchMap, take, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { debounceTime, filter, first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
|
||||
import { CustomerOrderSearchStore } from '../customer-order-search.store';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subject, Subscription } from 'rxjs';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
@@ -27,6 +29,8 @@ import { CustomerOrdersNavigationService } from '@shared/services';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { Filter, FilterInputGroupMainComponent } from '@shared/components/filter';
|
||||
import { CustomerOrderItemComponent } from './customer-order-item.component';
|
||||
import { CacheService } from '@core/cache';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
export interface CustomerOrderSearchResultsState {
|
||||
selectedOrderItemSubsetIds: number[];
|
||||
@@ -46,6 +50,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
@ViewChild(FilterInputGroupMainComponent, { static: false })
|
||||
sharedFilterInputGroupMain: FilterInputGroupMainComponent;
|
||||
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
items$: Observable<OrderItemListItemDTO[]> = this._customerOrderSearchStore.results$;
|
||||
|
||||
itemLength$ = this.items$.pipe(map((items) => items?.length));
|
||||
@@ -99,10 +105,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
|
||||
private _searchResultSubscription = new Subscription();
|
||||
|
||||
filter$ = this._customerOrderSearchStore.filter$.pipe(
|
||||
filter((f) => !!f),
|
||||
take(1)
|
||||
);
|
||||
filter$ = this._customerOrderSearchStore.filter$.pipe(filter((f) => !!f));
|
||||
|
||||
hasFilter$ = combineLatest([this.filter$, this._customerOrderSearchStore.defaultSettings$]).pipe(
|
||||
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), Filter.create(defaultFilter).getQueryParams()))
|
||||
@@ -112,18 +115,10 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
return this._environment.matchTablet$;
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get isDesktopLarge() {
|
||||
return this._environment.matchDesktopLarge();
|
||||
}
|
||||
|
||||
get rightOutletParams(): Params {
|
||||
return this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'right')?.snapshot?.params;
|
||||
}
|
||||
|
||||
get filterRoute() {
|
||||
const compartmentCode = this._activatedRoute?.snapshot?.params?.compartmentCode;
|
||||
const orderId = this._activatedRoute?.snapshot?.params?.orderId;
|
||||
@@ -141,6 +136,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
return this._environment.matchTablet$.pipe(map((matches) => !matches && this._activatedRoute.outlet === 'primary'));
|
||||
}
|
||||
|
||||
private readonly SCROLL_POSITION_TOKEN = 'CUSTOMER_ORDERS_LIST_SCROLL_POSITION';
|
||||
|
||||
constructor(
|
||||
private _customerOrderSearchStore: CustomerOrderSearchStore,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
@@ -149,7 +146,9 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
private _modal: UiModalService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: CustomerOrdersNavigationService,
|
||||
private _application: ApplicationService
|
||||
private _application: ApplicationService,
|
||||
private _cache: CacheService,
|
||||
private _router: Router
|
||||
) {
|
||||
super({
|
||||
selectedOrderItemSubsetIds: [],
|
||||
@@ -173,7 +172,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
this._searchResultSubscription.add(
|
||||
this.processId$
|
||||
.pipe(
|
||||
debounceTime(10),
|
||||
debounceTime(150),
|
||||
withLatestFrom(this._activatedRoute.queryParams),
|
||||
switchMap(([processId, params]) =>
|
||||
this._application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, params, selectedBranch })))
|
||||
@@ -182,46 +181,67 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
.subscribe(async ({ processId, params, selectedBranch }) => {
|
||||
const branchChanged = selectedBranch?.id !== this._customerOrderSearchStore?.selectedBranch?.id;
|
||||
|
||||
const processChanged = processId !== this._customerOrderSearchStore.processId;
|
||||
|
||||
if (processChanged) {
|
||||
if (!!this._customerOrderSearchStore.processId && this._customerOrderSearchStore.filter instanceof Filter) {
|
||||
const queryToken = {
|
||||
...this._customerOrderSearchStore.filter?.getQueryParams(),
|
||||
processId,
|
||||
branchId: String(selectedBranch?.id),
|
||||
};
|
||||
this._customerOrderSearchStore.setCache({
|
||||
queryToken,
|
||||
hits: this._customerOrderSearchStore.hits,
|
||||
results: this._customerOrderSearchStore.results,
|
||||
});
|
||||
await this.updateBreadcrumb(processId, this._customerOrderSearchStore.filter?.getQueryParams());
|
||||
}
|
||||
this._customerOrderSearchStore.patchState({ processId });
|
||||
}
|
||||
|
||||
if (branchChanged) {
|
||||
this._customerOrderSearchStore.setBranch(selectedBranch);
|
||||
}
|
||||
|
||||
this._customerOrderSearchStore.setQueryParams(params);
|
||||
|
||||
if (!(this._customerOrderSearchStore.filter instanceof UiFilter)) {
|
||||
await this._customerOrderSearchStore.loadDefaultSettings();
|
||||
}
|
||||
|
||||
if (this._customerOrderSearchStore.processId !== processId) {
|
||||
this.scrollItemIntoView();
|
||||
this.removeBreadcrumbs(processId);
|
||||
const cleanQueryParams = this.cleanupQueryParams(params);
|
||||
|
||||
this._customerOrderSearchStore.patchState({ processId });
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()))) {
|
||||
this._customerOrderSearchStore.setQueryParams(params);
|
||||
const queryToken = {
|
||||
...this._customerOrderSearchStore.filter.getQueryParams(),
|
||||
processId,
|
||||
branchId: String(selectedBranch?.id),
|
||||
};
|
||||
const data = this._customerOrderSearchStore.getCachedData({ queryToken });
|
||||
|
||||
const cache = this._customerOrderSearchStore.getCachedData();
|
||||
|
||||
if (cache?.results?.length > 0) {
|
||||
if (data?.results?.length > 0) {
|
||||
this._customerOrderSearchStore.patchState({
|
||||
results: cache?.results,
|
||||
hits: cache?.hits,
|
||||
results: data?.results,
|
||||
hits: data?.hits,
|
||||
});
|
||||
await this.updateBreadcrumb(
|
||||
this._customerOrderSearchStore.processId,
|
||||
this._customerOrderSearchStore.filter?.getQueryParams()
|
||||
);
|
||||
} else if (this._customerOrderSearchStore?.results?.length === 0) {
|
||||
this._customerOrderSearchStore.search({ siletReload: true });
|
||||
}
|
||||
|
||||
if (
|
||||
data.results?.length === 0 &&
|
||||
this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'side')?.snapshot?.routeConfig?.path !==
|
||||
'filter'
|
||||
) {
|
||||
this.search({ clear: true });
|
||||
}
|
||||
} else if (branchChanged) {
|
||||
this.search({ clear: true });
|
||||
this._customerOrderSearchStore.search({ siletReload: true });
|
||||
}
|
||||
|
||||
if (this._customerOrderSearchStore.getCachedData()?.results?.length > 0 && !this.isDesktopLarge) {
|
||||
const scrollPos = params?.scroll_position;
|
||||
if (!!scrollPos) {
|
||||
const scrollPos = this._getScrollPositionFromCache();
|
||||
if (!!scrollPos && this._activatedRoute.outlet === 'primary') {
|
||||
setTimeout(() => {
|
||||
this.scrollContainer?.scrollTo(scrollPos);
|
||||
}
|
||||
this.removeScrollPosition(params);
|
||||
}, 150);
|
||||
}
|
||||
|
||||
const process = await this._application.getProcessById$(processId).pipe(first()).toPromise();
|
||||
@@ -230,7 +250,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
await this.createBreadcrumb(processId, params);
|
||||
}
|
||||
|
||||
if (!this.isDesktopLarge || this._activatedRoute?.outlet === 'main') {
|
||||
if (this._activatedRoute?.outlet === 'primary') {
|
||||
await this.removeDetailsBreadcrumb(processId);
|
||||
}
|
||||
})
|
||||
@@ -239,7 +259,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
this._searchResultSubscription.add(
|
||||
this._customerOrderSearchStore.searchResultSubject.pipe(withLatestFrom(this.processId$)).subscribe(async ([result, processId]) => {
|
||||
const queryParams = this._customerOrderSearchStore.filter?.getQueryParams();
|
||||
await this.createBreadcrumb(processId, queryParams);
|
||||
this.createBreadcrumb(processId, queryParams);
|
||||
|
||||
if (result.results.hits === 0) {
|
||||
return;
|
||||
@@ -253,7 +273,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
result?.results?.result?.find((_) => true),
|
||||
queryParams
|
||||
);
|
||||
} else if ((!!result?.clear || this._activatedRoute.outlet === 'primary') && !this.isTablet) {
|
||||
} else if ((!!result?.clear || this._activatedRoute.outlet === 'primary') && this.isDesktopLarge) {
|
||||
await this._navigationService.getCustomerOrdersResultsPath(processId, { queryParams }).navigate();
|
||||
}
|
||||
})
|
||||
@@ -265,25 +285,68 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
this._searchResultSubscription.add(
|
||||
this._customerOrderSearchStore.searchStarted.subscribe(async (_) => {
|
||||
const queryParams = {
|
||||
...this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()),
|
||||
...this.cleanupQueryParams(this._customerOrderSearchStore?.filter?.getQueryParams()),
|
||||
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
|
||||
};
|
||||
|
||||
this._customerOrderSearchStore.setQueryParams(queryParams);
|
||||
this._customerOrderSearchStore?.setQueryParams(queryParams);
|
||||
})
|
||||
);
|
||||
|
||||
this._router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
||||
if (event instanceof NavigationStart) {
|
||||
this._addScrollPositionToCache();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.scrollItemIntoView();
|
||||
}
|
||||
|
||||
private _removeScrollPositionFromCache(): void {
|
||||
this._cache.delete({ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN });
|
||||
}
|
||||
|
||||
private _addScrollPositionToCache(): void {
|
||||
if (this._activatedRoute.outlet === 'primary') {
|
||||
this._cache.set<number>(
|
||||
{ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN },
|
||||
this.scrollContainer?.scrollPos
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _getScrollPositionFromCache(): number {
|
||||
return this._cache.get<number>({ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN });
|
||||
}
|
||||
|
||||
// After Navigating to Result Side Outlet
|
||||
scrollItemIntoView() {
|
||||
setTimeout(() => {
|
||||
const getPrimaryRouteParams = this._activatedRoute.parent?.children[0]?.snapshot?.params;
|
||||
const item = this.listItems?.find((item) => item.item.orderId === Number(getPrimaryRouteParams?.orderId));
|
||||
item?.scrollIntoView();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
async ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
|
||||
this._searchResultSubscription.unsubscribe();
|
||||
|
||||
const queryToken = {
|
||||
...this._customerOrderSearchStore.filter?.getQueryParams(),
|
||||
processId: this._customerOrderSearchStore.processId,
|
||||
branchId: String(this._customerOrderSearchStore.selectedBranch?.id),
|
||||
};
|
||||
this._customerOrderSearchStore.setCache({
|
||||
queryToken,
|
||||
hits: this._customerOrderSearchStore.hits,
|
||||
results: this._customerOrderSearchStore.results,
|
||||
});
|
||||
|
||||
await this.updateBreadcrumb(this._customerOrderSearchStore.processId, this._customerOrderSearchStore.filter?.getQueryParams());
|
||||
}
|
||||
|
||||
@@ -301,14 +364,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
return clean;
|
||||
}
|
||||
|
||||
removeScrollPosition(queryParams: Params) {
|
||||
const scroll_pos = queryParams?.scroll_position;
|
||||
if (!!scroll_pos) {
|
||||
const clean = { ...queryParams };
|
||||
delete clean['scroll_position'];
|
||||
}
|
||||
}
|
||||
|
||||
async removeBreadcrumbs(processId: number) {
|
||||
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'edit']).pipe(first()).toPromise();
|
||||
|
||||
@@ -357,15 +412,12 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
const scroll_position = this.scrollContainer?.scrollPos;
|
||||
|
||||
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
|
||||
const params = { ...queryParams, scroll_position };
|
||||
|
||||
for (const crumb of crumbs) {
|
||||
this._breadcrumb.patchBreadcrumb(crumb.id, {
|
||||
name,
|
||||
params,
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -377,13 +429,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
return input?.replace('ORD:', '') ?? 'Alle';
|
||||
}
|
||||
|
||||
scrollItemIntoView() {
|
||||
setTimeout(() => {
|
||||
const item = this.listItems?.find((item) => item.item.orderId === Number(this._activatedRoute?.snapshot?.params?.orderId));
|
||||
item?.scrollIntoView();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
async loadMore() {
|
||||
if (
|
||||
this._customerOrderSearchStore.hits > this._customerOrderSearchStore.results.length &&
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<shared-breadcrumb class="mb-5 desktop-small:mb-9" [key]="processId$ | async" [tags]="['customer-order']">
|
||||
<shared-breadcrumb [key]="processId$ | async" [tags]="['customer-order']">
|
||||
<shared-branch-selector [branchType]="1" [value]="selectedBranch$ | async" (valueChange)="patchProcessData($event)">
|
||||
</shared-branch-selector>
|
||||
</shared-breadcrumb>
|
||||
|
||||
@@ -31,7 +31,7 @@ shared-branch-selector.shared-branch-selector-opend {
|
||||
shared-branch-selector.shared-branch-selector-opend
|
||||
.shared-branch-selector-input-container
|
||||
.shared-branch-selector-input-icon {
|
||||
@apply pl-0 border-l-0;
|
||||
@apply border-l-0;
|
||||
}
|
||||
|
||||
::ng-deep .tablet page-customer-order shared-breadcrumb:focus-within .shared-breadcrumb__suffix {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export const CustomerLabelColor = {
|
||||
Abholfachbestellung: '#EDEFF0',
|
||||
'Versandbestellung (oder gemischt)': '#EDEFF0',
|
||||
'Bestellung ohne Konto': '#EDEFF0',
|
||||
Onlinekonto: '#804279',
|
||||
'Onlinekonto mit Kundenkarte': '#804279',
|
||||
'Business Konto (auf Rechnung)': '#804279',
|
||||
@@ -10,6 +11,7 @@ export const CustomerLabelColor = {
|
||||
|
||||
export const CustomerLabelTextColor = {
|
||||
Abholfachbestellung: '#000000',
|
||||
'Bestellung ohne Konto': '#000000',
|
||||
'Versandbestellung (oder gemischt)': '#000000',
|
||||
Onlinekonto: '#FFFFFF',
|
||||
'Onlinekonto mit Kundenkarte': '#FFFFFF',
|
||||
|
||||
@@ -4,10 +4,10 @@ import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AddressDTO, CustomerDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
|
||||
import { AddressDTO, CustomerDTO, CustomerInfoDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { UiValidators } from '@ui/validators';
|
||||
import { isNull } from 'lodash';
|
||||
import { isNull, merge } from 'lodash';
|
||||
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
|
||||
import {
|
||||
first,
|
||||
@@ -24,7 +24,12 @@ import {
|
||||
} from 'rxjs/operators';
|
||||
import { AddressFormBlockComponent, DeviatingAddressFormBlockComponent, DeviatingAddressFormBlockData } from '../components/form-blocks';
|
||||
import { FormBlock } from '../components/form-blocks/form-block';
|
||||
import { CustomerCreateFormData, decodeFormData, encodeFormData } from './customer-create-form-data';
|
||||
import {
|
||||
CustomerCreateFormData,
|
||||
decodeFormData,
|
||||
encodeFormData,
|
||||
mapCustomerInfoDtoToCustomerCreateFormData,
|
||||
} from './customer-create-form-data';
|
||||
import { AddressSelectionModalService } from '../modals';
|
||||
import { CustomerCreateNavigation, CustomerSearchNavigation } from '@shared/services';
|
||||
|
||||
@@ -224,7 +229,33 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
|
||||
const customerId = this.formData?._meta?.customerDto?.id ?? this.formData?._meta?.customerInfoDto?.id;
|
||||
return this.customerService.checkLoyaltyCard({ loyaltyCardNumber: value, customerId }).pipe(
|
||||
map((response) => {
|
||||
return !response?.error && (response as any)?.result === 1 ? null : { invalid: 'Kundenkartencode ist ungültig' };
|
||||
if (response.error) {
|
||||
throw response.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* #4485 Kubi // Verhalten mit angelegte aber nicht verknüpfte Kundenkartencode in Kundensuche und Kundendaten erfassen ist nicht gleich
|
||||
* Fall1: Kundenkarte hat Daten in point4more:
|
||||
* Sobald Kundenkartencode in Feld "Kundenkartencode" reingegeben wird- werden die Daten von point4more in Formular "Kundendaten Erfassen" eingefügt und ersetzen (im Ganzen, nicht inkremental) die Daten in Felder, falls welche schon reingetippt werden.
|
||||
* Fall2: Kundenkarte hat keine Daten in point4more:
|
||||
* Sobald Kundenkartencode in Feld "Kundenkartencode" reingegeben wird- bleiben die Daten in Formular "Kundendaten Erfassen" in Felder, falls welche schon reingetippt werden.
|
||||
*/
|
||||
if (response.result && response.result.customer) {
|
||||
const customer = response.result.customer;
|
||||
const data = mapCustomerInfoDtoToCustomerCreateFormData(customer);
|
||||
|
||||
if (data.name.firstName && data.name.lastName) {
|
||||
// Fall1
|
||||
this._formData.next(data);
|
||||
} else {
|
||||
// Fall2 Hier müssen die Metadaten gesetzt werden um eine verknüfung zur kundenkarte zu ermöglichen.
|
||||
const current = this.formData;
|
||||
current._meta = data._meta;
|
||||
current.p4m = data.p4m;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
catchError((error) => {
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
|
||||
@@ -85,7 +85,9 @@
|
||||
</app-deviating-address-form-block>
|
||||
|
||||
<div class="spacer"></div>
|
||||
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -19,7 +19,7 @@ form {
|
||||
}
|
||||
|
||||
button.cta-submit {
|
||||
@apply sticky left-1/2 bottom-8 text-center bg-brand text-lg text-white font-bold px-7 py-3 rounded-full transform -translate-x-1/2 transition-all duration-200 ease-in-out;
|
||||
@apply fixed bottom-16 desktop-small:bottom-8 text-center bg-brand text-lg text-white font-bold px-7 py-3 rounded-full transform transition-all duration-200 ease-in-out;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-active-branch cursor-not-allowed;
|
||||
|
||||
@@ -98,7 +98,9 @@
|
||||
</app-deviating-address-form-block>
|
||||
|
||||
<div class="spacer"></div>
|
||||
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -143,7 +143,9 @@
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -84,9 +84,12 @@
|
||||
</app-deviating-address-form-block>
|
||||
|
||||
<div class="spacer"></div>
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">
|
||||
Speichern
|
||||
</ui-spinner>
|
||||
</button>
|
||||
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">
|
||||
Speichern
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -98,7 +98,9 @@
|
||||
</app-deviating-address-form-block>
|
||||
|
||||
<div class="spacer"></div>
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -56,7 +56,7 @@ export class CreateWebshopCustomerComponent extends AbstractCreateCustomer {
|
||||
async saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
|
||||
const { customerDto, customerInfoDto } = this.formData?._meta ?? {};
|
||||
|
||||
const isUpgrade = !!(customerDto || customerInfoDto);
|
||||
const isUpgrade = !!(customerDto || customerInfoDto)?.id;
|
||||
|
||||
if (isUpgrade) {
|
||||
if (customerDto) {
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<a
|
||||
*ngIf="customerSearchNavigation$ | async; let customerSearchNavigation"
|
||||
[routerLink]="customerSearchNavigation.path"
|
||||
[queryParams]="customerSearchNavigation.queryParams"
|
||||
class="text-[1.375rem] font-bold text-[#596470] text-center py-4"
|
||||
>Kundensuche</a
|
||||
>
|
||||
<div class="text-center pt-10 px-8 rounded-card side-view-shadow grow">
|
||||
<h1 class="text-[1.625rem] font-bold">Kundendaten erfassen</h1>
|
||||
<p class="text-lg mt-2">
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-create-side-view',
|
||||
@@ -14,9 +11,5 @@ import { map } from 'rxjs/operators';
|
||||
imports: [CommonModule, RouterModule],
|
||||
})
|
||||
export class CustomerCreateSideViewComponent {
|
||||
customerSearchNavigation$ = this._applicationService.activatedProcessId$.pipe(
|
||||
map((processId) => this.searchNavigation.defaultRoute({ processId }))
|
||||
);
|
||||
|
||||
constructor(public readonly searchNavigation: CustomerSearchNavigation, private _applicationService: ApplicationService) {}
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,9 @@
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<shared-breadcrumb [key]="processId$ | async" [tags]="['customer']" class="mb-5 desktop-small:mb-9"></shared-breadcrumb>
|
||||
<shared-breadcrumb [key]="processId$ | async" [tags]="['customer']"></shared-breadcrumb>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</shared-form-control>
|
||||
|
||||
<div class="text-center col-span-2">
|
||||
<shared-checkbox>Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
<shared-checkbox formControlName="isDefault">Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
</div>
|
||||
<div class="mt-6 text-center col-span-2">
|
||||
<button
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
</shared-form-control>
|
||||
|
||||
<div class="text-center col-span-2">
|
||||
<shared-checkbox>Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
<shared-checkbox formControlName="isDefault">Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
</div>
|
||||
<div class="mt-6 text-center col-span-2">
|
||||
<button
|
||||
|
||||
@@ -30,7 +30,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
breadcrumb = this.snapshot.data?.breadcrumb;
|
||||
|
||||
if (!breadcrumb) {
|
||||
breadcrumb = this.firstChildSnapshot.data?.breadcrumb;
|
||||
breadcrumb = this.firstChildSnapshot?.data?.breadcrumb;
|
||||
}
|
||||
|
||||
return breadcrumb;
|
||||
@@ -145,7 +145,6 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
let processId: number;
|
||||
|
||||
processId = this.snapshot.data?.processId;
|
||||
|
||||
if (!processId) {
|
||||
processId = this.parentSnapshot?.data?.processId;
|
||||
}
|
||||
@@ -168,7 +167,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
|
||||
side = this.snapshot.data?.side;
|
||||
|
||||
if (!side) {
|
||||
side = this.firstChildSnapshot.data?.side;
|
||||
side = this.firstChildSnapshot?.data?.side;
|
||||
}
|
||||
|
||||
if (side !== this.side) {
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
[checked]="!(selectedPayer$ | async)"
|
||||
(change)="selectCustomerAddress()"
|
||||
/>
|
||||
<div class="ml-2 flex flex-row justify-between items-center grow">
|
||||
<span class="truncate">
|
||||
<div class="ml-2 flex flex-row justify-between items-start grow">
|
||||
<span class="mr-4">
|
||||
{{ customer$ | async | address }}
|
||||
</span>
|
||||
<a
|
||||
@@ -51,8 +51,8 @@
|
||||
[checked]="(selectedPayer$ | async)?.payer.id === assignedPayer.payer.id"
|
||||
(change)="selectPayer(assignedPayer)"
|
||||
/>
|
||||
<div class="flex flex-row justify-between items-center grow">
|
||||
<span class="ml-2">
|
||||
<div class="ml-2 flex flex-row justify-between items-start grow">
|
||||
<span class="mr-4">
|
||||
{{ assignedPayer.payer.data | address }}
|
||||
</span>
|
||||
<ng-container *ngIf="canEditAddress$ | async">
|
||||
|
||||
@@ -157,16 +157,19 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
|
||||
);
|
||||
|
||||
handleLoadAssignedPayersResponse = (response: ListResponseArgsOfAssignedPayerDTO) => {
|
||||
const selectedPayer = response.result.reduce<AssignedPayerDTO>((selected, payer) => {
|
||||
if (!selected) {
|
||||
return payer;
|
||||
const selectedPayer = response.result.reduce<AssignedPayerDTO>((prev, curr) => {
|
||||
if (!prev) {
|
||||
return curr;
|
||||
}
|
||||
|
||||
if (new Date(selected.isDefault).getTime() < new Date(payer.isDefault).getTime()) {
|
||||
return payer;
|
||||
const prevDate = new Date(prev?.isDefault ?? 0);
|
||||
const currDate = new Date(curr?.isDefault ?? 0);
|
||||
|
||||
if (prevDate > currDate) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return selected;
|
||||
return curr;
|
||||
}, undefined);
|
||||
|
||||
this.patchState({
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
*ngIf="showCustomerAddress$ | async"
|
||||
>
|
||||
<input name="shipping-address" type="radio" [checked]="!(selectedShippingAddress$ | async)" (change)="selectCustomerAddress()" />
|
||||
<div class="ml-2 flex flex-row justify-between items-center grow">
|
||||
<span class="truncate">
|
||||
<div class="ml-2 flex flex-row justify-between items-start grow">
|
||||
<span class="mr-4">
|
||||
{{ customer$ | async | address }}
|
||||
</span>
|
||||
<a
|
||||
@@ -45,11 +45,18 @@
|
||||
[checked]="(selectedShippingAddress$ | async)?.id === shippingAddress.id"
|
||||
(change)="selectShippingAddress(shippingAddress)"
|
||||
/>
|
||||
<div class="ml-2 flex flex-row justify-between items-center grow">
|
||||
<span class="ml-2">
|
||||
<div class="ml-2 flex flex-row justify-between items-start grow">
|
||||
<span class="mr-4">
|
||||
{{ shippingAddress | address }}
|
||||
</span>
|
||||
<a *ngIf="canEditAddress$ | async" class="text-brand font-bold" type="button">
|
||||
<a
|
||||
*ngIf="editShippingAddressRoute$(shippingAddress.id) | async; let route"
|
||||
class="text-brand font-bold"
|
||||
type="button"
|
||||
[routerLink]="route?.path"
|
||||
[queryParams]="route?.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Bearbeiten
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -71,6 +71,16 @@ export class DetailsMainViewDeliveryAddressesComponent extends ComponentStore<De
|
||||
)
|
||||
);
|
||||
|
||||
editShippingAddressRoute$ = (shippingAddressId: number) =>
|
||||
combineLatest([this.canEditAddress$, this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([canEditAddress, processId, customerId]) => {
|
||||
if (canEditAddress) {
|
||||
return this._navigation.editShippingAddressRoute({ processId, customerId, shippingAddressId });
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
|
||||
canEditAddress$ = combineLatest([this._store.isKundenkarte$, this._store.isBusinessKonto$, this._store.isMitarbeiter$]).pipe(
|
||||
map(([isKundenkarte, isBusinessKonto, isMitarbeiter]) => isKundenkarte || isBusinessKonto || isMitarbeiter)
|
||||
);
|
||||
@@ -150,16 +160,19 @@ export class DetailsMainViewDeliveryAddressesComponent extends ComponentStore<De
|
||||
);
|
||||
|
||||
handleLoadShippingAddressesResponse = (response: ListResponseArgsOfAssignedPayerDTO) => {
|
||||
const selectedShippingAddress = response.result.reduce<ShippingAddressDTO>((selected, shipping) => {
|
||||
if (!this.showCustomerAddress && !selected) {
|
||||
return shipping;
|
||||
const selectedShippingAddress = response.result.reduce<ShippingAddressDTO>((prev, curr) => {
|
||||
if (!this.showCustomerAddress && !prev) {
|
||||
return curr;
|
||||
}
|
||||
|
||||
if (selected && shipping && new Date(selected.isDefault).getTime() < new Date(shipping.isDefault).getTime()) {
|
||||
return shipping;
|
||||
const prevDate = new Date(prev?.isDefault ?? 0);
|
||||
const currDate = new Date(curr?.isDefault ?? 0);
|
||||
|
||||
if (prevDate > currDate) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return selected;
|
||||
return curr;
|
||||
}, undefined);
|
||||
|
||||
this.patchState({
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3">
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Erstellungsdatum</div>
|
||||
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'hh:mm' }} Uhr</div>
|
||||
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
@@ -12,6 +12,8 @@ import { ApplicationService } from '@core/application';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Router } from '@angular/router';
|
||||
import { log, logAsync } from '@utils/common';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { MessageModalComponent, MessageModalData } from '@shared/modals/message-modal';
|
||||
|
||||
const GENDER_MAP = {
|
||||
2: 'Herr',
|
||||
@@ -34,6 +36,8 @@ export interface CustomerDetailsViewMainState {
|
||||
export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDetailsViewMainState> implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
customerService = inject(CrmCustomerService);
|
||||
|
||||
fetching$ = this._store.fetchingCustomer$;
|
||||
|
||||
isBusy$ = this.select((s) => s.isBusy);
|
||||
@@ -230,15 +234,21 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
if (this.isBusy) return;
|
||||
this.setIsBusy(true);
|
||||
|
||||
if (this.shoppingCartHasItems && !(await this._canAddCustomerAsync())) {
|
||||
const canAddCustomer = await this._canAddCustomerAsync();
|
||||
if (this.shoppingCartHasItems && !canAddCustomer) {
|
||||
this.setIsBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shippingAddress && !this._canAddShippingAddressAsync()) {
|
||||
const canAddShippingAddress = await this._canAddShippingAddressAsync();
|
||||
if (this.shippingAddress && !canAddShippingAddress) {
|
||||
this.setIsBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this._updateDestinationAsync())) {
|
||||
const destinationUpdated = await this._updateDestinationAsync();
|
||||
if (!destinationUpdated) {
|
||||
this.setIsBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,13 +310,15 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
return true;
|
||||
}
|
||||
|
||||
const required = await this.customerService.canUpgrade(this.customer.id).toPromise();
|
||||
|
||||
const upgradeableTo = res.create;
|
||||
const data: CantAddCustomerToCartData = {
|
||||
message: res.message,
|
||||
upgradeableTo,
|
||||
customer: this.customer,
|
||||
attributes: this.customer.attributes.map((a) => a.data),
|
||||
required: res.create,
|
||||
required,
|
||||
};
|
||||
|
||||
await this._modalService
|
||||
@@ -321,21 +333,44 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
|
||||
@logAsync
|
||||
async _canAddShippingAddressAsync() {
|
||||
const res = await this._checkoutService
|
||||
.canAddDestination({
|
||||
processId: this.processId,
|
||||
destinationDTO: {
|
||||
target: 2,
|
||||
shippingAddress: this.shippingAddress,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
try {
|
||||
const res = await this._checkoutService
|
||||
.canAddDestination({
|
||||
processId: this.processId,
|
||||
destinationDTO: {
|
||||
target: 2,
|
||||
shippingAddress: this.shippingAddress,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
if (typeof res === 'string') {
|
||||
throw new Error(res);
|
||||
if (typeof res === 'string') {
|
||||
throw new Error(res);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this._modalService.open({
|
||||
content: MessageModalComponent,
|
||||
title: 'Warenkorb kann dem Kunden nicht zugewiesen werden',
|
||||
data: {
|
||||
message: 'Dieser Versand ist nur innerhalb Deutschlands möglich.',
|
||||
actions: [
|
||||
{ label: 'OK' },
|
||||
{
|
||||
label: 'Lieferadresse hinzufügen',
|
||||
primary: true,
|
||||
action: () => {
|
||||
const nav = this._navigation.addShippingAddressRoute({ processId: this.processId, customerId: this.customer.id });
|
||||
this._router.navigate(nav.path, { queryParams: nav.queryParams, queryParamsHandling: 'merge' });
|
||||
},
|
||||
},
|
||||
],
|
||||
} as MessageModalData,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@logAsync
|
||||
@@ -376,7 +411,9 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
|
||||
_patchProcessName() {
|
||||
let name = `${this.customer.firstName} ${this.customer.lastName}`;
|
||||
|
||||
if (this._store.isBusinessKonto) {
|
||||
// Ticket #4458 Es kann vorkommen, dass B2B Konten keinen Firmennamen hinterlegt haben
|
||||
// zusätzlich kanne es bei Mitarbeiter Konten vorkommen, dass die Namen in der Organisation statt im Kundennamen hinterlegt sind
|
||||
if ((this._store.isBusinessKonto && this.customer.organisation?.name) || (!this.customer.firstName && !this.customer.lastName)) {
|
||||
name = `${this.customer.organisation?.name}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,13 +70,13 @@
|
||||
</shared-form-control>
|
||||
|
||||
<div class="text-center col-span-2">
|
||||
<shared-checkbox>Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
<shared-checkbox formControlName="isDefault">Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
</div>
|
||||
<div class="mt-6 text-center col-span-2">
|
||||
<button
|
||||
[disabled]="formGroup.invalid || formGroup.disabled"
|
||||
type="submit"
|
||||
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white"
|
||||
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef, inject } from '@angular/core';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SelectModule } from '@shared/components/select';
|
||||
@@ -36,6 +36,8 @@ export class EditBillingAddressMainViewComponent extends ComponentStore<EditBill
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
|
||||
private _cdr = inject(ChangeDetectorRef);
|
||||
|
||||
formGroup = new FormGroup({
|
||||
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
|
||||
title: new FormControl<string>(undefined),
|
||||
@@ -103,6 +105,10 @@ export class EditBillingAddressMainViewComponent extends ComponentStore<EditBill
|
||||
country: payer.address.country,
|
||||
info: payer.address.info,
|
||||
});
|
||||
|
||||
this.formGroup.markAllAsTouched();
|
||||
this.formGroup.updateValueAndValidity();
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
async save() {
|
||||
|
||||
@@ -86,13 +86,13 @@
|
||||
</shared-form-control>
|
||||
|
||||
<div class="text-center col-span-2">
|
||||
<shared-checkbox>Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
<shared-checkbox formControlName="isDefault">Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
|
||||
</div>
|
||||
<div class="mt-6 text-center col-span-2">
|
||||
<button
|
||||
[disabled]="formGroup.invalid || formGroup.disabled"
|
||||
type="submit"
|
||||
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white"
|
||||
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||||
tabindex="15"
|
||||
>
|
||||
Speichern
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject, ChangeDetectorRef } from '@angular/core';
|
||||
import { CheckboxComponent } from '@shared/components/checkbox';
|
||||
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SelectModule } from '@shared/components/select';
|
||||
import { FormControlComponent } from '@shared/components/form-control';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AddressDTO, Gender, ShippingAddressDTO } from '@swagger/crm';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
|
||||
import { CustomerSearchStore } from '../store';
|
||||
import { CustomerSearchNavigation } from '@shared/services';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
|
||||
export interface EditShippingAddressMainViewState {
|
||||
shippingAddress?: ShippingAddressDTO;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-edit-shipping-address-main-view',
|
||||
@@ -33,9 +38,13 @@ import { RouterLink } from '@angular/router';
|
||||
CheckboxComponent,
|
||||
],
|
||||
})
|
||||
export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
|
||||
export class EditShippingAddressMainViewComponent extends ComponentStore<EditShippingAddressMainViewState> implements OnInit, OnDestroy {
|
||||
private _activatedRoute = inject(ActivatedRoute);
|
||||
|
||||
private _onDestroy = new Subject<void>();
|
||||
|
||||
private _cdr = inject(ChangeDetectorRef);
|
||||
|
||||
detailsRoute$ = combineLatest([this._store.processId$, this._store.customerId$]).pipe(
|
||||
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
|
||||
);
|
||||
@@ -61,14 +70,30 @@ export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
|
||||
|
||||
isBusinessKonto$ = this._store.isBusinessKonto$;
|
||||
|
||||
shippingAddressId$ = this._activatedRoute.params.pipe(
|
||||
map((params) => params.shippingAddressId),
|
||||
switchMap((shippingAddressId) => this._customerService.getShippingAddress(shippingAddressId).pipe(map((res) => res.result)))
|
||||
);
|
||||
|
||||
get shippingAddressId() {
|
||||
return this.get((s) => s.shippingAddress?.id);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _customerService: CrmCustomerService,
|
||||
private _addressSelection: AddressSelectionModalService,
|
||||
private _store: CustomerSearchStore,
|
||||
private _navigation: CustomerSearchNavigation
|
||||
) {}
|
||||
) {
|
||||
super({ shippingAddress: undefined });
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.shippingAddressId$.pipe(takeUntil(this._onDestroy)).subscribe((shippingAddress) => {
|
||||
this.patchState({ shippingAddress });
|
||||
this.patchFormGroup(shippingAddress);
|
||||
});
|
||||
|
||||
this._store.customer$.pipe(takeUntil(this._onDestroy)).subscribe(() => {
|
||||
if (this._store.isBusinessKonto) {
|
||||
this.formGroup.controls.organisation.setValidators([Validators.required]);
|
||||
@@ -78,6 +103,26 @@ export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
patchFormGroup(shipping: ShippingAddressDTO) {
|
||||
this.formGroup.patchValue({
|
||||
gender: shipping.gender ?? 0,
|
||||
title: shipping.title,
|
||||
lastName: shipping.lastName,
|
||||
firstName: shipping.firstName,
|
||||
organisation: shipping.organisation?.name,
|
||||
street: shipping.address.street,
|
||||
streetNumber: shipping.address.streetNumber,
|
||||
zipCode: shipping.address.zipCode,
|
||||
city: shipping.address.city,
|
||||
country: shipping.address.country,
|
||||
info: shipping.address.info,
|
||||
});
|
||||
|
||||
this.formGroup.markAllAsTouched();
|
||||
this.formGroup.updateValueAndValidity();
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy.next();
|
||||
this._onDestroy.complete();
|
||||
@@ -127,7 +172,12 @@ export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
|
||||
address: addressValidationResult,
|
||||
};
|
||||
|
||||
const result = await this._customerService.createShippingAddress(this._store.customerId, shippingAddress, formData.isDefault);
|
||||
const result = await this._customerService.updateShippingAddress(
|
||||
this._store.customerId,
|
||||
this.shippingAddressId,
|
||||
shippingAddress,
|
||||
formData.isDefault
|
||||
);
|
||||
|
||||
this._navigation.navigateToDetails({ processId: this._store.processId, customerId: this._store.customerId });
|
||||
} catch (error) {
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-3 bg-surface text-surface-content">
|
||||
<shared-filter [filter]="filter" [loading]="fetching$ | async" (search)="search(filter)" [hint]="message$ | async"></shared-filter>
|
||||
<shared-filter
|
||||
[filter]="filter"
|
||||
[loading]="fetching$ | async"
|
||||
(search)="search(filter)"
|
||||
[hint]="message$ | async"
|
||||
[scanner]="true"
|
||||
></shared-filter>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-col gap-6 items-center justify-center mt-6">
|
||||
|
||||
@@ -39,9 +39,7 @@ export class CustomerFilterMainViewComponent {
|
||||
private _location: Location,
|
||||
private _router: Router,
|
||||
private _customerSearchNavigation: CustomerSearchNavigation
|
||||
) {
|
||||
console.log(this.isOnMainRoute);
|
||||
}
|
||||
) {}
|
||||
|
||||
search(filter: Filter) {
|
||||
this._store.setFilter(filter);
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<a
|
||||
*ngIf="customerCreateNavigation$ | async; let customerCreateNavigation"
|
||||
[routerLink]="customerCreateNavigation.path"
|
||||
[queryParams]="customerCreateNavigation.queryParams"
|
||||
class="text-[1.375rem] font-bold text-[#596470] text-center py-4"
|
||||
>Kundendaten erfassen</a
|
||||
>
|
||||
<div class="text-center pt-10 px-8 rounded-card side-view-shadow grow">
|
||||
<h1 class="text-[1.625rem] font-bold">Kundensuche</h1>
|
||||
<p class="text-lg mt-2 mb-6">
|
||||
@@ -17,4 +10,10 @@
|
||||
[loading]="fetching$ | async"
|
||||
[hint]="message$ | async"
|
||||
></shared-filter-input-group-main>
|
||||
<p class="mt-6">
|
||||
Kunde nicht gefunden?
|
||||
<a class="text-brand" *ngIf="createRoute$ | async; let route" [routerLink]="route.path" [queryParams]="route.queryParams">
|
||||
Neue Kundendaten erfassen
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user