mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
345 Commits
angular-up
...
fix/4516-P
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e3b3064f2 | ||
|
|
382f6470d3 | ||
|
|
d5254cc150 | ||
|
|
adc5a5a280 | ||
|
|
f171be1af2 | ||
|
|
dcff09ec7f | ||
|
|
f0b653fd0f | ||
|
|
14eba6e5ea | ||
|
|
0a88a92b45 | ||
|
|
5073693fc2 | ||
|
|
736b8f7ec7 | ||
|
|
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 | ||
|
|
384c32dd1f | ||
|
|
983d075d5a | ||
|
|
29ce32f3fe | ||
|
|
d9e67ec9be | ||
|
|
a1e7ee2997 | ||
|
|
653ed1c1b2 | ||
|
|
d7a3641fed | ||
|
|
71cd95587d | ||
|
|
4ca12ba5c7 | ||
|
|
bb189abe01 | ||
|
|
f4e6d14a9c | ||
|
|
a7f0522d57 | ||
|
|
cf38eef3b8 | ||
|
|
104179a2e6 | ||
|
|
4e2be8e397 | ||
|
|
732b5a7fb1 | ||
|
|
0441401d9f | ||
|
|
564afb7e32 | ||
|
|
5167ba21a6 | ||
|
|
b2319e8ea6 | ||
|
|
f8a2166967 | ||
|
|
6a70d149db | ||
|
|
306f5ed7f9 | ||
|
|
6a5d478e62 | ||
|
|
b440ddbe82 | ||
|
|
6b8051f9df | ||
|
|
f4df6e8799 | ||
|
|
e9a63fd553 | ||
|
|
a8535d5f3e | ||
|
|
23b77c7e48 | ||
|
|
878bf44d0b | ||
|
|
8b6188a6b5 | ||
|
|
822625a1c2 | ||
|
|
d6e0d92132 | ||
|
|
da6489eb7a | ||
|
|
819827cc4c | ||
|
|
d09b5b1ce7 | ||
|
|
cc03ef4f9c | ||
|
|
b4dbd8889d | ||
|
|
483faad86a | ||
|
|
0dbc745ed0 | ||
|
|
180e93a7da | ||
|
|
5c6f416391 | ||
|
|
d97b6afac8 | ||
|
|
771816f3af | ||
|
|
0626538aea | ||
|
|
a1ad4e4a05 | ||
|
|
6df48eb555 | ||
|
|
27ab4526e2 | ||
|
|
9a24b34fbc | ||
|
|
d01e01534b | ||
|
|
5bca1f2a81 | ||
|
|
807b300885 | ||
|
|
b16ffa4352 | ||
|
|
da79d04ef4 | ||
|
|
cf619df576 | ||
|
|
8054c96315 | ||
|
|
2363f424f5 | ||
|
|
6ab1ea2e70 | ||
|
|
c9ce7d6762 | ||
|
|
6ee1b0a7f8 | ||
|
|
d881920312 | ||
|
|
516465db37 | ||
|
|
08e95cec55 | ||
|
|
9671683a93 | ||
|
|
d909d6e804 | ||
|
|
15c50779b4 | ||
|
|
1d865c47d7 | ||
|
|
5bdfec7c3f | ||
|
|
6b0beba1d9 | ||
|
|
9d886cd33f | ||
|
|
6bc265a358 |
@@ -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)';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
StoreCheckoutSupplierService,
|
||||
SupplierDTO,
|
||||
} from '@swagger/checkout';
|
||||
import { combineLatest, Observable, of } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
|
||||
import {
|
||||
AvailabilityRequestDTO,
|
||||
AvailabilityService,
|
||||
@@ -21,11 +21,16 @@ import { isArray, memorize } from '@utils/common';
|
||||
import { LogisticianDTO, LogisticianService } from '@swagger/oms';
|
||||
import { ResponseArgsOfIEnumerableOfStockInfoDTO, StockDTO, StockInfoDTO, StockService } from '@swagger/remi';
|
||||
import { PriceDTO } from '@swagger/availability';
|
||||
import { AvailabilityByBranchDTO, ItemData } from './defs';
|
||||
import { AvailabilityByBranchDTO, ItemData, Ssc } from './defs';
|
||||
import { Availability } from './defs/availability';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
@Injectable()
|
||||
export class DomainAvailabilityService {
|
||||
// Ticket #3378 Keep Result List Items and Details Page SSC in sync
|
||||
sscs$ = new BehaviorSubject<Array<Ssc>>([]);
|
||||
sscsObs$ = this.sscs$.asObservable();
|
||||
|
||||
constructor(
|
||||
private _availabilityService: AvailabilityService,
|
||||
private _logisticanService: LogisticianService,
|
||||
@@ -284,7 +289,7 @@ export class DomainAvailabilityService {
|
||||
const availabilities = r.result;
|
||||
const preferred = availabilities?.find((f) => f.preferred === 1);
|
||||
|
||||
const availability: AvailabilityDTO = {
|
||||
return {
|
||||
availabilityType: preferred?.status,
|
||||
ssc: preferred?.ssc,
|
||||
sscText: preferred?.sscText,
|
||||
@@ -297,8 +302,8 @@ export class DomainAvailabilityService {
|
||||
supplierProductNumber: preferred?.supplierProductNumber,
|
||||
supplierInfo: preferred?.requestStatusCode,
|
||||
lastRequest: preferred?.requested,
|
||||
priceMaintained: preferred?.priceMaintained,
|
||||
};
|
||||
return availability;
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
@@ -347,7 +352,7 @@ export class DomainAvailabilityService {
|
||||
const availabilities = r.result;
|
||||
const preferred = availabilities?.find((f) => f.preferred === 1);
|
||||
|
||||
const availability: AvailabilityDTO = {
|
||||
return {
|
||||
availabilityType: preferred?.status,
|
||||
ssc: preferred?.ssc,
|
||||
sscText: preferred?.sscText,
|
||||
@@ -359,8 +364,8 @@ export class DomainAvailabilityService {
|
||||
logistician: { id: preferred?.logisticianId },
|
||||
supplierInfo: preferred?.requestStatusCode,
|
||||
lastRequest: preferred?.requested,
|
||||
priceMaintained: preferred?.priceMaintained,
|
||||
};
|
||||
return availability;
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
@@ -453,30 +458,8 @@ export class DomainAvailabilityService {
|
||||
return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
|
||||
}
|
||||
|
||||
mapToOlaAvailability({
|
||||
availability,
|
||||
item,
|
||||
quantity,
|
||||
}: {
|
||||
availability: AvailabilityDTO;
|
||||
item: ItemDTO;
|
||||
quantity: number;
|
||||
}): OLAAvailabilityDTO {
|
||||
return {
|
||||
status: availability?.availabilityType,
|
||||
at: availability?.estimatedShippingDate,
|
||||
ean: item?.product?.ean,
|
||||
itemId: item?.id,
|
||||
format: item?.product?.format,
|
||||
isPrebooked: availability?.isPrebooked,
|
||||
logisticianId: availability?.logistician?.id,
|
||||
price: availability?.price,
|
||||
qty: quantity,
|
||||
ssc: availability?.ssc,
|
||||
sscText: availability?.sscText,
|
||||
supplierId: availability?.supplier?.id,
|
||||
supplierProductNumber: availability?.supplierProductNumber,
|
||||
};
|
||||
private _priceIsEmpty(price: PriceDTO) {
|
||||
return isEmpty(price?.value) || isEmpty(price?.vat);
|
||||
}
|
||||
|
||||
private _mapToTakeAwayAvailability({
|
||||
@@ -499,7 +482,7 @@ export class DomainAvailabilityService {
|
||||
inStock: inStock,
|
||||
supplierSSC: quantity <= inStock ? '999' : '',
|
||||
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
|
||||
price: price ?? stockInfo?.retailPrice,
|
||||
price: this._priceIsEmpty(price) ? stockInfo?.retailPrice : price,
|
||||
supplier: { id: supplier?.id },
|
||||
// TODO: Change after API Update
|
||||
// LH: 2021-03-09 preis Property hat nun ein Fallback auf retailPrice
|
||||
@@ -554,6 +537,7 @@ export class DomainAvailabilityService {
|
||||
supplierInfo: p?.requestStatusCode,
|
||||
lastRequest: p?.requested,
|
||||
itemId: p.itemId,
|
||||
priceMaintained: p.priceMaintained,
|
||||
},
|
||||
p,
|
||||
];
|
||||
@@ -561,7 +545,7 @@ export class DomainAvailabilityService {
|
||||
}
|
||||
}
|
||||
|
||||
private _mapToShippingAvailability(availabilities: SwaggerAvailabilityDTO[]) {
|
||||
private _mapToShippingAvailability(availabilities: SwaggerAvailabilityDTO[]): AvailabilityDTO[] {
|
||||
const preferred = availabilities.filter((f) => f.preferred === 1);
|
||||
return preferred.map((p) => {
|
||||
return {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './availability-by-branch-dto';
|
||||
export * from './availability';
|
||||
export * from './item-data';
|
||||
export * from './ssc';
|
||||
|
||||
5
apps/domain/availability/src/lib/defs/ssc.ts
Normal file
5
apps/domain/availability/src/lib/defs/ssc.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface Ssc {
|
||||
itemId?: number;
|
||||
ssc?: string;
|
||||
sscText?: string;
|
||||
}
|
||||
@@ -27,6 +27,9 @@ import {
|
||||
StoreCheckoutPayerService,
|
||||
StoreCheckoutBranchService,
|
||||
ItemsResult,
|
||||
KulturPassService,
|
||||
ProductDTO,
|
||||
KulturPassResult,
|
||||
} from '@swagger/checkout';
|
||||
import {
|
||||
DisplayOrderDTO,
|
||||
@@ -36,20 +39,41 @@ import {
|
||||
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
|
||||
} from '@swagger/oms';
|
||||
import { isNullOrUndefined, memorize } from '@utils/common';
|
||||
import { combineLatest, Observable, of, concat, isObservable, throwError } from 'rxjs';
|
||||
import { bufferCount, catchError, filter, first, map, mergeMap, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { combineLatest, Observable, of, concat, isObservable, throwError, interval as rxjsInterval } from 'rxjs';
|
||||
import {
|
||||
bufferCount,
|
||||
catchError,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
first,
|
||||
map,
|
||||
mergeMap,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
tap,
|
||||
withLatestFrom,
|
||||
startWith,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import * as DomainCheckoutSelectors from './store/domain-checkout.selectors';
|
||||
import * as DomainCheckoutActions from './store/domain-checkout.actions';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainAvailabilityService, ItemData } from '@domain/availability';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { CustomerDTO, EntityDTOContainerOfAttributeDTO } from '@swagger/crm';
|
||||
import { CustomerDTO } from '@swagger/crm';
|
||||
import { Config } from '@core/config';
|
||||
import parseDuration from 'parse-duration';
|
||||
|
||||
@Injectable()
|
||||
export class DomainCheckoutService {
|
||||
get olaExpiration() {
|
||||
const exp = this._config.get('@domain/checkout.olaExpiration') ?? '5m';
|
||||
return parseDuration(exp);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<any>,
|
||||
private _config: Config,
|
||||
private applicationService: ApplicationService,
|
||||
private storeCheckoutService: StoreCheckoutService,
|
||||
private orderCheckoutService: OrderCheckoutService,
|
||||
@@ -58,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
|
||||
@@ -119,14 +144,14 @@ export class DomainCheckoutService {
|
||||
})
|
||||
.pipe(
|
||||
map((response) => response.result),
|
||||
tap((shoppingCart) =>
|
||||
tap((shoppingCart) => {
|
||||
this.store.dispatch(
|
||||
DomainCheckoutActions.setShoppingCart({
|
||||
processId,
|
||||
shoppingCart,
|
||||
})
|
||||
)
|
||||
),
|
||||
);
|
||||
}),
|
||||
tap((shoppingCart) => this.updateProcessCount(processId, shoppingCart?.items?.length))
|
||||
)
|
||||
)
|
||||
@@ -205,6 +230,10 @@ export class DomainCheckoutService {
|
||||
);
|
||||
}
|
||||
|
||||
canAddItemsKulturpass(payload: ProductDTO[]): Observable<KulturPassResult[]> {
|
||||
return this._kulturpassService.KulturPassCanAddForKulturPass({ payload }).pipe(map((response) => response?.result));
|
||||
}
|
||||
|
||||
canAddItems({
|
||||
processId,
|
||||
payload,
|
||||
@@ -249,11 +278,24 @@ export class DomainCheckoutService {
|
||||
shoppingCartItemId: number;
|
||||
availability: AvailabilityDTO;
|
||||
}) {
|
||||
return this._shoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItemAvailability({
|
||||
shoppingCartId,
|
||||
shoppingCartItemId,
|
||||
availability,
|
||||
});
|
||||
return this._shoppingCartService
|
||||
.StoreCheckoutShoppingCartUpdateShoppingCartItemAvailability({
|
||||
shoppingCartId,
|
||||
shoppingCartItemId,
|
||||
availability,
|
||||
})
|
||||
.pipe(
|
||||
map((response) => response.result),
|
||||
tap((shoppingCart) => {
|
||||
this.store.dispatch(
|
||||
DomainCheckoutActions.addShoppingCartItemAvailabilityToHistoryByShoppingCartId({
|
||||
shoppingCartId,
|
||||
availability,
|
||||
shoppingCartItemId,
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateItemInShoppingCart({
|
||||
@@ -265,7 +307,7 @@ export class DomainCheckoutService {
|
||||
shoppingCartItemId: number;
|
||||
update: UpdateShoppingCartItemDTO;
|
||||
}): Observable<ShoppingCartDTO> {
|
||||
return this.getShoppingCart({ processId }).pipe(
|
||||
return this.getShoppingCart({ processId, latest: true }).pipe(
|
||||
first(),
|
||||
mergeMap((shoppingCart) =>
|
||||
this._shoppingCartService
|
||||
@@ -276,8 +318,21 @@ export class DomainCheckoutService {
|
||||
})
|
||||
.pipe(
|
||||
map((response) => response.result),
|
||||
tap((shoppingCart) => this.store.dispatch(DomainCheckoutActions.setShoppingCart({ processId, shoppingCart }))),
|
||||
tap((shoppingCart) => this.updateProcessCount(processId, shoppingCart?.items?.length))
|
||||
tap((shoppingCart) => {
|
||||
this.store.dispatch(DomainCheckoutActions.setShoppingCart({ processId, shoppingCart }));
|
||||
|
||||
if (update.availability) {
|
||||
this.store.dispatch(
|
||||
DomainCheckoutActions.addShoppingCartItemAvailabilityToHistory({
|
||||
processId,
|
||||
availability: update.availability,
|
||||
shoppingCartItemId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this.updateProcessCount(processId, shoppingCart?.items?.length);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -547,6 +602,175 @@ export class DomainCheckoutService {
|
||||
);
|
||||
}
|
||||
|
||||
async refreshAvailability({
|
||||
processId,
|
||||
shoppingCartItemId,
|
||||
}: {
|
||||
processId: number;
|
||||
shoppingCartItemId: number;
|
||||
}): Promise<AvailabilityDTO> {
|
||||
const shoppingCart = await this.getShoppingCart({ processId }).pipe(first()).toPromise();
|
||||
const item = shoppingCart?.items.find((item) => item.id === shoppingCartItemId)?.data;
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemData: ItemData = {
|
||||
ean: item.product.ean,
|
||||
itemId: Number(item.product.catalogProductNumber),
|
||||
price: item.availability.price,
|
||||
};
|
||||
|
||||
let availability: AvailabilityDTO;
|
||||
|
||||
switch (item.features.orderType) {
|
||||
case 'Abholung':
|
||||
const abholung = await this.availabilityService
|
||||
.getPickUpAvailability({
|
||||
item: itemData,
|
||||
branch: item.destination?.data?.targetBranch?.data,
|
||||
quantity: item.quantity,
|
||||
})
|
||||
.toPromise();
|
||||
availability = abholung[0];
|
||||
break;
|
||||
case 'Rücklage':
|
||||
const ruecklage = await this.availabilityService
|
||||
.getTakeAwayAvailability({
|
||||
item: itemData,
|
||||
quantity: item.quantity,
|
||||
branch: item.destination?.data?.targetBranch?.data,
|
||||
})
|
||||
.toPromise();
|
||||
availability = ruecklage;
|
||||
break;
|
||||
case 'Download':
|
||||
const download = await this.availabilityService
|
||||
.getDownloadAvailability({
|
||||
item: itemData,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
availability = download;
|
||||
break;
|
||||
|
||||
case 'Versand':
|
||||
const versand = await this.availabilityService
|
||||
.getDeliveryAvailability({
|
||||
item: itemData,
|
||||
quantity: item.quantity,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
availability = versand;
|
||||
break;
|
||||
|
||||
case 'DIG-Versand':
|
||||
const digVersand = await this.availabilityService
|
||||
.getDigDeliveryAvailability({
|
||||
item: itemData,
|
||||
quantity: item.quantity,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
availability = digVersand;
|
||||
break;
|
||||
|
||||
case 'B2B-Versand':
|
||||
const b2bVersand = await this.availabilityService
|
||||
.getB2bDeliveryAvailability({
|
||||
item: itemData,
|
||||
quantity: item.quantity,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
availability = b2bVersand;
|
||||
break;
|
||||
}
|
||||
|
||||
await this.updateItemInShoppingCart({
|
||||
processId,
|
||||
update: { availability },
|
||||
shoppingCartItemId: item.id,
|
||||
}).toPromise();
|
||||
|
||||
return availability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the availability of all items is valid
|
||||
* @param param0 Process Id
|
||||
* @returns true if the availability of all items is valid
|
||||
*/
|
||||
validateOlaStatus({ processId, interval }: { processId: number; interval?: number }): Observable<boolean> {
|
||||
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 itemAvailabilityTimestamp = entity.itemAvailabilityTimestamp ?? {};
|
||||
const shoppingCart = entity.shoppingCart;
|
||||
|
||||
const timestamps = shoppingCart.items
|
||||
?.map((i) => i.data)
|
||||
?.filter((item) => !!item?.features?.orderType)
|
||||
?.map((item) => {
|
||||
const orderType = item.features.orderType;
|
||||
|
||||
let timestamp = itemAvailabilityTimestamp[`${item.id}_${orderType}`];
|
||||
|
||||
if (timestamp) {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
if (orderType.endsWith('Versand')) {
|
||||
timestamp =
|
||||
itemAvailabilityTimestamp[`${item.id}_Versand`] ??
|
||||
itemAvailabilityTimestamp[`${item.id}_DIG-Versand`] ??
|
||||
itemAvailabilityTimestamp[`${item.id}_B2B-Versand`];
|
||||
}
|
||||
|
||||
return timestamp;
|
||||
})
|
||||
?.filter((timestamp) => !!timestamp);
|
||||
|
||||
if (timestamps?.length > 0) {
|
||||
const oldestTimestamp = Math.min(...timestamps);
|
||||
const expirationTimestamp = oldestTimestamp + this.olaExpiration;
|
||||
return expirationTimestamp > now;
|
||||
}
|
||||
|
||||
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 }));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
checkoutIsValid({ processId }: { processId: number }): Observable<boolean> {
|
||||
const olaStatus$ = this.validateOlaStatus({ processId, interval: 250 });
|
||||
|
||||
const availabilities$ = this.validateAvailabilities({ processId });
|
||||
|
||||
return combineLatest([olaStatus$, availabilities$]).pipe(map(([olaStatus, availabilities]) => olaStatus && availabilities));
|
||||
}
|
||||
|
||||
completeCheckout({ processId }: { processId: number }): Observable<DisplayOrderDTO[]> {
|
||||
const refreshShoppingCart$ = this.getShoppingCart({ processId, latest: true }).pipe(first());
|
||||
const refreshCheckout$ = this.getCheckout({ processId, refresh: true }).pipe(first());
|
||||
@@ -700,21 +924,23 @@ export class DomainCheckoutService {
|
||||
)
|
||||
);
|
||||
|
||||
return updateDestination$
|
||||
.pipe(tap(console.log.bind(window, 'updateDestination$')))
|
||||
return of(undefined)
|
||||
.pipe(
|
||||
mergeMap((_) => updateDestination$.pipe(tap(console.log.bind(window, 'updateDestination$')))),
|
||||
mergeMap((_) => refreshShoppingCart$.pipe(tap(console.log.bind(window, 'refreshShoppingCart$')))),
|
||||
mergeMap((_) => setSpecialComment$.pipe(tap(console.log.bind(window, 'setSpecialComment$')))),
|
||||
mergeMap((_) => refreshCheckout$.pipe(tap(console.log.bind(window, 'refreshCheckout$')))),
|
||||
mergeMap((_) => checkAvailabilities$.pipe(tap(console.log.bind(window, 'checkAvailabilities$')))),
|
||||
mergeMap((_) => updateAvailabilities$.pipe(tap(console.log.bind(window, 'updateAvailabilities$')))),
|
||||
mergeMap((_) => updateAvailabilities$.pipe(tap(console.log.bind(window, 'updateAvailabilities$'))))
|
||||
)
|
||||
.pipe(
|
||||
mergeMap((_) => setBuyer$.pipe(tap(console.log.bind(window, 'setBuyer$')))),
|
||||
mergeMap((_) => setNotificationChannels$.pipe(tap(console.log.bind(window, 'setNotificationChannels$')))),
|
||||
mergeMap((_) => setPayer$.pipe(tap(console.log.bind(window, 'setPayer$')))),
|
||||
mergeMap((_) => setPaymentType$.pipe(tap(console.log.bind(window, 'setPaymentType$')))),
|
||||
mergeMap((_) => setDestination$.pipe(tap(console.log.bind(window, 'setDestination$'))))
|
||||
)
|
||||
.pipe(mergeMap((_) => completeOrder$.pipe(tap(console.log.bind(window, 'completeOrder$')))));
|
||||
mergeMap((_) => setDestination$.pipe(tap(console.log.bind(window, 'setDestination$')))),
|
||||
mergeMap((_) => completeOrder$.pipe(tap(console.log.bind(window, 'completeOrder$'))))
|
||||
);
|
||||
}
|
||||
|
||||
completeKulturpassOrder({
|
||||
@@ -795,6 +1021,7 @@ export class DomainCheckoutService {
|
||||
|
||||
//#region Common
|
||||
|
||||
@memorize()
|
||||
canSetCustomer({
|
||||
processId,
|
||||
customerFeatures,
|
||||
@@ -802,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 {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { BuyerDTO, CheckoutDTO, NotificationChannel, PayerDTO, ShippingAddressDTO, ShoppingCartDTO } from '@swagger/checkout';
|
||||
import {
|
||||
AvailabilityDTO,
|
||||
BuyerDTO,
|
||||
CheckoutDTO,
|
||||
NotificationChannel,
|
||||
PayerDTO,
|
||||
ShippingAddressDTO,
|
||||
ShoppingCartDTO,
|
||||
} from '@swagger/checkout';
|
||||
import { CustomerDTO } from '@swagger/crm';
|
||||
import { DisplayOrderDTO } from '@swagger/oms';
|
||||
|
||||
@@ -14,4 +22,5 @@ export interface CheckoutEntity {
|
||||
specialComment: string;
|
||||
notificationChannels: NotificationChannel;
|
||||
olaErrorIds: number[];
|
||||
itemAvailabilityTimestamp: Record<string, number | undefined>;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ShippingAddressDTO,
|
||||
BuyerDTO,
|
||||
PayerDTO,
|
||||
AvailabilityDTO,
|
||||
} from '@swagger/checkout';
|
||||
import { CustomerDTO } from '@swagger/crm';
|
||||
import { DisplayOrderDTO, DisplayOrderItemDTO } from '@swagger/oms';
|
||||
@@ -61,3 +62,13 @@ export const setSpecialComment = createAction(`${prefix} Set Agent Comment`, pro
|
||||
export const setOlaError = createAction(`${prefix} Set Ola Error`, props<{ processId: number; olaErrorIds: number[] }>());
|
||||
|
||||
export const setCustomer = createAction(`${prefix} Set Customer`, props<{ processId: number; customer: CustomerDTO }>());
|
||||
|
||||
export const addShoppingCartItemAvailabilityToHistory = createAction(
|
||||
`${prefix} Add Shopping Cart Item Availability To History`,
|
||||
props<{ processId: number; shoppingCartItemId: number; availability: AvailabilityDTO }>()
|
||||
);
|
||||
|
||||
export const addShoppingCartItemAvailabilityToHistoryByShoppingCartId = createAction(
|
||||
`${prefix} Add Shopping Cart Item Availability To History By Shopping Cart Id`,
|
||||
props<{ shoppingCartId: number; shoppingCartItemId: number; availability: AvailabilityDTO }>()
|
||||
);
|
||||
|
||||
@@ -10,7 +10,22 @@ const _domainCheckoutReducer = createReducer(
|
||||
initialCheckoutState,
|
||||
on(DomainCheckoutActions.setShoppingCart, (s, { processId, shoppingCart }) => {
|
||||
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
|
||||
|
||||
const addedShoppingCartItems =
|
||||
shoppingCart?.items?.filter((item) => !entity.shoppingCart?.items?.find((i) => i.id === item.id))?.map((item) => item.data) ?? [];
|
||||
|
||||
entity.shoppingCart = shoppingCart;
|
||||
|
||||
entity.itemAvailabilityTimestamp = entity.itemAvailabilityTimestamp ? { ...entity.itemAvailabilityTimestamp } : {};
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
for (let shoppingCartItem of addedShoppingCartItems) {
|
||||
if (shoppingCartItem.features?.orderType) {
|
||||
entity.itemAvailabilityTimestamp[`${shoppingCartItem.id}_${shoppingCartItem.features.orderType}`] = now;
|
||||
}
|
||||
}
|
||||
|
||||
return storeCheckoutAdapter.setOne(entity, s);
|
||||
}),
|
||||
on(DomainCheckoutActions.setCheckout, (s, { processId, checkout }) => {
|
||||
@@ -100,7 +115,40 @@ const _domainCheckoutReducer = createReducer(
|
||||
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
|
||||
entity.customer = customer;
|
||||
return storeCheckoutAdapter.setOne(entity, s);
|
||||
})
|
||||
}),
|
||||
on(DomainCheckoutActions.addShoppingCartItemAvailabilityToHistory, (s, { processId, shoppingCartItemId, availability }) => {
|
||||
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
|
||||
|
||||
const itemAvailabilityTimestamp = entity?.itemAvailabilityTimestamp ? { ...entity?.itemAvailabilityTimestamp } : {};
|
||||
|
||||
const item = entity?.shoppingCart?.items?.find((i) => i.id === shoppingCartItemId)?.data;
|
||||
|
||||
if (!item?.features?.orderType) return s;
|
||||
|
||||
itemAvailabilityTimestamp[`${item.id}_${item?.features?.orderType}`] = Date.now();
|
||||
|
||||
entity.itemAvailabilityTimestamp = itemAvailabilityTimestamp;
|
||||
|
||||
return storeCheckoutAdapter.setOne(entity, s);
|
||||
}),
|
||||
on(
|
||||
DomainCheckoutActions.addShoppingCartItemAvailabilityToHistoryByShoppingCartId,
|
||||
(s, { shoppingCartId, shoppingCartItemId, availability }) => {
|
||||
const entity = getCheckoutEntityByShoppingCartId({ shoppingCartId, entities: s.entities });
|
||||
|
||||
const itemAvailabilityTimestamp = entity?.itemAvailabilityTimestamp ? { ...entity?.itemAvailabilityTimestamp } : {};
|
||||
|
||||
const item = entity?.shoppingCart?.items?.find((i) => i.id === shoppingCartItemId)?.data;
|
||||
|
||||
if (!item?.features?.orderType) return s;
|
||||
|
||||
itemAvailabilityTimestamp[`${item.id}_${item?.features?.orderType}`] = Date.now();
|
||||
|
||||
entity.itemAvailabilityTimestamp = itemAvailabilityTimestamp;
|
||||
|
||||
return storeCheckoutAdapter.setOne(entity, s);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export function domainCheckoutReducer(state, action) {
|
||||
@@ -123,8 +171,20 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
|
||||
notificationChannels: 0,
|
||||
olaErrorIds: [],
|
||||
customer: undefined,
|
||||
// availabilityHistory: [],
|
||||
itemAvailabilityTimestamp: {},
|
||||
};
|
||||
}
|
||||
|
||||
return { ...entity };
|
||||
}
|
||||
|
||||
function getCheckoutEntityByShoppingCartId({
|
||||
entities,
|
||||
shoppingCartId,
|
||||
}: {
|
||||
entities: Dictionary<CheckoutEntity>;
|
||||
shoppingCartId: number;
|
||||
}): CheckoutEntity {
|
||||
return Object.values(entities).find((entity) => entity.shoppingCart?.id === shoppingCartId);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -22,7 +22,7 @@ export class CanActivateCartGuard {
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
});
|
||||
}
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: lastActivatedProcessId });
|
||||
await this._checkoutNavigationService.getCheckoutReviewPath(lastActivatedProcessId).path;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class CanActivateCustomerOrdersGuard {
|
||||
await this.fromGoodsOutProcess(processes, route);
|
||||
return false;
|
||||
} else {
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId: lastActivatedProcessId });
|
||||
await this._navigationService.getCustomerOrdersBasePath(lastActivatedProcessId).navigate();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -52,7 +52,7 @@ export class CanActivateCustomerOrdersGuard {
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId: newProcessId });
|
||||
await this._navigationService.getCustomerOrdersBasePath(newProcessId).navigate();
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Kundenbestellungen
|
||||
@@ -70,7 +70,7 @@ export class CanActivateCustomerOrdersGuard {
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId });
|
||||
await this._navigationService.getCustomerOrdersBasePath(processId).navigate();
|
||||
}
|
||||
|
||||
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -42,7 +42,7 @@ export class CanActivateProductGuard {
|
||||
await this.fromCartProcess(processes);
|
||||
return false;
|
||||
} else {
|
||||
await this._navigationService.navigateToProductSearch({ processId: lastActivatedProcessId });
|
||||
await this._navigationService.getArticleSearchBasePath(lastActivatedProcessId).navigate();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -58,7 +58,7 @@ export class CanActivateProductGuard {
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this._navigationService.navigateToProductSearch({ processId: newProcessId });
|
||||
await this._navigationService.getArticleSearchBasePath(newProcessId).navigate();
|
||||
}
|
||||
|
||||
// Bei offener Warenausgabe und Klick auf Footer Artikelsuche
|
||||
@@ -82,7 +82,7 @@ export class CanActivateProductGuard {
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._navigationService.navigateToProductSearch({ processId });
|
||||
await this._navigationService.getArticleSearchBasePath(processId).navigate();
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Footer Artikelsuche
|
||||
@@ -100,7 +100,7 @@ export class CanActivateProductGuard {
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._navigationService.navigateToProductSearch({ processId });
|
||||
await this._navigationService.getArticleSearchBasePath(processId).navigate();
|
||||
}
|
||||
|
||||
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
|
||||
|
||||
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"
|
||||
@@ -266,6 +270,16 @@
|
||||
"name": "text-decrease",
|
||||
"data": "m40-200 220-560h80l220 560h-75l-57-150H172l-57 150H40Zm156-214h208L302-685h-4L196-414Zm414-36v-60h310v60H610Z",
|
||||
"viewBox":"0 -960 960 960"
|
||||
},
|
||||
{
|
||||
"name": "calendar-today",
|
||||
"data": "M180-80q-24 0-42-18t-18-42v-620q0-24 18-42t42-18h65v-60h65v60h340v-60h65v60h65q24 0 42 18t18 42v620q0 24-18 42t-42 18H180Zm0-60h600v-430H180v430Zm0-490h600v-130H180v130Zm0 0v-130 130Z",
|
||||
"viewBox": "0 -960 960 960"
|
||||
},
|
||||
{
|
||||
"name": "apps",
|
||||
"data": "M226-160q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19ZM226-414q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19ZM226-668q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Z",
|
||||
"viewBox": "0 -960 960 960"
|
||||
}
|
||||
|
||||
],
|
||||
@@ -401,6 +415,10 @@
|
||||
{
|
||||
"name": "isa-box-out",
|
||||
"alias": "Versandbestellung (oder gemischt)"
|
||||
},
|
||||
{
|
||||
"name": "package-variant-closed",
|
||||
"alias": "Bestellung ohne Konto"
|
||||
},{
|
||||
"name": "person",
|
||||
"alias": "Onlinekonto"
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||
},
|
||||
@@ -64,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"
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
|
||||
},
|
||||
@@ -63,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"
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
"@swagger/wws": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
||||
@@ -65,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"
|
||||
}
|
||||
@@ -16,6 +16,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/isa/v1"
|
||||
},
|
||||
@@ -64,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"
|
||||
}
|
||||
@@ -16,6 +16,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/isa/v1"
|
||||
},
|
||||
@@ -64,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"
|
||||
}
|
||||
@@ -17,6 +17,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||
},
|
||||
@@ -65,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>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply text-[#0556B4];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<a [routerLink]="route?.path" [queryParams]="route?.queryParams">
|
||||
<ng-content></ng-content>
|
||||
</a>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-details-text-link',
|
||||
templateUrl: 'article-details-text-link.component.html',
|
||||
styleUrls: ['article-details-text-link.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-article-details-text-link' },
|
||||
standalone: true,
|
||||
imports: [RouterLink],
|
||||
})
|
||||
export class ArticleDetailsTextLinkComponent {
|
||||
@Input()
|
||||
route: { path: string[]; queryParams?: Record<string, string> };
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply block whitespace-pre-line;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<ng-container *ngFor="let line of lines">
|
||||
<ng-container [ngSwitch]="line | lineType">
|
||||
<ng-container *ngSwitchCase="'reihe'">
|
||||
<page-article-details-text-link *ngFor="let reihe of getReihen(line)" [route]="reihe | reiheRoute">
|
||||
{{ reihe }}
|
||||
</page-article-details-text-link>
|
||||
<br />
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault> {{ line }} <br /> </ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { TextDTO } from '@swagger/cat';
|
||||
import { ArticleDetailsTextLinkComponent } from './article-details-text-link.component';
|
||||
import { NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
|
||||
import { LineTypePipe } from './line-type.pipe';
|
||||
import { ReiheRoutePipe } from './reihe-route.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-details-text',
|
||||
templateUrl: 'article-details-text.component.html',
|
||||
styleUrls: ['article-details-text.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-article-details-text' },
|
||||
standalone: true,
|
||||
imports: [ArticleDetailsTextLinkComponent, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault, LineTypePipe, ReiheRoutePipe],
|
||||
})
|
||||
export class ArticleDetailsTextComponent {
|
||||
@Input()
|
||||
text: TextDTO;
|
||||
|
||||
get lines() {
|
||||
return this.text?.value?.split('\n');
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
|
||||
getReihen(line: string): string[] {
|
||||
let splittedReihen = line?.split(';');
|
||||
return splittedReihen?.map((reihe, index) => {
|
||||
if (splittedReihen?.length !== index + 1) {
|
||||
return reihe + ';';
|
||||
}
|
||||
return reihe;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'lineType',
|
||||
standalone: true,
|
||||
pure: true,
|
||||
})
|
||||
export class LineTypePipe implements PipeTransform {
|
||||
transform(value: string, ...args: any[]): 'text' | 'reihe' {
|
||||
const REIHE_REGEX = /^Reihe:\s*"(.+)\"$/g;
|
||||
const reihe = REIHE_REGEX.exec(value)?.[1];
|
||||
return reihe ? 'reihe' : 'text';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Subscription, combineLatest, BehaviorSubject } from 'rxjs';
|
||||
import { distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
@Pipe({
|
||||
name: 'reiheRoute',
|
||||
standalone: true,
|
||||
pure: false,
|
||||
})
|
||||
export class ReiheRoutePipe implements PipeTransform, OnDestroy {
|
||||
private subscription: Subscription;
|
||||
|
||||
value$ = new BehaviorSubject<string>('');
|
||||
|
||||
result: { path: string[]; queryParams?: Record<string, string> };
|
||||
|
||||
constructor(
|
||||
private navigation: ProductCatalogNavigationService,
|
||||
private application: ApplicationService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
this.subscription = combineLatest([this.application.activatedProcessId$, this.value$])
|
||||
.pipe(distinctUntilChanged(isEqual))
|
||||
.subscribe(([processId, value]) => {
|
||||
const REIHE_REGEX = /[";]|Reihe:/g; // Entferne jedes Semikolon, Anführungszeichen und den String Reihe:
|
||||
const reihe = value?.replace(REIHE_REGEX, '')?.trim();
|
||||
|
||||
if (!reihe) {
|
||||
this.result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const main_qs = reihe.split('/')[0];
|
||||
|
||||
const path = this.navigation.getArticleSearchResultsPath(processId).path;
|
||||
|
||||
this.result = {
|
||||
path,
|
||||
queryParams: {
|
||||
main_qs,
|
||||
main_serial: 'serial',
|
||||
},
|
||||
};
|
||||
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription?.unsubscribe();
|
||||
this.value$?.unsubscribe();
|
||||
}
|
||||
|
||||
transform(value: string, ...args: any[]) {
|
||||
this.value$.next(value);
|
||||
|
||||
return this.result;
|
||||
|
||||
// const REIHE_REGEX = /^Reihe:\s*"(.+)\"$/g;
|
||||
// const reihe = REIHE_REGEX.exec(value)?.[1];
|
||||
|
||||
// this.navigation.getArticleSearchResultsPath(this.)
|
||||
|
||||
// return reihe ? `/search?query=${reihe}` : null;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -103,28 +489,16 @@
|
||||
<div class="page-article-details__product-publication">{{ publicationDate$ | async }}</div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-price-info flex flex-col mb-4">
|
||||
<div
|
||||
class="page-article-details__product-price font-bold text-xl self-end"
|
||||
*ngIf="item.catalogAvailability?.price?.value?.value; else retailPrice"
|
||||
>
|
||||
{{ item.catalogAvailability?.price?.value?.value | currency: item.catalogAvailability?.price?.value?.currency:'code' }}
|
||||
<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>
|
||||
<ng-template #retailPrice>
|
||||
<div
|
||||
class="page-article-details__product-price font-bold text-xl self-end"
|
||||
*ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability"
|
||||
>
|
||||
{{ takeAwayAvailability?.retailPrice?.value?.value | currency: takeAwayAvailability?.retailPrice?.value?.currency:'code' }}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div class="page-article-details__product-points self-end" *ngIf="store.promotionPoints$ | async; let promotionPoints">
|
||||
{{ promotionPoints }} Lesepunkte
|
||||
</div>
|
||||
|
||||
<!-- TODO: Ticket PREISGEBUNDEN -->
|
||||
<div class="page-article-details__product-price-bound self-end"></div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-origin-infos flex flex-col mb-4">
|
||||
@@ -137,21 +511,23 @@
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</button>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-12" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
|
||||
<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>
|
||||
@@ -206,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>
|
||||
|
||||
@@ -219,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>
|
||||
|
||||
@@ -232,9 +608,9 @@
|
||||
|
||||
<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 *ngIf="store.sscText$ | async; let sscText">
|
||||
<div class="text-right" *ngIf="store.sscText$ | async; let sscText">
|
||||
{{ sscText }}
|
||||
</div>
|
||||
</ng-container>
|
||||
@@ -304,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
|
||||
@@ -325,9 +701,7 @@
|
||||
<hr class="bg-[#E6EFF9] border-t-2 my-3" />
|
||||
|
||||
<div #description class="page-article-details__product-description flex flex-col flex-grow" *ngIf="item.texts?.length > 0">
|
||||
<div class="whitespace-pre-line">
|
||||
{{ item.texts[0].value }}
|
||||
</div>
|
||||
<page-article-details-text [text]="item.texts[0]"> </page-article-details-text>
|
||||
|
||||
<button
|
||||
class="font-bold flex flex-row text-[#0556B4] items-center mt-2"
|
||||
@@ -362,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
|
||||
@@ -394,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 {
|
||||
@@ -15,8 +19,8 @@
|
||||
'image contributors contributors contributors'
|
||||
'image title title print'
|
||||
'image title title .'
|
||||
'image misc misc price'
|
||||
'image misc misc price'
|
||||
'image misc price price'
|
||||
'image misc price price'
|
||||
'image origin origin stock'
|
||||
'image origin origin stock'
|
||||
'image specs availabilities availabilities'
|
||||
@@ -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,8 +20,9 @@ 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';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-details',
|
||||
@@ -32,6 +33,8 @@ import { DomainCheckoutService } from '@domain/checkout';
|
||||
animations: [slideYAnimation],
|
||||
})
|
||||
export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
private _customerSearchNavigation = inject(CustomerSearchNavigation);
|
||||
|
||||
private readonly subscriptions = new Subscription();
|
||||
showRecommendations: boolean;
|
||||
|
||||
@@ -100,29 +103,12 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
switchMap((processId) => this.applicationService.getSelectedBranch$(processId))
|
||||
);
|
||||
|
||||
stockTooltipText$ = combineLatest([this.store.branch$, this.selectedBranchId$]).pipe(
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
if (defaultBranch?.branchType === 4) {
|
||||
if (!selectedBranch) {
|
||||
return 'Wählen Sie eine Filiale aus, um den Bestand zu sehen.';
|
||||
}
|
||||
return 'Sie sehen den Bestand einer anderen Filiale.';
|
||||
} else {
|
||||
if (selectedBranch && defaultBranch.id !== selectedBranch?.id) {
|
||||
return 'Sie sehen den Bestand einer anderen Filiale.';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
get isTablet$() {
|
||||
return this._environment.matchTablet$;
|
||||
}
|
||||
|
||||
get resultsPath() {
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId);
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId).path;
|
||||
}
|
||||
|
||||
showMore: boolean = false;
|
||||
@@ -134,6 +120,58 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
return this.detailsContainer?.nativeElement;
|
||||
}
|
||||
|
||||
stockTooltipText$ = combineLatest([this.store.defaultBranch$, this.selectedBranchId$]).pipe(
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
if (defaultBranch?.branchType !== 4 && selectedBranch && defaultBranch.id !== selectedBranch?.id) {
|
||||
return 'Sie sehen den Bestand einer anderen Filiale.';
|
||||
}
|
||||
return '';
|
||||
})
|
||||
);
|
||||
|
||||
priceMaintained$ = combineLatest([
|
||||
this.store.takeAwayAvailability$,
|
||||
this.store.deliveryAvailability$,
|
||||
this.store.deliveryDigAvailability$,
|
||||
this.store.deliveryB2BAvailability$,
|
||||
]).pipe(
|
||||
map((availabilities) => {
|
||||
return availabilities?.some((availability) => availability?.priceMaintained) ?? false;
|
||||
})
|
||||
);
|
||||
|
||||
price$ = combineLatest([
|
||||
this.store.item$,
|
||||
this.store.takeAwayAvailability$,
|
||||
this.store.deliveryAvailability$,
|
||||
this.store.deliveryDigAvailability$,
|
||||
this.store.deliveryB2BAvailability$,
|
||||
]).pipe(
|
||||
map(([item, takeAway, delivery, deliveryDig, deliveryB2B]) => {
|
||||
if (item?.catalogAvailability?.price?.value?.value) {
|
||||
return item?.catalogAvailability?.price;
|
||||
}
|
||||
|
||||
if (takeAway?.price?.value?.value) {
|
||||
return takeAway.price;
|
||||
}
|
||||
|
||||
if (delivery?.price?.value?.value) {
|
||||
return delivery.price;
|
||||
}
|
||||
|
||||
if (deliveryDig?.price?.value?.value) {
|
||||
return deliveryDig.price;
|
||||
}
|
||||
|
||||
if (deliveryB2B?.price?.value?.value) {
|
||||
return deliveryB2B.price;
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
constructor(
|
||||
public readonly applicationService: ApplicationService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
@@ -149,7 +187,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
private _checkoutNavigationService: CheckoutNavigationService,
|
||||
private _environment: EnvironmentService,
|
||||
private _router: Router,
|
||||
private _domainCheckoutService: DomainCheckoutService
|
||||
private _domainCheckoutService: DomainCheckoutService,
|
||||
private _store: Store
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -208,7 +247,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getDetailsPath(ean?: string) {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, ean });
|
||||
return this._navigationService.getArticleDetailsPathByEan({ processId: this.applicationService.activatedProcessId, ean }).path;
|
||||
}
|
||||
|
||||
async updateBreadcrumb(item: ItemDTO) {
|
||||
@@ -224,13 +263,21 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: item.product?.name,
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }).path,
|
||||
params: this.activatedRoute.snapshot.queryParams,
|
||||
tags: ['catalog', 'details', `${item.id}`],
|
||||
section: 'customer',
|
||||
});
|
||||
}
|
||||
|
||||
async showTooltip() {
|
||||
const text = await this.stockTooltipText$.pipe(first()).toPromise();
|
||||
if (!text) {
|
||||
// Show Tooltip attached to branch selector dropdown
|
||||
this._store.dispatch({ type: 'OPEN_TOOLTIP_NO_BRANCH_SELECTED' });
|
||||
}
|
||||
}
|
||||
|
||||
async updateBreadcrumbDesktop(item: ItemDTO) {
|
||||
const crumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details'])
|
||||
@@ -241,7 +288,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: item.product?.name,
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id })
|
||||
.path,
|
||||
params: this.activatedRoute.snapshot.queryParams,
|
||||
tags: ['catalog', 'details', `${item.id}`],
|
||||
section: 'customer',
|
||||
@@ -251,7 +299,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.breadcrumb.patchBreadcrumb(crumb.id, {
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: item.product?.name,
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id })
|
||||
.path,
|
||||
params: this.activatedRoute.snapshot.queryParams,
|
||||
tags: ['catalog', 'details', `${item.id}`],
|
||||
section: 'customer',
|
||||
@@ -360,10 +409,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async navigateToShoppingCart() {
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: this.applicationService.activatedProcessId });
|
||||
await this._checkoutNavigationService.getCheckoutReviewPath(this.applicationService.activatedProcessId).navigate();
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch() {
|
||||
const nav = this._customerSearchNavigation.defaultRoute({ processId: this.applicationService.activatedProcessId });
|
||||
try {
|
||||
const response = await this.customerFeatures$
|
||||
.pipe(
|
||||
@@ -373,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,9 +441,9 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
crumbs = crumbs.filter((crumb) => !crumb.tags?.includes('details'));
|
||||
const crumb = crumbs[crumbs.length - 1];
|
||||
if (!!crumb) {
|
||||
this._router.navigate(this._navigationService.getArticleSearchResultsPath(processId), { queryParams: crumb.params });
|
||||
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: crumb.params }).navigate();
|
||||
} else {
|
||||
this._navigationService.navigateToProductSearch({ processId });
|
||||
await this._navigationService.getArticleSearchBasePath(processId).navigate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { UiTooltipModule } from '@ui/tooltip';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { ArticleDetailsTextComponent } from './article-details-text/article-details-text.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -26,6 +27,7 @@ import { IconModule } from '@shared/components/icon';
|
||||
IconModule,
|
||||
PipesModule,
|
||||
OrderDeadlinePipeModule,
|
||||
ArticleDetailsTextComponent,
|
||||
],
|
||||
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||
|
||||
@@ -61,8 +61,12 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
|
||||
return this.get((s) => s.branch);
|
||||
}
|
||||
|
||||
get defaultBranch$() {
|
||||
return this.domainAvailabilityService.getDefaultBranch();
|
||||
}
|
||||
|
||||
readonly branch$ = this.select((s) => s.branch).pipe(
|
||||
withLatestFrom(this.domainAvailabilityService.getDefaultBranch()),
|
||||
withLatestFrom(this.defaultBranch$),
|
||||
map(([selectedBranch, defaultBranch]) => selectedBranch ?? defaultBranch)
|
||||
);
|
||||
|
||||
@@ -267,7 +271,8 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
|
||||
this.deliveryB2BAvailability$,
|
||||
this.downloadAvailability$,
|
||||
]).pipe(
|
||||
map(([item, isDownload, pickupAvailability, deliveryDigAvailability, deliveryB2BAvailability, downloadAvailability]) => {
|
||||
withLatestFrom(this.domainAvailabilityService.sscs$),
|
||||
map(([[item, isDownload, pickupAvailability, deliveryDigAvailability, deliveryB2BAvailability, downloadAvailability], sscs]) => {
|
||||
let availability: AvailabilityDTO;
|
||||
|
||||
if (isDownload) {
|
||||
@@ -282,15 +287,30 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
|
||||
}
|
||||
}
|
||||
|
||||
let ssc = '';
|
||||
let sscText = 'Keine Lieferanten vorhanden';
|
||||
|
||||
if (item?.catalogAvailability?.supplier === 'S' && !isDownload) {
|
||||
return [item?.catalogAvailability?.ssc, item?.catalogAvailability?.sscText].filter((f) => !!f).join(' - ');
|
||||
ssc = item?.catalogAvailability?.ssc;
|
||||
sscText = item?.catalogAvailability?.sscText;
|
||||
|
||||
return [ssc, sscText].filter((f) => !!f).join(' - ');
|
||||
}
|
||||
|
||||
if (availability?.ssc || availability?.sscText) {
|
||||
return [availability?.ssc, availability?.sscText].filter((f) => !!f).join(' - ');
|
||||
ssc = availability?.ssc;
|
||||
sscText = availability?.sscText;
|
||||
|
||||
const sscExists = !!sscs?.find((ssc) => !!item?.id && ssc?.itemId === item.id);
|
||||
const sscEqualsCatalogSsc = ssc === item.catalogAvailability.ssc && sscText === item.catalogAvailability.sscText;
|
||||
|
||||
// To keep result list in sync with details page
|
||||
if (!sscExists && !sscEqualsCatalogSsc) {
|
||||
this.domainAvailabilityService.sscs$.next([...sscs, { itemId: item?.id, ssc, sscText }]);
|
||||
}
|
||||
}
|
||||
|
||||
return 'Keine Lieferanten vorhanden';
|
||||
return [ssc, sscText].filter((f) => !!f).join(' - ');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -19,6 +19,6 @@ export class ArticleRecommendationsComponent {
|
||||
) {}
|
||||
|
||||
getDetailsPath(ean?: string) {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this._applicationService.activatedProcessId, ean });
|
||||
return this._navigationService.getArticleDetailsPathByEan({ processId: this._applicationService.activatedProcessId, ean }).path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,27 +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(([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);
|
||||
this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
} else {
|
||||
this._navigationService.navigateToResults({
|
||||
processId,
|
||||
queryParams: params,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
@@ -109,7 +82,7 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
await this._breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name: 'Artikelsuche',
|
||||
path: this._navigationService.getArticleSearchBasePath(processId),
|
||||
path: this._navigationService.getArticleSearchBasePath(processId).path,
|
||||
params: queryParams,
|
||||
tags: ['catalog', 'main'],
|
||||
section: 'customer',
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1,38 +1,44 @@
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<div class="catalog-search-filter-content">
|
||||
<div class="w-full flex flex-row justify-end items-center">
|
||||
<button (click)="clearFilter(filter)" class="text-[#0556B4] p-4">Alle Filter entfernen</button>
|
||||
<a
|
||||
*ngIf="showFilterClose$ | async"
|
||||
class="text-black p-4 outline-none border-none bg-transparent"
|
||||
[routerLink]="closeFilterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon icon="close" [size]="25"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden desktop-large:block" [class.show-filter]="showFilter">
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<div class="catalog-search-filter-content">
|
||||
<div class="w-full flex flex-row justify-end items-center">
|
||||
<button (click)="clearFilter(filter)" class="text-[#0556B4] p-4">Alle Filter entfernen</button>
|
||||
<a
|
||||
*ngIf="showFilterClose$ | async"
|
||||
class="text-black p-4 outline-none border-none bg-transparent"
|
||||
[routerLink]="closeFilterRoute"
|
||||
(click)="showFilter = false"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon icon="close" [size]="25"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="catalog-search-filter-content-main -mt-14 desktop-small:-mt-8 desktop-large:-mt-12">
|
||||
<h1 class="text-h3 text-[1.625rem] font-bold text-center pt-6 pb-10">Filter</h1>
|
||||
<shared-filter
|
||||
[filter]="filter"
|
||||
[loading]="fetching$ | async"
|
||||
(search)="applyFilter(filter)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
[scanner]="true"
|
||||
></shared-filter>
|
||||
</div>
|
||||
<div class="catalog-search-filter-content-main -mt-14 desktop-small:-mt-8 desktop-large:-mt-12">
|
||||
<h1 class="text-h3 text-[1.625rem] font-bold text-center pt-6 pb-10">Filter</h1>
|
||||
<shared-filter
|
||||
[filter]="filter"
|
||||
[loading]="fetching$ | async"
|
||||
(search)="applyFilter(filter)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
[scanner]="true"
|
||||
></shared-filter>
|
||||
</div>
|
||||
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="(fetching$ | async) || !hasSelectedOptions(filter)">
|
||||
<ui-spinner [show]="fetching$ | async">
|
||||
Filter anwenden
|
||||
</ui-spinner>
|
||||
</button>
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="(fetching$ | async) || !hasSelectedOptions(filter)">
|
||||
<ui-spinner [show]="fetching$ | async">
|
||||
Filter anwenden
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="desktop-large:hidden" [class.hidden]="showFilter">
|
||||
<page-article-search-main (showFilter)="showFilter = true"></page-article-search-main>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
@apply block bg-white h-split-screen-tablet desktop-small:h-split-screen-desktop;
|
||||
}
|
||||
|
||||
.show-filter {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
.catalog-search-filter-content {
|
||||
@apply relative mx-auto p-4;
|
||||
}
|
||||
@@ -13,7 +17,7 @@
|
||||
}
|
||||
|
||||
.cta-wrapper {
|
||||
@apply text-center whitespace-nowrap absolute bottom-8 left-0 w-full;
|
||||
@apply text-center whitespace-nowrap absolute bottom-8 left-1/2 -translate-x-1/2;
|
||||
}
|
||||
|
||||
.cta-reset-filter,
|
||||
|
||||
@@ -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',
|
||||
@@ -26,36 +26,35 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(FilterComponent, { static: false })
|
||||
uiFilterComponent: FilterComponent;
|
||||
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
showFilter: boolean = false;
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get showFilterClose$() {
|
||||
return this._environment.matchDesktopLarge$.pipe(map((matches) => !(matches && this.leftOutlet === 'search')));
|
||||
return this._environment.matchDesktopLarge$.pipe(map((matches) => !(matches && this.sideOutlet === 'search')));
|
||||
}
|
||||
|
||||
get leftOutlet() {
|
||||
return this._navigationService.getOutletLocations(this._activatedRoute)?.left;
|
||||
get sideOutlet() {
|
||||
return this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'side')?.snapshot?.routeConfig?.path;
|
||||
}
|
||||
|
||||
get mainOutlet() {
|
||||
return this._navigationService.getOutletLocations(this._activatedRoute)?.main;
|
||||
get primaryOutlet() {
|
||||
return this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'primary')?.snapshot?.routeConfig?.path;
|
||||
}
|
||||
|
||||
get closeFilterRoute() {
|
||||
const processId = Number(this._activatedRoute?.parent?.snapshot?.data?.processId);
|
||||
const itemId = this._navigationService.getOutletParams(this._activatedRoute)?.right?.id;
|
||||
const itemId = this._activatedRoute.snapshot.params.id;
|
||||
if (!itemId) {
|
||||
if (this.leftOutlet === 'search') {
|
||||
return this._navigationService.getArticleSearchBasePath(processId);
|
||||
} else if (this.mainOutlet === 'results' || this.leftOutlet === 'results') {
|
||||
return this._navigationService.getArticleSearchResultsPath(processId);
|
||||
if (this.sideOutlet === 'search') {
|
||||
return this._navigationService.getArticleSearchBasePath(processId).path;
|
||||
} else if (this.primaryOutlet === 'results' || this.sideOutlet === 'results') {
|
||||
return this._navigationService.getArticleSearchResultsPath(processId).path;
|
||||
}
|
||||
} else {
|
||||
return this._navigationService.getArticleDetailsPath({ processId, itemId });
|
||||
return this._navigationService.getArticleDetailsPath({ processId, itemId }).path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,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));
|
||||
@@ -88,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 {
|
||||
@@ -100,21 +120,20 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
this.articleSearch.search({ clear: true });
|
||||
this.articleSearch.searchCompleted
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
.subscribe(([searchCompleted, 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);
|
||||
this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
});
|
||||
} else if (!this.isDesktop) {
|
||||
const params = searchCompleted.state.filter.getQueryParams();
|
||||
this._navigationService.navigateToResults({
|
||||
processId,
|
||||
queryParams: params,
|
||||
});
|
||||
await this._navigationService
|
||||
.getArticleDetailsPath({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
extras: { queryParams: params },
|
||||
})
|
||||
.navigate();
|
||||
} else {
|
||||
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,9 +5,10 @@ import { UiSpinnerModule } from '@ui/spinner';
|
||||
import { ArticleSearchFilterComponent } from './search-filter.component';
|
||||
import { FilterModule } from '@shared/components/filter';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { SearchMainModule } from '../search-main/search-main.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule, FilterModule, UiSpinnerModule, IconComponent],
|
||||
imports: [CommonModule, SearchMainModule, RouterModule, FilterModule, UiSpinnerModule, IconComponent],
|
||||
exports: [ArticleSearchFilterComponent],
|
||||
declarations: [ArticleSearchFilterComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -19,16 +19,16 @@
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
<a
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="!(isDesktop$ | async)"
|
||||
(click)="showFilter.emit()"
|
||||
class="page-search-main__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
[routerLink]="openFilterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-start ml-12 desktop-large:ml-8 py-6 bg-white overflow-hidden h-[calc(100%-13.5rem)]">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
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, shareReplay } 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';
|
||||
@@ -18,7 +18,10 @@ import { ProductCatalogNavigationService } from '@shared/services';
|
||||
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$;
|
||||
|
||||
@@ -35,6 +38,8 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
@Output() showFilter = new EventEmitter<void>();
|
||||
|
||||
@ViewChild(FilterInputGroupMainComponent, { static: false })
|
||||
sharedFilterInputGroupMain: FilterInputGroupMainComponent;
|
||||
|
||||
@@ -42,22 +47,6 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
return this._environment.matchDesktopLarge$;
|
||||
}
|
||||
|
||||
get leftOutlet() {
|
||||
return this._navigationService.getOutletLocations(this.route.parent)?.left;
|
||||
}
|
||||
|
||||
get openFilterRoute() {
|
||||
if (this.leftOutlet === 'search') {
|
||||
return this._navigationService.getArticleSearchBaseAndFilterPath(this.application.activatedProcessId);
|
||||
} else {
|
||||
const itemId = this._navigationService?.getOutletParams(this.route)?.right?.id;
|
||||
return this._navigationService.getArticleSearchResultsAndFilterPath({
|
||||
processId: this.application.activatedProcessId,
|
||||
itemId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private searchService: ArticleSearchService,
|
||||
private catalog: DomainCatalogService,
|
||||
@@ -93,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);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -176,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();
|
||||
|
||||
@@ -26,13 +26,13 @@ export class AddedToCartModalComponent {
|
||||
) {}
|
||||
async continue() {
|
||||
if (this.isTablet) {
|
||||
await this._navigation.navigateToProductSearch({ processId: this._applicationService.activatedProcessId });
|
||||
await this._navigation.getArticleSearchBasePath(this._applicationService.activatedProcessId).navigate();
|
||||
}
|
||||
this.ref.close();
|
||||
}
|
||||
|
||||
async toCart() {
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: this._applicationService.activatedProcessId });
|
||||
await this._checkoutNavigationService.getCheckoutReviewPath(this._applicationService.activatedProcessId).navigate();
|
||||
this.ref.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ng-container *ngIf="!mainOutletActive; else mainOutlet">
|
||||
<ng-container *ngIf="!primaryOutletActive; else primaryOutlet">
|
||||
<div class="bg-ucla-blue rounded w-[4.375rem] h-[5.625rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-col flex-grow">
|
||||
<div class="h-4 bg-ucla-blue ml-4 mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #mainOutlet>
|
||||
<ng-template #primaryOutlet>
|
||||
<div class="bg-ucla-blue rounded w-[3rem] h-[4.125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-col ml-4 w-[36.6%]">
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[8.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
|
||||
@@ -8,11 +8,11 @@ import { Component, ChangeDetectionStrategy, HostBinding, Input } from '@angular
|
||||
})
|
||||
export class SearchResultItemLoadingComponent {
|
||||
@Input()
|
||||
mainOutletActive?: boolean = false;
|
||||
primaryOutletActive?: boolean = false;
|
||||
|
||||
constructor() {}
|
||||
|
||||
@HostBinding('style') get class() {
|
||||
return this.mainOutletActive ? { height: '6.125rem' } : '';
|
||||
return this.primaryOutletActive ? { height: '6.125rem' } : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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-main]="mainOutletActive"
|
||||
[routerLink]="detailsPath"
|
||||
[routerLinkActive]="!isTablet && !mainOutletActive ? 'active' : ''"
|
||||
[queryParamsHandling]="!isTablet ? 'preserve' : ''"
|
||||
(click)="isDesktop ? scrollIntoView() : ''"
|
||||
[class.page-search-result-item__item-card-primary]="primaryOutletActive"
|
||||
[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"
|
||||
@@ -16,7 +13,10 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="page-search-result-item__item-grid-container" [class.page-search-result-item__item-grid-container-main]="mainOutletActive">
|
||||
<div
|
||||
class="page-search-result-item__item-grid-container"
|
||||
[class.page-search-result-item__item-grid-container-primary]="primaryOutletActive"
|
||||
>
|
||||
<div
|
||||
class="page-search-result-item__item-contributors desktop-small:text-p3 font-bold text-[#0556B4] text-ellipsis overflow-hidden max-w-[24rem] whitespace-nowrap"
|
||||
>
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-price desktop-small:text-p3 font-bold justify-self-end"
|
||||
[class.page-search-result-item__item-price-main]="mainOutletActive"
|
||||
[class.page-search-result-item__item-price-primary]="primaryOutletActive"
|
||||
>
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
||||
</div>
|
||||
@@ -81,44 +81,42 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-stock desktop-small:text-p3 font-bold z-dropdown justify-self-start"
|
||||
[class.justify-self-end]="!mainOutletActive"
|
||||
<button
|
||||
class="page-search-result-item__item-stock desktop-small:text-p3 font-bold z-dropdown justify-self-start flex flex-row items-center justify-center"
|
||||
[class.justify-self-end]="!primaryOutletActive"
|
||||
[uiOverlayTrigger]="tooltip"
|
||||
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
|
||||
type="button"
|
||||
(click)="$event.stopPropagation(); $event.preventDefault(); showTooltip()"
|
||||
>
|
||||
<ui-icon class="mr-[0.125rem] -mt-[0.275rem]" icon="home" size="1rem"></ui-icon>
|
||||
<ng-container *ngIf="isOrderBranch$ | async">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<ui-icon class="-mt-[0.1875rem]" icon="home" size="1em"></ui-icon>
|
||||
<span
|
||||
*ngIf="inStock$ | async; let stock"
|
||||
[class.skeleton]="stock.inStock === undefined"
|
||||
class="min-w-[1rem] text-right inline-block"
|
||||
>{{ stock?.inStock }}</span
|
||||
>
|
||||
<span>x</span>
|
||||
</div>
|
||||
<span
|
||||
*ngIf="inStock$ | async; let stock"
|
||||
[class.skeleton]="stock.inStock === undefined"
|
||||
class="min-w-[0.75rem] text-right inline-block"
|
||||
>{{ stock?.inStock }}</span
|
||||
>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!(isOrderBranch$ | async)">
|
||||
<div class="flex flex-row items-center justify-between z-dropdown">
|
||||
<ui-icon class="block" icon="home" size="1em"></ui-icon>
|
||||
<span class="min-w-[1rem] text-center inline-block">-</span>
|
||||
<span>x</span>
|
||||
</div>
|
||||
<span class="min-w-[1rem] text-center inline-block">-</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||
<span>x</span>
|
||||
</button>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-12" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-ssc desktop-small:text-p3 w-full text-right overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
[class.page-search-result-item__item-ssc-main]="mainOutletActive"
|
||||
[class.page-search-result-item__item-ssc-primary]="primaryOutletActive"
|
||||
>
|
||||
<div class="hidden" [class.page-search-result-item__item-ssc-tooltip]="mainOutletActive">
|
||||
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
|
||||
</div>
|
||||
<strong>{{ item?.catalogAvailability?.ssc }}</strong> - {{ item?.catalogAvailability?.sscText }}
|
||||
<ng-container *ngIf="ssc$ | async; let ssc">
|
||||
<div class="hidden" [class.page-search-result-item__item-ssc-tooltip]="primaryOutletActive">
|
||||
{{ ssc?.ssc }} - {{ ssc?.sscText }}
|
||||
</div>
|
||||
<strong>{{ ssc?.ssc }}</strong> - {{ ssc?.sscText }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
'misc ssc ssc';
|
||||
}
|
||||
|
||||
.page-search-result-item__item-grid-container-main {
|
||||
.page-search-result-item__item-grid-container-primary {
|
||||
@apply gap-x-4;
|
||||
grid-template-rows: 1.3125rem 1.3125rem auto;
|
||||
grid-template-columns: 37.5% 32.5% 20% auto;
|
||||
@@ -38,7 +38,7 @@
|
||||
grid-area: price;
|
||||
}
|
||||
|
||||
.page-search-result-item__item-price-main {
|
||||
.page-search-result-item__item-price-primary {
|
||||
@apply justify-self-start;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.page-search-result-item__item-ssc-main {
|
||||
.page-search-result-item__item-ssc-primary {
|
||||
@apply text-left overflow-hidden text-ellipsis whitespace-nowrap;
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -8,9 +8,10 @@ import { ItemDTO } from '@swagger/cat';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { debounceTime, switchMap, map, shareReplay, filter } from 'rxjs/operators';
|
||||
import { debounceTime, switchMap, map, filter, first } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
export interface SearchResultItemComponentState {
|
||||
item?: ItemDTO;
|
||||
@@ -51,7 +52,9 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
}
|
||||
|
||||
@Input()
|
||||
mainOutletActive?: boolean = false;
|
||||
primaryOutletActive?: boolean = false;
|
||||
|
||||
@Input() isActive: boolean;
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<ItemDTO>();
|
||||
@@ -77,16 +80,12 @@ 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 });
|
||||
get isDesktopLarge() {
|
||||
return this._environment.matchDesktopLarge();
|
||||
}
|
||||
|
||||
get resultsPath() {
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId);
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId).path;
|
||||
}
|
||||
|
||||
defaultBranch$ = this._availability.getDefaultBranch();
|
||||
@@ -104,15 +103,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
|
||||
stockTooltipText$ = combineLatest([this.defaultBranch$, this.selectedBranchId$]).pipe(
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
if (defaultBranch?.branchType === 4) {
|
||||
if (!selectedBranch) {
|
||||
return 'Wählen Sie eine Filiale aus, um den Bestand zu sehen.';
|
||||
}
|
||||
if (defaultBranch?.branchType !== 4 && selectedBranch && defaultBranch.id !== selectedBranch?.id) {
|
||||
return 'Sie sehen den Bestand einer anderen Filiale.';
|
||||
} else {
|
||||
if (selectedBranch && defaultBranch.id !== selectedBranch?.id) {
|
||||
return 'Sie sehen den Bestand einer anderen Filiale.';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
})
|
||||
@@ -126,6 +118,17 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
)
|
||||
);
|
||||
|
||||
ssc$ = this._availability.sscsObs$.pipe(
|
||||
debounceTime(100),
|
||||
map((sscs) => {
|
||||
const updatedSsc = sscs?.find((ssc) => this.item?.id === ssc?.itemId);
|
||||
return {
|
||||
ssc: updatedSsc?.ssc ?? this.item?.catalogAvailability?.ssc,
|
||||
sscText: updatedSsc?.sscText ?? this.item?.catalogAvailability?.sscText,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
constructor(
|
||||
private _dateAdapter: DateAdapter,
|
||||
private _datePipe: DatePipe,
|
||||
@@ -135,7 +138,7 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _elRef: ElementRef<HTMLElement>
|
||||
private _store: Store
|
||||
) {
|
||||
super({
|
||||
selected: false,
|
||||
@@ -143,21 +146,20 @@ 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() {
|
||||
const text = await this.stockTooltipText$.pipe(first()).toPromise();
|
||||
if (!text) {
|
||||
// Show Tooltip attached to branch selector dropdown
|
||||
this._store.dispatch({ type: 'OPEN_TOOLTIP_NO_BRANCH_SELECTED' });
|
||||
}
|
||||
}
|
||||
|
||||
@HostBinding('style') get class() {
|
||||
return this.mainOutletActive ? { height: '6.125rem' } : '';
|
||||
return this.primaryOutletActive ? { height: '6.125rem' } : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div
|
||||
class="page-search-results__header bg-background-liste flex items-end justify-between"
|
||||
[class.pb-4]="!(mainOutletActive$ | async)"
|
||||
[class.flex-col]="!(mainOutletActive$ | async)"
|
||||
[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]="!(mainOutletActive$ | 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.desktop-large:w-full]="!(mainOutletActive$ | async)"
|
||||
class="block mr-3 w-full desktop:w-[23.5rem]"
|
||||
[class.desktop-large:w-full]="!(primaryOutletActive$ | async)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
@@ -30,7 +30,7 @@
|
||||
<div
|
||||
*ngIf="hits$ | async; let hits"
|
||||
class="page-search-results__items-count inline-flex flex-row items-center pr-5 text-p3"
|
||||
[class.mb-4]="mainOutletActive$ | async"
|
||||
[class.mb-4]="primaryOutletActive$ | async"
|
||||
>
|
||||
{{ hits ??
|
||||
0 }}
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-search-results__order-by" [class.page-search-results__order-by-main]="mainOutletActive$ | 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]="(mainOutletActive$ | 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-main]="mainOutletActive$ | async"
|
||||
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selected]="isSelected(item)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[mainOutletActive]="mainOutletActive$ | async"
|
||||
></search-result-item>
|
||||
<page-search-result-item-loading
|
||||
[mainOutletActive]="mainOutletActive$ | 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-main {
|
||||
@apply mb-[5px];
|
||||
.page-search-results__result-item-primary {
|
||||
@apply mb-[0.3125rem];
|
||||
}
|
||||
|
||||
.page-search-results__order-by {
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-search-results .page-search-results__order-by-main shared-order-by-filter {
|
||||
::ng-deep page-search-results .page-search-results__order-by-primary shared-order-by-filter {
|
||||
@apply grid grid-flow-col justify-items-start gap-x-4 justify-start;
|
||||
|
||||
.order-by-filter-button {
|
||||
|
||||
@@ -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(
|
||||
@@ -81,39 +88,26 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
);
|
||||
|
||||
get filterRoute() {
|
||||
const itemId = this._navigationService?.getOutletParams(this.route)?.right?.id;
|
||||
const itemId = this.route?.snapshot?.params?.id;
|
||||
return this._navigationService.getArticleSearchResultsAndFilterPath({
|
||||
processId: this.application.activatedProcessId,
|
||||
itemId,
|
||||
});
|
||||
}).path;
|
||||
}
|
||||
|
||||
get mainOutletActive$() {
|
||||
return this._environment.matchTablet$.pipe(map((matches) => this._navigationService.mainOutletActive(this.route, matches)));
|
||||
get primaryOutletActive$() {
|
||||
return this._environment.matchDesktop$.pipe(map((matches) => matches && this.route.outlet === 'primary'));
|
||||
}
|
||||
|
||||
get rightOutletLocation() {
|
||||
return this._navigationService.getOutletLocations(this.route).right;
|
||||
}
|
||||
private readonly SCROLL_INDEX_TOKEN = 'CATALOG_RESULTS_LIST_SCROLL_INDEX';
|
||||
|
||||
// Ticket #4169 Splitscreen
|
||||
// Render genug Artikel um bei Navigation auf Trefferliste | PDP zum angewählten Artikel zu Scrollen
|
||||
maxBufferCdkScrollContainer$ = this.results$.pipe(
|
||||
withLatestFrom(this.mainOutletActive$),
|
||||
map(([results, mainOutlet]) => {
|
||||
if (!mainOutlet && results?.length > 0) {
|
||||
// Splitscreen mode: Items Length * Item Pixel Height
|
||||
const maxBufferSize = results.length * 181;
|
||||
return maxBufferSize >= 1200 ? maxBufferSize : 1200;
|
||||
} else {
|
||||
return 1200;
|
||||
}
|
||||
})
|
||||
);
|
||||
shellService = inject(ShellService);
|
||||
|
||||
scale$ = this.shellService.scale$;
|
||||
|
||||
constructor(
|
||||
public searchService: ArticleSearchService,
|
||||
private route: ActivatedRoute,
|
||||
public route: ActivatedRoute,
|
||||
public application: ApplicationService,
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private cache: CacheService,
|
||||
@@ -160,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._navigationService.mainOutletActive(this.route)) {
|
||||
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()))) {
|
||||
@@ -172,14 +165,12 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
this.searchService.setItems(data.items);
|
||||
this.searchService.setHits(data.hits);
|
||||
}
|
||||
if (data.items?.length === 0 && this.rightOutletLocation !== 'filter') {
|
||||
if (
|
||||
data.items?.length === 0 &&
|
||||
this.route?.parent?.children?.find((childRoute) => childRoute?.outlet === 'side')?.snapshot?.routeConfig?.path !== 'filter'
|
||||
) {
|
||||
this.search({ clear: true });
|
||||
} else {
|
||||
if (!this.isDesktop || this._navigationService.mainOutletActive(this.route)) {
|
||||
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) {
|
||||
@@ -195,7 +186,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
await this.createBreadcrumb(processId, queryParams);
|
||||
}
|
||||
|
||||
if (!this.isDesktop || this.route?.outlet === 'main') {
|
||||
if (this.route?.outlet === 'primary') {
|
||||
await this.removeDetailsBreadcrumb(processId);
|
||||
}
|
||||
})
|
||||
@@ -221,23 +212,36 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
// Navigation from Cart uses ean
|
||||
if (!!ean) {
|
||||
await this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
ean,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
await this._navigationService
|
||||
.getArticleDetailsPathByEan({
|
||||
processId,
|
||||
ean,
|
||||
extras: { queryParams: params },
|
||||
})
|
||||
.navigate();
|
||||
} else {
|
||||
await this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
itemId,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
await this._navigationService
|
||||
.getArticleDetailsPath({
|
||||
processId,
|
||||
itemId,
|
||||
extras: { 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();
|
||||
}
|
||||
} else if (searchCompleted?.clear || this.isTablet || this._navigationService.mainOutletActive(this.route)) {
|
||||
await this._navigationService.navigateToResults({
|
||||
processId,
|
||||
queryParams: params,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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._navigationService.mainOutletActive(this.route)) {
|
||||
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, {
|
||||
@@ -352,7 +365,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
await this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name,
|
||||
path: this._navigationService.getArticleSearchResultsPath(processId),
|
||||
path: this._navigationService.getArticleSearchResultsPath(processId, { queryParams }).path,
|
||||
params: queryParams,
|
||||
section: 'customer',
|
||||
tags: ['catalog', 'filter', 'results'],
|
||||
@@ -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,
|
||||
|
||||
@@ -7,68 +7,23 @@ import { ArticleSearchMainComponent } from './article-search/search-main/search-
|
||||
import { ArticleSearchResultsComponent } from './article-search/search-results/search-results.component';
|
||||
import { PageCatalogComponent } from './page-catalog.component';
|
||||
|
||||
const auxiliaryRoutes = [
|
||||
{
|
||||
path: 'search',
|
||||
component: ArticleSearchComponent,
|
||||
outlet: 'left',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: ArticleSearchMainComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'filter',
|
||||
component: ArticleSearchFilterComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'filter/:id',
|
||||
component: ArticleSearchFilterComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'results',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'left',
|
||||
},
|
||||
{
|
||||
path: 'results',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'main',
|
||||
},
|
||||
{
|
||||
path: 'results/:id',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'left',
|
||||
},
|
||||
{
|
||||
path: 'results/ean/:ean',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'left',
|
||||
},
|
||||
{
|
||||
path: 'details/ean/:ean',
|
||||
component: ArticleDetailsComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'details/:id',
|
||||
component: ArticleDetailsComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: PageCatalogComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'filter',
|
||||
component: ArticleSearchFilterComponent,
|
||||
},
|
||||
{
|
||||
path: 'filter/:id',
|
||||
component: ArticleSearchFilterComponent,
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: ArticleSearchComponent,
|
||||
outlet: 'side',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -81,22 +36,32 @@ const routes: Routes = [
|
||||
component: ArticleSearchResultsComponent,
|
||||
},
|
||||
{
|
||||
path: 'filter',
|
||||
component: ArticleSearchFilterComponent,
|
||||
path: 'results',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'side',
|
||||
},
|
||||
{
|
||||
path: 'details/ean/:ean',
|
||||
component: ArticleDetailsComponent,
|
||||
path: 'results/:id',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'side',
|
||||
},
|
||||
{
|
||||
path: 'results/:ean/ean',
|
||||
component: ArticleSearchResultsComponent,
|
||||
outlet: 'side',
|
||||
},
|
||||
{
|
||||
path: 'details/:id',
|
||||
component: ArticleDetailsComponent,
|
||||
},
|
||||
...auxiliaryRoutes,
|
||||
{
|
||||
path: 'details/:ean/ean',
|
||||
component: ArticleDetailsComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'search',
|
||||
redirectTo: 'filter',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<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"
|
||||
[filterCurrentBranch]="!!auth.hasRole('Store')"
|
||||
[orderBy]="auth.hasRole('Store') ? 'distance' : 'name'"
|
||||
[branchType]="1"
|
||||
@@ -7,26 +9,9 @@
|
||||
(valueChange)="patchProcessData($event)"
|
||||
>
|
||||
</shared-branch-selector>
|
||||
<ui-tooltip #tooltip yPosition="below" xPosition="after" [xOffset]="-263" [yOffset]="4" [closeable]="true">
|
||||
{{ stockTooltipText$ | async }}
|
||||
</ui-tooltip>
|
||||
</shared-breadcrumb>
|
||||
|
||||
<ng-container *ngIf="routerEvents$ | async">
|
||||
<ng-container *ngIf="!(isDesktop$ | async); else desktop">
|
||||
<router-outlet></router-outlet>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #desktop>
|
||||
<ng-container *ngIf="showMainOutlet$ | async">
|
||||
<router-outlet name="main"></router-outlet>
|
||||
</ng-container>
|
||||
|
||||
<div class="grid grid-cols-split-screen gap-split-screen">
|
||||
<div *ngIf="!(showMainOutlet$ | async)" class="block">
|
||||
<router-outlet name="left"></router-outlet>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!(showMainOutlet$ | async)">
|
||||
<router-outlet name="right"></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<shared-splitscreen></shared-splitscreen>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { UiOverlayTriggerDirective } from '@ui/common';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { fromEvent, Observable, Subject } from 'rxjs';
|
||||
import { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { combineLatest, fromEvent, Observable, Subject } from 'rxjs';
|
||||
import { first, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { ActionsSubject } from '@ngrx/store';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
|
||||
@Component({
|
||||
selector: 'page-catalog',
|
||||
@@ -23,6 +25,8 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
activatedProcessId$: Observable<string>;
|
||||
selectedBranch$: Observable<BranchDTO>;
|
||||
|
||||
@ViewChild(UiOverlayTriggerDirective) branchInputNoBranchSelectedTrigger: UiOverlayTriggerDirective;
|
||||
|
||||
get branchSelectorWidth() {
|
||||
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
|
||||
}
|
||||
@@ -37,35 +41,50 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return this._environmentService.matchDesktopLarge$;
|
||||
}
|
||||
|
||||
routerEvents$ = this._router.events.pipe(shareReplay());
|
||||
defaultBranch$ = this._availability.getDefaultBranch();
|
||||
|
||||
showMainOutlet$ = this.routerEvents$.pipe(
|
||||
map((_) => {
|
||||
const primary = this._activatedRoute?.children?.find(
|
||||
(child) => child?.outlet === 'primary' && child?.routeConfig?.path === 'results'
|
||||
);
|
||||
const main = this._activatedRoute?.children?.find((child) => child?.outlet === 'main' && child?.routeConfig?.path === 'results');
|
||||
return !!primary && !!main;
|
||||
})
|
||||
);
|
||||
stockTooltipText$: Observable<string>;
|
||||
|
||||
stockTooltipDisabled$: Observable<boolean>;
|
||||
|
||||
get stockTooltipDisabled() {
|
||||
return this.branchInputNoBranchSelectedTrigger?.opened ? false : true;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public application: ApplicationService,
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _uiModal: UiModalService,
|
||||
public auth: AuthService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _renderer: Renderer2,
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _router: Router
|
||||
private _actions: ActionsSubject
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||
|
||||
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
|
||||
|
||||
this.stockTooltipText$ = combineLatest([this.defaultBranch$, this.selectedBranch$]).pipe(
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
if (defaultBranch?.branchType === 4 && !selectedBranch) {
|
||||
return 'Bitte wählen Sie eine Filiale aus, um den Bestand zu sehen.';
|
||||
} else if (defaultBranch?.branchType !== 4 && !selectedBranch) {
|
||||
return 'Bitte wählen Sie eine Filiale aus, um den Bestand einer anderen Filiale zu sehen';
|
||||
}
|
||||
return '';
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this._actions.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.stockTooltipText$)).subscribe(([action, text]) => {
|
||||
if (action.type === 'OPEN_TOOLTIP_NO_BRANCH_SELECTED' && !!text) {
|
||||
this.branchInputNoBranchSelectedTrigger.open();
|
||||
}
|
||||
});
|
||||
|
||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusin')
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.isTablet$))
|
||||
.subscribe(([_, isTablet]) => {
|
||||
|
||||
@@ -6,9 +6,22 @@ import { ArticleDetailsModule } from './article-details/article-details.module';
|
||||
import { ArticleSearchModule } from './article-search/article-search.module';
|
||||
import { PageCatalogRoutingModule } from './page-catalog-routing.module';
|
||||
import { PageCatalogComponent } from './page-catalog.component';
|
||||
import { SharedSplitscreenComponent } from '@shared/components/splitscreen';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, PageCatalogRoutingModule, ArticleSearchModule, ArticleDetailsModule, BreadcrumbModule, BranchSelectorComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
PageCatalogRoutingModule,
|
||||
ArticleSearchModule,
|
||||
ArticleDetailsModule,
|
||||
BreadcrumbModule,
|
||||
BranchSelectorComponent,
|
||||
SharedSplitscreenComponent,
|
||||
UiCommonModule,
|
||||
UiTooltipModule,
|
||||
],
|
||||
exports: [],
|
||||
declarations: [PageCatalogComponent],
|
||||
})
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { TrimPipe } from './trim.pipe';
|
||||
import { VatPipe } from './vat.pipe';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
exports: [TrimPipe],
|
||||
declarations: [TrimPipe],
|
||||
exports: [TrimPipe, VatPipe],
|
||||
declarations: [TrimPipe, VatPipe],
|
||||
providers: [],
|
||||
})
|
||||
export class PipesModule {}
|
||||
|
||||
18
apps/page/catalog/src/lib/shared/pipes/vat.pipe.ts
Normal file
18
apps/page/catalog/src/lib/shared/pipes/vat.pipe.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { VATType } from '@swagger/cat';
|
||||
|
||||
@Pipe({
|
||||
name: 'vat',
|
||||
})
|
||||
export class VatPipe implements PipeTransform {
|
||||
transform(vatType: VATType, priceMaintained?: boolean, ...args: any[]): any {
|
||||
const vatString = vatType === 1 ? '0%' : vatType === 2 ? '19%' : vatType === 8 ? '7%' : undefined;
|
||||
if (!vatString) {
|
||||
return;
|
||||
}
|
||||
if (priceMaintained) {
|
||||
return `inkl. ${vatString} MwSt; Preisgebunden`;
|
||||
}
|
||||
return `inkl. ${vatString} MwSt`;
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
queryParams: { customertype: filter.customertype },
|
||||
});
|
||||
} else {
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: this._applicationService.activatedProcessId });
|
||||
await this._checkoutNavigationService.getCheckoutReviewPath(this._applicationService.activatedProcessId).navigate();
|
||||
}
|
||||
this._ref?.close();
|
||||
});
|
||||
@@ -217,7 +217,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
queryParams: { customertype: filter.customertype },
|
||||
});
|
||||
} else {
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: this._applicationService.activatedProcessId });
|
||||
await this._checkoutNavigationService.getCheckoutReviewPath(this._applicationService.activatedProcessId).navigate();
|
||||
}
|
||||
this._ref?.close();
|
||||
});
|
||||
|
||||
@@ -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]">
|
||||
@@ -76,7 +76,7 @@
|
||||
"
|
||||
/>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let item of group.items; let lastItem = last; let i = index">
|
||||
<ng-container *ngFor="let item of group.items; let lastItem = last; let i = index; trackBy: trackByItemId">
|
||||
<ng-container
|
||||
*ngIf="group?.orderType !== undefined && (item.features?.orderType === 'Abholung' || item.features?.orderType === 'Rücklage')"
|
||||
>
|
||||
@@ -126,7 +126,8 @@
|
||||
[disabled]="
|
||||
showOrderButtonSpinner ||
|
||||
((primaryCtaLabel$ | async) === 'Bestellen' && !(checkNotificationChannelControl$ | async)) ||
|
||||
notificationsControl?.invalid
|
||||
notificationsControl?.invalid ||
|
||||
((primaryCtaLabel$ | async) === 'Bestellen' && ((checkingOla$ | async) || (checkoutIsInValid$ | async)))
|
||||
"
|
||||
>
|
||||
<ui-spinner [show]="showOrderButtonSpinner">
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ViewChildren,
|
||||
QueryList,
|
||||
AfterViewInit,
|
||||
TrackByFunction,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
@@ -6,8 +17,8 @@ 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 { first, map, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
|
||||
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';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
@@ -17,6 +28,9 @@ import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-mod
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
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',
|
||||
@@ -24,9 +38,16 @@ import { CheckoutReviewStore } from './checkout-review.store';
|
||||
styleUrls: ['checkout-review.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
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$;
|
||||
|
||||
buyer$ = this._store.buyer$;
|
||||
|
||||
shoppingCart$ = this._store.shoppingCart$;
|
||||
|
||||
fetching$ = this._store.fetching$;
|
||||
@@ -38,6 +59,9 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
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) =>
|
||||
@@ -120,12 +144,12 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
showQuantityControlSpinnerItemId: number;
|
||||
quantityError$ = new BehaviorSubject<{ [key: string]: string }>({});
|
||||
|
||||
primaryCtaLabel$ = combineLatest([this.payer$, this.shoppingCartItemsWithoutOrderType$]).pipe(
|
||||
map(([payer, shoppingCartItemsWithoutOrderType]) => {
|
||||
primaryCtaLabel$ = combineLatest([this.payer$, this.buyer$, this.shoppingCartItemsWithoutOrderType$]).pipe(
|
||||
map(([payer, buyer, shoppingCartItemsWithoutOrderType]) => {
|
||||
if (shoppingCartItemsWithoutOrderType?.length > 0) {
|
||||
return 'Kaufoptionen';
|
||||
}
|
||||
if (!payer) {
|
||||
if (!(payer || buyer)) {
|
||||
return 'Weiter';
|
||||
}
|
||||
return 'Bestellen';
|
||||
@@ -147,15 +171,26 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
loadingOnQuantityChangeById$ = new Subject<number>();
|
||||
showOrderButtonSpinner: boolean;
|
||||
|
||||
checkoutIsInValid$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
switchMap((processId) => this.domainCheckoutService.checkoutIsValid({ processId })),
|
||||
map((valid) => !valid)
|
||||
);
|
||||
|
||||
get productSearchBasePath() {
|
||||
return this._productNavigationService.getArticleSearchBasePath(this.applicationService.activatedProcessId);
|
||||
return this._productNavigationService.getArticleSearchBasePath(this.applicationService.activatedProcessId).path;
|
||||
}
|
||||
|
||||
get isDesktop$() {
|
||||
return this._environmentService.matchDesktopLarge$;
|
||||
}
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
@ViewChildren(ShoppingCartItemComponent)
|
||||
private _shoppingCartItems: QueryList<ShoppingCartItemComponent>;
|
||||
|
||||
olaCheckSubscription: Subscription;
|
||||
|
||||
trackByItemId: TrackByFunction<ShoppingCartItemDTO> = (_, item) => item?.id;
|
||||
|
||||
constructor(
|
||||
private domainCheckoutService: DomainCheckoutService,
|
||||
@@ -171,7 +206,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
private _productNavigationService: ProductCatalogNavigationService,
|
||||
private _navigationService: CheckoutNavigationService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _store: CheckoutReviewStore
|
||||
private _store: CheckoutReviewStore,
|
||||
private _toaster: ToasterService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -181,19 +217,58 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
|
||||
await this.removeBreadcrumbs();
|
||||
await this.updateBreadcrumb();
|
||||
|
||||
window['Checkout'] = {
|
||||
refreshAvailabilities: this.refreshAvailabilities.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.registerOlaCechk();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
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(
|
||||
delay(250),
|
||||
switchMap((processId) =>
|
||||
this.domainCheckoutService.validateOlaStatus({
|
||||
processId,
|
||||
})
|
||||
)
|
||||
)
|
||||
.subscribe((result) => {
|
||||
if (!result) {
|
||||
this.refreshAvailabilities();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async refreshAvailabilities() {
|
||||
this.checkingOla$.next(true);
|
||||
|
||||
for (let itemComp of this._shoppingCartItems.toArray()) {
|
||||
await itemComp.refreshAvailability();
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
this.checkingOla$.next(false);
|
||||
}
|
||||
|
||||
async updateBreadcrumb() {
|
||||
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: 'Warenkorb',
|
||||
path: this._navigationService.getCheckoutReviewPath(this.applicationService.activatedProcessId),
|
||||
path: this._navigationService.getCheckoutReviewPath(this.applicationService.activatedProcessId).path,
|
||||
tags: ['checkout', 'cart'],
|
||||
section: 'customer',
|
||||
});
|
||||
@@ -401,6 +476,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch(processId: number) {
|
||||
const nav = this._customerSearchNavigation.defaultRoute({ processId });
|
||||
|
||||
try {
|
||||
const response = await this.customerFeatures$
|
||||
.pipe(
|
||||
@@ -411,11 +488,11 @@ export class CheckoutReviewComponent 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +512,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
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() {
|
||||
@@ -459,7 +537,7 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
const orderIds = orders.map((order) => order.id).join(',');
|
||||
this._store.orderCompleted.next();
|
||||
await this.patchProcess(processId);
|
||||
await this._navigationService.navigateToCheckoutSummary({ processId, orderIds });
|
||||
await this._navigationService.getCheckoutSummaryPath({ processId, orderIds }).navigate();
|
||||
} catch (error) {
|
||||
const response = error?.error;
|
||||
let message: string = response?.message ?? '';
|
||||
@@ -474,12 +552,14 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
title: 'Hinweis',
|
||||
data: { message: message.trim() },
|
||||
});
|
||||
} else if (error) {
|
||||
this.uiModal.error('Fehler beim abschließen der Bestellung', error);
|
||||
}
|
||||
|
||||
if (error.status === 409) {
|
||||
this._store.orderCompleted.next();
|
||||
await this.patchProcess(processId);
|
||||
await this._navigationService.navigateToCheckoutSummary({ processId });
|
||||
await this._navigationService.getCheckoutSummaryPath({ processId }).navigate();
|
||||
}
|
||||
} finally {
|
||||
this.showOrderButtonSpinner = false;
|
||||
|
||||
@@ -20,6 +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, SkeletonLoaderComponent } from '@shared/components/loader';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -39,6 +40,8 @@ import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
UiCheckboxModule,
|
||||
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 }}
|
||||
@@ -67,6 +67,7 @@
|
||||
<page-special-comment
|
||||
class="mb-6 mt-4"
|
||||
[hasPayer]="!!(payer$ | async)"
|
||||
[hasBuyer]="!!(buyer$ | async)"
|
||||
[ngModel]="specialComment$ | async"
|
||||
(ngModelChange)="setAgentComment($event)"
|
||||
>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -40,17 +40,30 @@
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ item?.product?.publicationDate | date }}
|
||||
</div>
|
||||
|
||||
<div class="item-date" *ngIf="orderType === 'Abholung'">Abholung ab {{ item?.availability?.estimatedShippingDate | date }}</div>
|
||||
<div class="item-date" *ngIf="orderType === 'Versand' || orderType === 'B2B-Versand' || orderType === 'DIG-Versand'">
|
||||
<ng-container *ngIf="item?.availability?.estimatedDelivery; else estimatedShippingDate">
|
||||
Zustellung zwischen {{ (item?.availability?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
und
|
||||
{{ (item?.availability?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate> Versand {{ item?.availability?.estimatedShippingDate | date }} </ng-template>
|
||||
<div *ngIf="notAvailable$ | async">
|
||||
<span class="text-brand item-date">Nicht verfügbar</span>
|
||||
</div>
|
||||
|
||||
<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.availability-changed]="estimatedShippingDateChanged$ | async"
|
||||
*ngIf="orderType === 'Versand' || orderType === 'B2B-Versand' || orderType === 'DIG-Versand'"
|
||||
>
|
||||
<ng-container *ngIf="item?.availability?.estimatedDelivery; else estimatedShippingDate">
|
||||
Zustellung zwischen {{ (item?.availability?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
und
|
||||
{{ (item?.availability?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate> Versand {{ item?.availability?.estimatedShippingDate | date }} </ng-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div class="item-availability-message" *ngIf="olaError$ | async">
|
||||
Artikel nicht verfügbar
|
||||
</div>
|
||||
|
||||
@@ -101,8 +101,16 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.availability-changed {
|
||||
@apply text-dark-goldenrod;
|
||||
}
|
||||
|
||||
::ng-deep page-shopping-cart-item ui-quantity-dropdown {
|
||||
.current-quantity {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-shopping-cart-item .ava-loader ui-icon {
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnInit, Output, inject } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
@@ -6,6 +6,8 @@ import { DomainCheckoutService } from '@domain/checkout';
|
||||
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';
|
||||
|
||||
@@ -14,6 +16,10 @@ export interface ShoppingCartItemComponentState {
|
||||
orderType: string;
|
||||
loadingOnItemChangeById?: number;
|
||||
loadingOnQuantityChangeById?: number;
|
||||
refreshingAvailability: boolean;
|
||||
sscChanged: boolean;
|
||||
sscTextChanged: boolean;
|
||||
estimatedShippingDateChanged: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -23,6 +29,8 @@ export interface ShoppingCartItemComponentState {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemComponentState> implements OnInit {
|
||||
private _zone = inject(NgZone);
|
||||
|
||||
@Output() changeItem = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO }>();
|
||||
@Output() changeDummyItem = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO }>();
|
||||
@Output() changeQuantity = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO; quantity: number }>();
|
||||
@@ -113,28 +121,58 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
||||
.pipe(map((ids) => ids?.find((id) => id === this.item.id)));
|
||||
|
||||
get productSearchResultsPath() {
|
||||
return this._productNavigationService.getArticleSearchResultsPath(this.application.activatedProcessId);
|
||||
return this._productNavigationService.getArticleSearchResultsPath(this.application.activatedProcessId).path;
|
||||
}
|
||||
|
||||
get productSearchDetailsPath() {
|
||||
return this._productNavigationService.getArticleDetailsPath({
|
||||
return this._productNavigationService.getArticleDetailsPathByEan({
|
||||
processId: this.application.activatedProcessId,
|
||||
ean: this.item?.product?.ean,
|
||||
});
|
||||
}).path;
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
refreshingAvailabilit$ = this.select((s) => s.refreshingAvailability);
|
||||
|
||||
sscChanged$ = this.select((s) => s.sscChanged || s.sscTextChanged);
|
||||
|
||||
estimatedShippingDateChanged$ = this.select((s) => s.estimatedShippingDateChanged);
|
||||
|
||||
notAvailable$ = this.item$.pipe(
|
||||
map((item) => {
|
||||
const availability = item?.availability;
|
||||
|
||||
if (availability.availabilityType === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (availability.inStock && item.quantity > availability.inStock) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !this.availabilityService.isAvailable({ availability });
|
||||
})
|
||||
);
|
||||
|
||||
constructor(
|
||||
private availabilityService: DomainAvailabilityService,
|
||||
private checkoutService: DomainCheckoutService,
|
||||
public application: ApplicationService,
|
||||
private _productNavigationService: ProductCatalogNavigationService,
|
||||
private _environment: EnvironmentService
|
||||
private _environment: EnvironmentService,
|
||||
private _cdr: ChangeDetectorRef
|
||||
) {
|
||||
super({ item: undefined, orderType: '' });
|
||||
super({
|
||||
item: undefined,
|
||||
orderType: '',
|
||||
refreshingAvailability: false,
|
||||
sscChanged: false,
|
||||
sscTextChanged: false,
|
||||
estimatedShippingDateChanged: false,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
@@ -147,4 +185,54 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
||||
onChangeQuantity(quantity: number) {
|
||||
this.changeQuantity.emit({ shoppingCartItem: this.item, quantity });
|
||||
}
|
||||
|
||||
async refreshAvailability() {
|
||||
const currentAvailability = cloneDeep(this.item.availability);
|
||||
|
||||
try {
|
||||
this.patchRefreshingAvailability(true);
|
||||
this._cdr.markForCheck();
|
||||
const availability = await this.checkoutService.refreshAvailability({
|
||||
processId: this.application.activatedProcessId,
|
||||
shoppingCartItemId: this.item.id,
|
||||
});
|
||||
|
||||
if (currentAvailability.ssc !== availability.ssc) {
|
||||
this.sscChanged();
|
||||
}
|
||||
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);
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
patchRefreshingAvailability(value: boolean) {
|
||||
this._zone.run(() => {
|
||||
this.patchState({ refreshingAvailability: value });
|
||||
this._cdr.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
ssctextChanged() {
|
||||
this.patchState({ sscTextChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
sscChanged() {
|
||||
this.patchState({ sscChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
estimatedShippingDateChanged() {
|
||||
this.patchState({ estimatedShippingDateChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,5 +33,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!hasPayer" class="text-p3">Zur Info: Sie haben dem Warenkorb noch keinen Kunden hinzugefügt.</div>
|
||||
<div *ngIf="!(hasPayer || hasBuyer)" class="text-p3">Zur Info: Sie haben dem Warenkorb noch keinen Kunden hinzugefügt.</div>
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,9 @@ export class SpecialCommentComponent implements ControlValueAccessor {
|
||||
@Input()
|
||||
hasPayer: boolean;
|
||||
|
||||
@Input()
|
||||
hasBuyer: boolean;
|
||||
|
||||
@Output()
|
||||
isDirtyChange = new EventEmitter<boolean>();
|
||||
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
@@ -173,7 +194,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
path: this._navigation.getCheckoutSummaryPath({
|
||||
processId: this.applicationService.activatedProcessId,
|
||||
orderIds: this._route.snapshot.params.orderIds,
|
||||
}),
|
||||
}).path,
|
||||
tags: ['checkout', 'cart'],
|
||||
section: 'customer',
|
||||
});
|
||||
@@ -206,30 +227,16 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getProductSearchDetailsPath(ean: string) {
|
||||
return this._productNavigationService.getArticleDetailsPath({
|
||||
return this._productNavigationService.getArticleDetailsPathByEan({
|
||||
processId: this.processId,
|
||||
ean,
|
||||
});
|
||||
}).path;
|
||||
}
|
||||
|
||||
getProductSearchDetailsQueryParams(item: DisplayOrderItemDTO) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,38 +5,28 @@ import { CheckoutSummaryComponent } from './checkout-summary/checkout-summary.co
|
||||
import { PageCheckoutComponent } from './page-checkout.component';
|
||||
import { CheckoutReviewDetailsComponent } from './checkout-review/details/checkout-review-details.component';
|
||||
|
||||
const auxiliaryRoutes = [
|
||||
{
|
||||
path: 'details',
|
||||
component: CheckoutReviewDetailsComponent,
|
||||
outlet: 'left',
|
||||
},
|
||||
{
|
||||
path: 'review',
|
||||
component: CheckoutReviewComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'summary',
|
||||
component: CheckoutSummaryComponent,
|
||||
outlet: 'main',
|
||||
},
|
||||
{
|
||||
path: 'summary/:orderIds',
|
||||
component: CheckoutSummaryComponent,
|
||||
outlet: 'main',
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: PageCheckoutComponent,
|
||||
children: [
|
||||
{ path: 'summary', component: CheckoutSummaryComponent },
|
||||
{ path: 'summary/:orderIds', component: CheckoutSummaryComponent },
|
||||
{ path: 'review', component: CheckoutReviewComponent },
|
||||
...auxiliaryRoutes,
|
||||
{
|
||||
path: 'details',
|
||||
component: CheckoutReviewDetailsComponent,
|
||||
outlet: 'side',
|
||||
},
|
||||
{
|
||||
path: 'review',
|
||||
component: CheckoutReviewComponent,
|
||||
},
|
||||
{
|
||||
path: 'summary',
|
||||
component: CheckoutSummaryComponent,
|
||||
},
|
||||
{
|
||||
path: 'summary/:orderIds',
|
||||
component: CheckoutSummaryComponent,
|
||||
},
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'review' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,23 +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>
|
||||
|
||||
<ng-container *ngIf="routerEvents$ | async">
|
||||
<ng-container *ngIf="!(isDesktop$ | async); else desktop">
|
||||
<router-outlet></router-outlet>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #desktop>
|
||||
<ng-container *ngIf="showMainOutlet$ | async">
|
||||
<router-outlet name="main"></router-outlet>
|
||||
</ng-container>
|
||||
|
||||
<div class="grid grid-cols-split-screen gap-split-screen">
|
||||
<div *ngIf="!(showMainOutlet$ | async)" class="block">
|
||||
<router-outlet name="left"></router-outlet>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!(showMainOutlet$ | async)">
|
||||
<router-outlet name="right"></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<shared-splitscreen></shared-splitscreen>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout',
|
||||
@@ -13,28 +11,7 @@ import { map, shareReplay } from 'rxjs/operators';
|
||||
export class PageCheckoutComponent implements OnInit {
|
||||
readonly breadcrumbKey$ = this.applicationService.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||
|
||||
get isDesktop$() {
|
||||
return this._environmentService.matchDesktopLarge$;
|
||||
}
|
||||
|
||||
routerEvents$ = this._router.events.pipe(shareReplay());
|
||||
|
||||
showMainOutlet$ = this.routerEvents$.pipe(
|
||||
map((_) => {
|
||||
const primary = this._activatedRoute?.children?.find(
|
||||
(child) => child?.outlet === 'primary' && child?.routeConfig?.path === 'summary'
|
||||
);
|
||||
const main = this._activatedRoute?.children?.find((child) => child?.outlet === 'main' && child?.routeConfig?.path === 'summary');
|
||||
return !!primary && !!main;
|
||||
})
|
||||
);
|
||||
|
||||
constructor(
|
||||
private applicationService: ApplicationService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _router: Router,
|
||||
private _activatedRoute: ActivatedRoute
|
||||
) {}
|
||||
constructor(private applicationService: ApplicationService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,18 @@ import { CheckoutReviewModule } from './checkout-review/checkout-review.module';
|
||||
import { CheckoutSummaryModule } from './checkout-summary/checkout-summary.module';
|
||||
import { PageCheckoutRoutingModule } from './page-checkout-routing.module';
|
||||
import { PageCheckoutComponent } from './page-checkout.component';
|
||||
import { SharedSplitscreenComponent } from '@shared/components/splitscreen';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CheckoutSummaryModule, PageCheckoutRoutingModule, CheckoutReviewModule, CheckoutDummyModule, BreadcrumbModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CheckoutSummaryModule,
|
||||
PageCheckoutRoutingModule,
|
||||
CheckoutReviewModule,
|
||||
SharedSplitscreenComponent,
|
||||
CheckoutDummyModule,
|
||||
BreadcrumbModule,
|
||||
],
|
||||
declarations: [PageCheckoutComponent],
|
||||
exports: [],
|
||||
})
|
||||
|
||||
@@ -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),
|
||||
@@ -237,7 +263,7 @@ export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
|
||||
processingStatus: item?.processingStatus,
|
||||
compartmentCode: item?.compartmentCode ? encodeURIComponent(item.compartmentCode) : undefined,
|
||||
orderId: item?.orderId,
|
||||
});
|
||||
}).path;
|
||||
}
|
||||
|
||||
getEditPath(item: OrderItemListItemDTO) {
|
||||
@@ -246,7 +272,7 @@ export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
|
||||
processingStatus: item?.processingStatus,
|
||||
compartmentCode: item?.compartmentCode ? encodeURIComponent(item.compartmentCode) : undefined,
|
||||
orderId: item?.orderId,
|
||||
});
|
||||
}).path;
|
||||
}
|
||||
|
||||
getHistoryPath(item: OrderItemListItemDTO) {
|
||||
@@ -255,7 +281,7 @@ export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
|
||||
processingStatus: item?.processingStatus,
|
||||
compartmentCode: item?.compartmentCode ? encodeURIComponent(item.compartmentCode) : undefined,
|
||||
orderId: item?.orderId,
|
||||
});
|
||||
}).path;
|
||||
}
|
||||
|
||||
getItemQuantityMap(): Map<number, number> {
|
||||
@@ -346,7 +372,7 @@ export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
async actionHandled(handler: { orderItemsContext: OrderItemsContext; command: string; navigation: 'details' | 'main' | 'reservation' }) {
|
||||
if (handler.navigation === 'main') {
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId: this.processId });
|
||||
await this._navigationService.getCustomerOrdersBasePath(this.processId).navigate();
|
||||
} else {
|
||||
const item: OrderItemListItemDTO = handler.orderItemsContext.items.find((_) => true);
|
||||
await this._router.navigate(this.getDetailsPath(item), {
|
||||
|
||||
@@ -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';
|
||||
@@ -80,25 +80,27 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
processingStatus,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderId: Number(orderId),
|
||||
}),
|
||||
}).path,
|
||||
section: 'customer',
|
||||
params: this._activatedRoute.snapshot.queryParams,
|
||||
tags: ['customer-order', 'edit', compartmentCode || orderId],
|
||||
});
|
||||
}
|
||||
|
||||
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.navigateToDetails({
|
||||
processId: this.processId,
|
||||
processingStatus: processingStatus,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderId: orderId ? Number(orderId) : undefined,
|
||||
queryParams: this._activatedRoute.snapshot.queryParams,
|
||||
});
|
||||
await this._navigation
|
||||
.getCustomerOrdersDetailsPath({
|
||||
processId: this.processId,
|
||||
processingStatus: processingStatus,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderId: orderId ? Number(orderId) : undefined,
|
||||
extras: { queryParams: this._activatedRoute.snapshot.queryParams },
|
||||
})
|
||||
.navigate();
|
||||
}
|
||||
|
||||
openModalIfItemsHaveDifferentCustomers(items: OrderItemListItemDTO[]) {
|
||||
|
||||
@@ -74,7 +74,7 @@ export class CustomerOrderHistoryComponent implements OnInit {
|
||||
processingStatus,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderId: Number(orderId),
|
||||
}),
|
||||
}).path,
|
||||
section: 'customer',
|
||||
params: this._activatedRoute.snapshot.queryParams,
|
||||
tags: ['customer-order', 'history', compartmentCode || orderId],
|
||||
@@ -86,12 +86,14 @@ export class CustomerOrderHistoryComponent implements OnInit {
|
||||
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
|
||||
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
|
||||
|
||||
await this._navigation.navigateToDetails({
|
||||
processId: this.processId,
|
||||
processingStatus: processingStatus,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderId: orderId ? Number(orderId) : undefined,
|
||||
queryParams: this._activatedRoute.snapshot.queryParams,
|
||||
});
|
||||
await this._navigation
|
||||
.getCustomerOrdersDetailsPath({
|
||||
processId: this.processId,
|
||||
processingStatus: processingStatus,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderId: orderId ? Number(orderId) : undefined,
|
||||
extras: { queryParams: this._activatedRoute.snapshot.queryParams },
|
||||
})
|
||||
.navigate();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user