mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
26 Commits
fix/4752-P
...
feature/39
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
967df6316b | ||
|
|
878bf44d0b | ||
|
|
da6489eb7a | ||
|
|
819827cc4c | ||
|
|
d09b5b1ce7 | ||
|
|
cc03ef4f9c | ||
|
|
b4dbd8889d | ||
|
|
483faad86a | ||
|
|
0dbc745ed0 | ||
|
|
180e93a7da | ||
|
|
5c6f416391 | ||
|
|
d97b6afac8 | ||
|
|
771816f3af | ||
|
|
0626538aea | ||
|
|
a1ad4e4a05 | ||
|
|
6df48eb555 | ||
|
|
27ab4526e2 | ||
|
|
9a24b34fbc | ||
|
|
d01e01534b | ||
|
|
5bca1f2a81 | ||
|
|
807b300885 | ||
|
|
9671683a93 | ||
|
|
d909d6e804 | ||
|
|
15c50779b4 | ||
|
|
5bdfec7c3f | ||
|
|
6bc265a358 |
@@ -27,6 +27,7 @@ import {
|
||||
StoreCheckoutPayerService,
|
||||
StoreCheckoutBranchService,
|
||||
ItemsResult,
|
||||
ShoppingCartItemDTO,
|
||||
} from '@swagger/checkout';
|
||||
import {
|
||||
DisplayOrderDTO,
|
||||
@@ -36,20 +37,45 @@ 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, zip, EMPTY, Subscription } from 'rxjs';
|
||||
import {
|
||||
bufferCount,
|
||||
catchError,
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
first,
|
||||
map,
|
||||
mergeMap,
|
||||
share,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
withLatestFrom,
|
||||
} 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';
|
||||
import { CheckoutEntity } from './store/defs/checkout.entity';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
@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,
|
||||
@@ -119,14 +145,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))
|
||||
)
|
||||
)
|
||||
@@ -249,11 +275,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 +304,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 +315,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 +599,172 @@ 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 new Observable((observer) => {
|
||||
const enity$ = this.store.select(DomainCheckoutSelectors.selectCheckoutEntityByProcessId, { processId });
|
||||
|
||||
const olaExpiration = this.olaExpiration;
|
||||
|
||||
let timeout: any;
|
||||
|
||||
let subscription: Subscription;
|
||||
|
||||
function check() {
|
||||
const now = Date.now();
|
||||
|
||||
subscription?.unsubscribe();
|
||||
subscription = enity$.pipe(take(1)).subscribe((entity) => {
|
||||
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) => itemAvailabilityTimestamp[`${item.id}_${item.features.orderType}`]);
|
||||
|
||||
if (timestamps?.length > 0) {
|
||||
const oldestTimestamp = Math.min(...timestamps);
|
||||
observer.next(now - oldestTimestamp < olaExpiration);
|
||||
}
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
check.call(this);
|
||||
}, interval ?? olaExpiration / 10);
|
||||
});
|
||||
}
|
||||
|
||||
check.call(this);
|
||||
|
||||
return () => {
|
||||
subscription?.unsubscribe();
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
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 +918,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({
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||
},
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
|
||||
},
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
"@swagger/wws": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "30s"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa.paragon-systems.de/isa/v1"
|
||||
},
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-staging.paragon-systems.de/isa/v1"
|
||||
},
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||
},
|
||||
|
||||
@@ -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,6 @@
|
||||
<ng-container *ngFor="let line of lines">
|
||||
<ng-container [ngSwitch]="line | lineType">
|
||||
<page-article-details-text-link *ngSwitchCase="'reihe'" [route]="line | reiheRoute"> {{ line }} <br /> </page-article-details-text-link>
|
||||
<ng-container *ngSwitchDefault> {{ line }} <br /> </ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Renderer2, ElementRef, OnChanges, SimpleChanges, HostBinding } 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';
|
||||
|
||||
const REIHE_REGEX = /^Reihe:\s*"(.+)\"$/m;
|
||||
|
||||
@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(private elementRef: ElementRef, private renderer: Renderer2) {}
|
||||
|
||||
// ngOnChanges(changes: SimpleChanges): void {
|
||||
// if (changes.text) {
|
||||
// this.renderText();
|
||||
// }
|
||||
// }
|
||||
|
||||
// renderText() {
|
||||
// console.log(this.getReihe());
|
||||
// }
|
||||
|
||||
// getReihe() {
|
||||
// REIHE_REGEX.exec(this.text?.value)?.forEach((match, groupIndex) => {
|
||||
// if (groupIndex === 0) return;
|
||||
// return match;
|
||||
// });
|
||||
// }
|
||||
|
||||
// @HostBinding('innerHTML')
|
||||
// get htmlContent() {
|
||||
// let content = this.text?.value;
|
||||
|
||||
// REIHE_REGEX.exec(content)?.forEach((match, groupIndex) => {
|
||||
// if (groupIndex === 0) return;
|
||||
|
||||
// const aElement: HTMLAnchorElement = this.renderer.createElement('a');
|
||||
// const text = this.renderer.createText(match);
|
||||
// this.renderer.appendChild(aElement, text);
|
||||
// this.renderer.setAttribute(aElement, 'href', `/search?query=${match}`);
|
||||
|
||||
// content = content.replace(match, aElement.outerHTML);
|
||||
// });
|
||||
|
||||
// return content;
|
||||
// }
|
||||
}
|
||||
@@ -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:\s*"(.+)\"$/g;
|
||||
const reihe = REIHE_REGEX.exec(value)?.[1];
|
||||
|
||||
if (!reihe) {
|
||||
this.result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const main_qs = reihe.split('/')[0];
|
||||
|
||||
const path = this.navigation.getArticleSearchResultsPath(processId);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -107,12 +107,12 @@
|
||||
<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>
|
||||
|
||||
<!-- 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">
|
||||
@@ -315,9 +315,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"
|
||||
|
||||
@@ -15,8 +15,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'
|
||||
|
||||
@@ -127,6 +127,18 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
priceMaintained$ = combineLatest([
|
||||
this.store.item$,
|
||||
this.store.takeAwayAvailability$,
|
||||
this.store.deliveryAvailability$,
|
||||
this.store.deliveryDigAvailability$,
|
||||
this.store.deliveryB2BAvailability$,
|
||||
]).pipe(
|
||||
map(([item, takeAway, delivery, deliveryDig, deliveryB2B]) =>
|
||||
[item, takeAway, delivery, deliveryDig, deliveryB2B].some((i) => i?.priceMaintained)
|
||||
)
|
||||
);
|
||||
|
||||
price$ = combineLatest([
|
||||
this.store.item$,
|
||||
this.store.takeAwayAvailability$,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -13,7 +13,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,
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
}
|
||||
@@ -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,4 @@
|
||||
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
@@ -6,8 +6,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 { catchError, debounceTime, delay, first, map, shareReplay, switchMap, take, takeUntil, tap } from 'rxjs/operators';
|
||||
import { Subject, NEVER, combineLatest, BehaviorSubject, interval, of, merge } from 'rxjs';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
@@ -17,6 +17,8 @@ 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';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-review',
|
||||
@@ -25,6 +27,8 @@ import { CheckoutReviewStore } from './checkout-review.store';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
checkingOla$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
payer$ = this._store.payer$;
|
||||
|
||||
buyer$ = this._store.buyer$;
|
||||
@@ -149,6 +153,11 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
loadingOnQuantityChangeById$ = new Subject<number>();
|
||||
showOrderButtonSpinner: boolean;
|
||||
|
||||
checkoutIsInValid$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.domainCheckoutService.checkoutIsValid({ processId })),
|
||||
map((valid) => !valid)
|
||||
);
|
||||
|
||||
get productSearchBasePath() {
|
||||
return this._productNavigationService.getArticleSearchBasePath(this.applicationService.activatedProcessId);
|
||||
}
|
||||
@@ -159,6 +168,9 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
@ViewChildren(ShoppingCartItemComponent)
|
||||
private _shoppingCartItems: QueryList<ShoppingCartItemComponent>;
|
||||
|
||||
constructor(
|
||||
private domainCheckoutService: DomainCheckoutService,
|
||||
public applicationService: ApplicationService,
|
||||
@@ -173,7 +185,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() {
|
||||
@@ -183,6 +196,12 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
|
||||
await this.removeBreadcrumbs();
|
||||
await this.updateBreadcrumb();
|
||||
|
||||
this.registerOlaCechk();
|
||||
|
||||
window['Checkout'] = {
|
||||
refreshAvailabilities: this.refreshAvailabilities.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -191,6 +210,32 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
registerOlaCechk() {
|
||||
this.applicationService.activatedProcessId$
|
||||
.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
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, 500));
|
||||
}
|
||||
this.checkingOla$.next(false);
|
||||
}
|
||||
|
||||
async updateBreadcrumb() {
|
||||
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
@@ -476,6 +521,8 @@ 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) {
|
||||
|
||||
@@ -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 } from '@shared/components/loader';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -39,6 +40,7 @@ import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
UiCheckboxModule,
|
||||
SharedNotificationChannelControlModule,
|
||||
TextFieldModule,
|
||||
LoaderComponent,
|
||||
],
|
||||
exports: [CheckoutReviewComponent, CheckoutReviewDetailsComponent],
|
||||
declarations: [CheckoutReviewComponent, SpecialCommentComponent, ShoppingCartItemComponent, CheckoutReviewDetailsComponent],
|
||||
|
||||
@@ -40,16 +40,26 @@
|
||||
{{ 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>
|
||||
<ui-spinner class="ava-loader" [show]="refreshingAvailabilit$ | async">
|
||||
<div class="item-date" [class.ssc-changed]="sscChanged$ | async" *ngIf="orderType === 'Abholung'">
|
||||
Abholung ab {{ item?.availability?.estimatedShippingDate | date }}
|
||||
</div>
|
||||
<div
|
||||
class="item-date"
|
||||
[class.ssc-changed]="sscChanged$ | 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>
|
||||
</ui-spinner>
|
||||
|
||||
<div class="item-availability-message" *ngIf="olaError$ | async">
|
||||
Artikel nicht verfügbar
|
||||
|
||||
@@ -101,8 +101,16 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.ssc-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,7 @@ 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 { combineLatest } from 'rxjs';
|
||||
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
|
||||
@@ -14,6 +15,9 @@ export interface ShoppingCartItemComponentState {
|
||||
orderType: string;
|
||||
loadingOnItemChangeById?: number;
|
||||
loadingOnQuantityChangeById?: number;
|
||||
refreshingAvailability: boolean;
|
||||
sscChanged: boolean;
|
||||
sscTextChanged: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -23,6 +27,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 }>();
|
||||
@@ -127,14 +133,35 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
refreshingAvailabilit$ = this.select((s) => s.refreshingAvailability);
|
||||
|
||||
sscChanged$ = this.select((s) => s.sscChanged || s.sscTextChanged);
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
@@ -147,4 +174,48 @@ 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();
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
this.patchRefreshingAvailability(false);
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
patchRefreshingAvailability(value: boolean) {
|
||||
this._zone.run(() => {
|
||||
this.patchState({ refreshingAvailability: value });
|
||||
this._cdr.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
ssctextChanged() {
|
||||
this._zone.run(() => {
|
||||
this.patchState({ sscTextChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
sscChanged() {
|
||||
this._zone.run(() => {
|
||||
this.patchState({ sscChanged: true });
|
||||
this._cdr.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,14 @@
|
||||
[attr.data-selected]="uiStartOption?.selected"
|
||||
>
|
||||
<shared-input-control>
|
||||
<input placeholder="TT.MM.JJJJ" sharedInputControlInput uiDateInput type="text" formControlName="start" />
|
||||
<input
|
||||
placeholder="TT.MM.JJJJ"
|
||||
sharedInputControlInput
|
||||
sharedDateInput
|
||||
type="text"
|
||||
formControlName="start"
|
||||
(blur)="setStratValue($event.target['value'])"
|
||||
/>
|
||||
<shared-input-control-suffix>
|
||||
<button
|
||||
type="button"
|
||||
@@ -27,7 +34,14 @@
|
||||
[attr.data-selected]="uiStopOption?.selected"
|
||||
>
|
||||
<shared-input-control>
|
||||
<input placeholder="TT.MM.JJJJ" sharedInputControlInput uiDateInput type="text" formControlName="stop" />
|
||||
<input
|
||||
placeholder="TT.MM.JJJJ"
|
||||
sharedInputControlInput
|
||||
sharedDateInput
|
||||
type="text"
|
||||
formControlName="stop"
|
||||
(blur)="setStopValue($event.target['value'])"
|
||||
/>
|
||||
<shared-input-control-suffix>
|
||||
<button
|
||||
type="button"
|
||||
@@ -40,25 +54,27 @@
|
||||
</shared-input-control-suffix>
|
||||
</shared-input-control>
|
||||
</shared-form-control>
|
||||
</div>
|
||||
|
||||
<ui-datepicker
|
||||
class="dp-left"
|
||||
#dpStart
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[ngModel]="uiStartOption?.value"
|
||||
saveLabel="Übernehmen"
|
||||
(save)="uiStartOption?.setValue($event)"
|
||||
>
|
||||
</ui-datepicker>
|
||||
<ui-datepicker
|
||||
class="dp-right"
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
#dpStop
|
||||
[ngModel]="uiStopOption?.value"
|
||||
(save)="setStopValue($event)"
|
||||
saveLabel="Übernehmen"
|
||||
>
|
||||
</ui-datepicker>
|
||||
<ui-datepicker
|
||||
class="dp-left"
|
||||
#dpStart
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
formControlName="start"
|
||||
[max]="maxDate"
|
||||
saveLabel="Übernehmen"
|
||||
(save)="setStratValue($event)"
|
||||
>
|
||||
</ui-datepicker>
|
||||
<ui-datepicker
|
||||
class="dp-right"
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
#dpStop
|
||||
[min]="minDate"
|
||||
formControlName="stop"
|
||||
(save)="setStopValue($event)"
|
||||
saveLabel="Übernehmen"
|
||||
>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, ChangeDetectorRef, ViewChild } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { IOption, Option } from '../../../tree';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { UiValidators } from '@ui/validators';
|
||||
import { DateValidator } from '@shared/forms';
|
||||
import { UiDatepickerComponent } from '@ui/datepicker';
|
||||
|
||||
@Component({
|
||||
selector: 'shared-input-option-date-range',
|
||||
@@ -11,11 +12,15 @@ import { UiValidators } from '@ui/validators';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FilterInputOptionDateRangeComponent {
|
||||
@ViewChild('dpStart', { static: true }) startDatepicker: UiDatepickerComponent;
|
||||
|
||||
@ViewChild('dpStop', { static: true }) stopDatepicker: UiDatepickerComponent;
|
||||
|
||||
private _options: Option[];
|
||||
|
||||
formGroup = new FormGroup({
|
||||
start: new FormControl<Date>(undefined, UiValidators.date),
|
||||
stop: new FormControl<Date>(undefined, UiValidators.date),
|
||||
start: new FormControl<Date>(undefined, DateValidator),
|
||||
stop: new FormControl<Date>(undefined, DateValidator),
|
||||
});
|
||||
|
||||
get startControl(): FormControl<Date> {
|
||||
@@ -47,6 +52,22 @@ export class FilterInputOptionDateRangeComponent {
|
||||
|
||||
optionChangeSubscription: Subscription;
|
||||
|
||||
get startDate() {
|
||||
return this.uiStartOption?.value ? new Date(this.uiStartOption?.value) : undefined;
|
||||
}
|
||||
|
||||
get stopDate() {
|
||||
return this.uiStopOption?.value ? new Date(this.uiStopOption?.value) : undefined;
|
||||
}
|
||||
|
||||
get minDate() {
|
||||
return this.startDate ?? new Date(0);
|
||||
}
|
||||
|
||||
get maxDate() {
|
||||
return this.stopDate ?? new Date('9999-12-31');
|
||||
}
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {}
|
||||
|
||||
subscribeChanges() {
|
||||
@@ -55,44 +76,33 @@ export class FilterInputOptionDateRangeComponent {
|
||||
this.formGroup.patchValue({ start: (this.uiStartOption.value as any) as Date });
|
||||
|
||||
this.optionChangeSubscription.add(
|
||||
this.uiStartOption.changes.subscribe(() => {
|
||||
if (new Date(this.uiStartOption.value) !== new Date(this.formGroup.get('start').value)) {
|
||||
this.formGroup.patchValue({ start: (this.uiStartOption.value as any) as Date });
|
||||
this.uiStartOption.changes.subscribe(({ target, keys }) => {
|
||||
if (keys.includes('value')) {
|
||||
if (new Date(target.value) !== this.formGroup.get('start').value) {
|
||||
this.formGroup.patchValue({ start: target.value as any });
|
||||
this.startDatepicker?.setDisplayed(new Date(target.value));
|
||||
}
|
||||
}
|
||||
|
||||
this.cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
this.optionChangeSubscription.add(
|
||||
this.formGroup.get('start').valueChanges.subscribe((value) => {
|
||||
if (new Date(this.uiStartOption.value) !== new Date(this.formGroup.get('start').value)) {
|
||||
this.uiStartOption.setValue(value);
|
||||
}
|
||||
this.cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
}
|
||||
if (this.uiStopOption) {
|
||||
this.formGroup.patchValue({ stop: (this.uiStopOption.value as any) as Date });
|
||||
|
||||
this.optionChangeSubscription.add(
|
||||
this.uiStopOption.changes.subscribe(() => {
|
||||
if (new Date(this.uiStopOption.value) !== new Date(this.formGroup.get('stop').value)) {
|
||||
this.formGroup.patchValue({ stop: (this.uiStopOption.value as any) as Date });
|
||||
this.uiStopOption.changes.subscribe(({ target, keys }) => {
|
||||
if (keys.includes('value')) {
|
||||
if (new Date(target.value) !== this.formGroup.get('start').value) {
|
||||
this.formGroup.patchValue({ stop: target.value as any });
|
||||
this.stopDatepicker?.setDisplayed(new Date(target.value));
|
||||
}
|
||||
}
|
||||
|
||||
this.cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
|
||||
this.optionChangeSubscription.add(
|
||||
this.formGroup.get('stop').valueChanges.subscribe((value) => {
|
||||
if (new Date(this.uiStopOption.value) !== new Date(this.formGroup.get('stop').value)) {
|
||||
this.uiStopOption.setValue(value);
|
||||
}
|
||||
this.cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +111,55 @@ export class FilterInputOptionDateRangeComponent {
|
||||
this.optionChangeSubscription = new Subscription();
|
||||
}
|
||||
|
||||
setStratValue(date: Date) {
|
||||
if (!date) {
|
||||
this.uiStartOption?.setValue(undefined);
|
||||
this.formGroup.patchValue({ start: undefined });
|
||||
this.startDatepicker?.setDisplayed(undefined);
|
||||
this.startDatepicker?.setSelected(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const startDate = new Date(date);
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
|
||||
if (this.startDate === date) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uiStartOption?.setValue(startDate);
|
||||
this.formGroup.patchValue({ start: startDate });
|
||||
this.startDatepicker?.setDisplayed(startDate ?? new Date());
|
||||
this.startDatepicker?.setSelected(startDate);
|
||||
|
||||
if (this.stopDate && startDate > this.stopDate) {
|
||||
this.setStopValue(startDate);
|
||||
}
|
||||
}
|
||||
|
||||
setStopValue(date: Date) {
|
||||
if (!date) {
|
||||
this.uiStopOption?.setValue(undefined);
|
||||
this.formGroup.patchValue({ stop: undefined });
|
||||
this.stopDatepicker?.setDisplayed(undefined);
|
||||
this.stopDatepicker?.setSelected(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const stopDate = new Date(date);
|
||||
stopDate.setHours(23, 59, 59, 999);
|
||||
|
||||
if (this.stopDate === date) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uiStopOption?.setValue(stopDate);
|
||||
this.formGroup.patchValue({ stop: stopDate });
|
||||
this.stopDatepicker?.setDisplayed(stopDate ?? new Date());
|
||||
this.stopDatepicker?.setSelected(stopDate);
|
||||
|
||||
if (this.startDate && stopDate < this.startDate) {
|
||||
this.setStratValue(stopDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { FormControlComponent } from '@shared/components/form-control';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { InputControlModule } from '@shared/components/input-control';
|
||||
import { UiDateInputDirective } from '@ui/input';
|
||||
import { DateInputDirective } from '@shared/forms';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -22,6 +23,7 @@ import { UiDateInputDirective } from '@ui/input';
|
||||
FormsModule,
|
||||
IconComponent,
|
||||
UiDateInputDirective,
|
||||
DateInputDirective,
|
||||
],
|
||||
exports: [FilterInputOptionDateRangeComponent],
|
||||
declarations: [FilterInputOptionDateRangeComponent],
|
||||
|
||||
@@ -98,7 +98,7 @@ export class InputControlComponent implements AfterContentInit, OnDestroy {
|
||||
console.error(new Error(`No input[sharedInput] found in \`<shared-input-control>\` component`));
|
||||
}
|
||||
|
||||
const statusChangesSub = this.inputDirective.control.statusChanges.subscribe(() => {
|
||||
const statusChangesSub = this.inputDirective.control.statusChanges.subscribe((s) => {
|
||||
this.renderError();
|
||||
this.renderIndicator();
|
||||
});
|
||||
|
||||
6
apps/shared/forms/ng-package.json
Normal file
6
apps/shared/forms/ng-package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
86
apps/shared/forms/src/lib/directives/date-input.directive.ts
Normal file
86
apps/shared/forms/src/lib/directives/date-input.directive.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { ChangeDetectorRef, Directive, HostBinding, HostListener, Input, forwardRef } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import moment from 'moment';
|
||||
import { DE_DATE_REGEX, ISO_DATE_REGEX } from '../regex';
|
||||
|
||||
@Directive({
|
||||
selector: 'input[type="text"][sharedDateInput]',
|
||||
standalone: true,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DateInputDirective),
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class DateInputDirective implements ControlValueAccessor {
|
||||
private _onChange = (_: any) => {};
|
||||
|
||||
private _onTouched = () => {};
|
||||
|
||||
@Input()
|
||||
value: any;
|
||||
|
||||
@HostBinding('value')
|
||||
displayValue: string;
|
||||
|
||||
@HostBinding('disabled')
|
||||
disabled: boolean;
|
||||
|
||||
constructor(private _cdr: ChangeDetectorRef) {}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.value = obj;
|
||||
this.setDisplayValue(obj);
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this._onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
this._onTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
setDisplayValue(value: any) {
|
||||
let date: Date;
|
||||
|
||||
if (value instanceof Date) {
|
||||
date = value;
|
||||
} else if (DE_DATE_REGEX.test(value)) {
|
||||
const mom = moment(value, 'L');
|
||||
date = mom.toDate();
|
||||
} else if (ISO_DATE_REGEX.test(value)) {
|
||||
date = new Date(value);
|
||||
}
|
||||
|
||||
if (date) {
|
||||
this.displayValue = moment(date).format('L');
|
||||
} else {
|
||||
this.displayValue = value ?? '';
|
||||
}
|
||||
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
@HostListener('input', ['$event.target.value'])
|
||||
onInput(value: string) {
|
||||
let date: Date;
|
||||
if (DE_DATE_REGEX.test(value)) {
|
||||
const mom = moment(value, 'L');
|
||||
date = mom.toDate();
|
||||
}
|
||||
|
||||
this.value = date ?? value;
|
||||
|
||||
this.displayValue = value;
|
||||
|
||||
this._onTouched();
|
||||
this._onChange(this.value);
|
||||
}
|
||||
}
|
||||
1
apps/shared/forms/src/lib/directives/index.ts
Normal file
1
apps/shared/forms/src/lib/directives/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './date-input.directive';
|
||||
3
apps/shared/forms/src/lib/regex.ts
Normal file
3
apps/shared/forms/src/lib/regex.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const DE_DATE_REGEX = /^(0?[1-9]|[12][0-9]|3[01])\.(0?[1-9]|1[0-2])\.\d{4}$/g;
|
||||
|
||||
export const ISO_DATE_REGEX = /^[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}Z$/g;
|
||||
25
apps/shared/forms/src/lib/validators/date.validator.ts
Normal file
25
apps/shared/forms/src/lib/validators/date.validator.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { AbstractControl } from '@angular/forms';
|
||||
import { DE_DATE_REGEX, ISO_DATE_REGEX } from '../regex';
|
||||
import moment from 'moment';
|
||||
|
||||
export function DateValidator(control: AbstractControl) {
|
||||
if (control.value) {
|
||||
let date: Date = null;
|
||||
|
||||
if (control.value instanceof Date) {
|
||||
date = control.value;
|
||||
} else if (typeof control.value === 'string') {
|
||||
if (DE_DATE_REGEX.test(control.value)) {
|
||||
date = moment(control.value, 'L').toDate();
|
||||
} else if (ISO_DATE_REGEX.test(control.value)) {
|
||||
date = new Date(control.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (date === null) {
|
||||
return { date: 'Datum ist ungültig' };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
1
apps/shared/forms/src/lib/validators/index.ts
Normal file
1
apps/shared/forms/src/lib/validators/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './date.validator';
|
||||
2
apps/shared/forms/src/public-api.ts
Normal file
2
apps/shared/forms/src/public-api.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './lib/directives';
|
||||
export * from './lib/validators';
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './purchase-options.helpers';
|
||||
export * from './purchase-options.selectors';
|
||||
export * from './purchase-options.service';
|
||||
export * from './purchase-options.state';
|
||||
export * from './purchase-options.store';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PriceDTO, PriceValueDTO } from '@swagger/checkout';
|
||||
import { DEFAULT_PRICE_DTO, DEFAULT_PRICE_VALUE, GIFT_CARD_MAX_PRICE, GIFT_CARD_TYPE, PURCHASE_OPTIONS } from '../constants';
|
||||
import { isArchive, isGiftCard, isItemDTO } from './purchase-options.helpers';
|
||||
import { PriceDTO } from '@swagger/checkout';
|
||||
import { DEFAULT_PRICE_DTO, GIFT_CARD_MAX_PRICE, PURCHASE_OPTIONS } from '../constants';
|
||||
import { isGiftCard, isItemDTO } from './purchase-options.helpers';
|
||||
import { PurchaseOptionsState } from './purchase-options.state';
|
||||
import { ActionType, Availability, Branch, CanAdd, FetchingAvailability, Item, PurchaseOption } from './purchase-options.types';
|
||||
|
||||
@@ -198,6 +198,14 @@ export function getAvailabilitiesForItem(
|
||||
};
|
||||
}
|
||||
|
||||
export function isArchive(item: Item, type: ActionType): boolean {
|
||||
if (isItemDTO(item, type)) {
|
||||
return item?.features?.some((f) => f.key === 'ARC');
|
||||
} else {
|
||||
return !!item?.features['ARC'];
|
||||
}
|
||||
}
|
||||
|
||||
export function getCanEditPrice(itemId: number): (state: PurchaseOptionsState) => boolean {
|
||||
return (state) => {
|
||||
const item = getItems(state).find((item) => item.id === itemId);
|
||||
@@ -241,6 +249,13 @@ export function getAvailabilityPriceForPurchaseOption(
|
||||
purchaseOption: PurchaseOption
|
||||
): (state: PurchaseOptionsState) => (PriceDTO & { fromCatalogue?: boolean }) | undefined {
|
||||
return (state) => {
|
||||
if (getCanEditPrice(itemId)(state)) {
|
||||
const price = getPrices(state)[itemId];
|
||||
if (price) {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
|
||||
const item = getItems(state).find((item) => item.id === itemId);
|
||||
const type = getType(state);
|
||||
let availabilities = getAvailabilitiesForItem(itemId)(state);
|
||||
|
||||
@@ -8,11 +8,7 @@
|
||||
<div class="hr"></div>
|
||||
<ui-datepicker-body></ui-datepicker-body>
|
||||
<div class="text-center mb-px-10">
|
||||
<button
|
||||
*ngIf="!content"
|
||||
class="rounded-full font-bold text-white bg-brand py-px-15 px-px-25"
|
||||
(click)="save.emit(selectedDate); close()"
|
||||
>
|
||||
<button *ngIf="!content" class="rounded-full font-bold text-white bg-brand py-px-15 px-px-25" (click)="onSave()">
|
||||
<ng-container>{{ saveLabel }}</ng-container>
|
||||
</button>
|
||||
<ng-content></ng-content>
|
||||
|
||||
@@ -85,9 +85,8 @@ export class UiDatepickerComponent extends Datepicker implements UiOverlayTrigge
|
||||
}
|
||||
|
||||
this.setSelected(date, { emit: false });
|
||||
if (isDate(date)) {
|
||||
this.setDisplayed(date, { emit: false });
|
||||
}
|
||||
|
||||
this.setDisplayed(date ?? new Date(), { emit: false });
|
||||
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
@@ -101,4 +100,11 @@ export class UiDatepickerComponent extends Datepicker implements UiOverlayTrigge
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {}
|
||||
|
||||
onSave() {
|
||||
this.save.emit(this.selectedDate);
|
||||
this.onChange(this.selectedDate);
|
||||
this.onTouched();
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EventEmitter, Input, Output, Directive } from '@angular/core';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
interface SetPropertyOptions {
|
||||
emit: boolean;
|
||||
@@ -26,6 +27,7 @@ export abstract class Datepicker {
|
||||
return this.displayedSubject.value;
|
||||
}
|
||||
set displayed(val: Date) {
|
||||
console.log('setDisplayed', val);
|
||||
this.setDisplayed(val, { emit: false });
|
||||
}
|
||||
@Output()
|
||||
@@ -65,7 +67,7 @@ export abstract class Datepicker {
|
||||
return this.selectedSubject.asObservable();
|
||||
}
|
||||
get displayed$() {
|
||||
return this.displayedSubject.asObservable();
|
||||
return this.displayedSubject.asObservable()?.pipe(map((date) => date ?? this.dateAdapter.today()));
|
||||
}
|
||||
get min$() {
|
||||
return this.minSubject.asObservable();
|
||||
|
||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -34,6 +34,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"ng2-pdf-viewer": "^9.1.3",
|
||||
"parse-duration": "^1.1.0",
|
||||
"rxjs": "^6.6.7",
|
||||
"scandit-sdk": "^5.13.2",
|
||||
"socket.io": "^4.5.4",
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"ng2-pdf-viewer": "^9.1.3",
|
||||
"parse-duration": "^1.1.0",
|
||||
"rxjs": "^6.6.7",
|
||||
"scandit-sdk": "^5.13.2",
|
||||
"socket.io": "^4.5.4",
|
||||
|
||||
Reference in New Issue
Block a user