mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Compare commits
26 Commits
feature/51
...
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,
|
StoreCheckoutPayerService,
|
||||||
StoreCheckoutBranchService,
|
StoreCheckoutBranchService,
|
||||||
ItemsResult,
|
ItemsResult,
|
||||||
|
ShoppingCartItemDTO,
|
||||||
} from '@swagger/checkout';
|
} from '@swagger/checkout';
|
||||||
import {
|
import {
|
||||||
DisplayOrderDTO,
|
DisplayOrderDTO,
|
||||||
@@ -36,20 +37,45 @@ import {
|
|||||||
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
|
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
|
||||||
} from '@swagger/oms';
|
} from '@swagger/oms';
|
||||||
import { isNullOrUndefined, memorize } from '@utils/common';
|
import { isNullOrUndefined, memorize } from '@utils/common';
|
||||||
import { combineLatest, Observable, of, concat, isObservable, throwError } from 'rxjs';
|
import { combineLatest, Observable, of, concat, isObservable, throwError, interval, zip, EMPTY, Subscription } from 'rxjs';
|
||||||
import { bufferCount, catchError, filter, first, map, mergeMap, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
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 DomainCheckoutSelectors from './store/domain-checkout.selectors';
|
||||||
import * as DomainCheckoutActions from './store/domain-checkout.actions';
|
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 { HttpErrorResponse } from '@angular/common/http';
|
||||||
import { ApplicationService } from '@core/application';
|
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()
|
@Injectable()
|
||||||
export class DomainCheckoutService {
|
export class DomainCheckoutService {
|
||||||
|
get olaExpiration() {
|
||||||
|
const exp = this._config.get('@domain/checkout.olaExpiration') ?? '5m';
|
||||||
|
return parseDuration(exp);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<any>,
|
private store: Store<any>,
|
||||||
|
private _config: Config,
|
||||||
private applicationService: ApplicationService,
|
private applicationService: ApplicationService,
|
||||||
private storeCheckoutService: StoreCheckoutService,
|
private storeCheckoutService: StoreCheckoutService,
|
||||||
private orderCheckoutService: OrderCheckoutService,
|
private orderCheckoutService: OrderCheckoutService,
|
||||||
@@ -119,14 +145,14 @@ export class DomainCheckoutService {
|
|||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
map((response) => response.result),
|
map((response) => response.result),
|
||||||
tap((shoppingCart) =>
|
tap((shoppingCart) => {
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
DomainCheckoutActions.setShoppingCart({
|
DomainCheckoutActions.setShoppingCart({
|
||||||
processId,
|
processId,
|
||||||
shoppingCart,
|
shoppingCart,
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
),
|
}),
|
||||||
tap((shoppingCart) => this.updateProcessCount(processId, shoppingCart?.items?.length))
|
tap((shoppingCart) => this.updateProcessCount(processId, shoppingCart?.items?.length))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -249,11 +275,24 @@ export class DomainCheckoutService {
|
|||||||
shoppingCartItemId: number;
|
shoppingCartItemId: number;
|
||||||
availability: AvailabilityDTO;
|
availability: AvailabilityDTO;
|
||||||
}) {
|
}) {
|
||||||
return this._shoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItemAvailability({
|
return this._shoppingCartService
|
||||||
shoppingCartId,
|
.StoreCheckoutShoppingCartUpdateShoppingCartItemAvailability({
|
||||||
shoppingCartItemId,
|
shoppingCartId,
|
||||||
availability,
|
shoppingCartItemId,
|
||||||
});
|
availability,
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
map((response) => response.result),
|
||||||
|
tap((shoppingCart) => {
|
||||||
|
this.store.dispatch(
|
||||||
|
DomainCheckoutActions.addShoppingCartItemAvailabilityToHistoryByShoppingCartId({
|
||||||
|
shoppingCartId,
|
||||||
|
availability,
|
||||||
|
shoppingCartItemId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateItemInShoppingCart({
|
updateItemInShoppingCart({
|
||||||
@@ -265,7 +304,7 @@ export class DomainCheckoutService {
|
|||||||
shoppingCartItemId: number;
|
shoppingCartItemId: number;
|
||||||
update: UpdateShoppingCartItemDTO;
|
update: UpdateShoppingCartItemDTO;
|
||||||
}): Observable<ShoppingCartDTO> {
|
}): Observable<ShoppingCartDTO> {
|
||||||
return this.getShoppingCart({ processId }).pipe(
|
return this.getShoppingCart({ processId, latest: true }).pipe(
|
||||||
first(),
|
first(),
|
||||||
mergeMap((shoppingCart) =>
|
mergeMap((shoppingCart) =>
|
||||||
this._shoppingCartService
|
this._shoppingCartService
|
||||||
@@ -276,8 +315,21 @@ export class DomainCheckoutService {
|
|||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
map((response) => response.result),
|
map((response) => response.result),
|
||||||
tap((shoppingCart) => this.store.dispatch(DomainCheckoutActions.setShoppingCart({ processId, shoppingCart }))),
|
tap((shoppingCart) => {
|
||||||
tap((shoppingCart) => this.updateProcessCount(processId, shoppingCart?.items?.length))
|
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[]> {
|
completeCheckout({ processId }: { processId: number }): Observable<DisplayOrderDTO[]> {
|
||||||
const refreshShoppingCart$ = this.getShoppingCart({ processId, latest: true }).pipe(first());
|
const refreshShoppingCart$ = this.getShoppingCart({ processId, latest: true }).pipe(first());
|
||||||
const refreshCheckout$ = this.getCheckout({ processId, refresh: true }).pipe(first());
|
const refreshCheckout$ = this.getCheckout({ processId, refresh: true }).pipe(first());
|
||||||
@@ -700,21 +918,23 @@ export class DomainCheckoutService {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return updateDestination$
|
return of(undefined)
|
||||||
.pipe(tap(console.log.bind(window, 'updateDestination$')))
|
|
||||||
.pipe(
|
.pipe(
|
||||||
|
mergeMap((_) => updateDestination$.pipe(tap(console.log.bind(window, 'updateDestination$')))),
|
||||||
mergeMap((_) => refreshShoppingCart$.pipe(tap(console.log.bind(window, 'refreshShoppingCart$')))),
|
mergeMap((_) => refreshShoppingCart$.pipe(tap(console.log.bind(window, 'refreshShoppingCart$')))),
|
||||||
mergeMap((_) => setSpecialComment$.pipe(tap(console.log.bind(window, 'setSpecialComment$')))),
|
mergeMap((_) => setSpecialComment$.pipe(tap(console.log.bind(window, 'setSpecialComment$')))),
|
||||||
mergeMap((_) => refreshCheckout$.pipe(tap(console.log.bind(window, 'refreshCheckout$')))),
|
mergeMap((_) => refreshCheckout$.pipe(tap(console.log.bind(window, 'refreshCheckout$')))),
|
||||||
mergeMap((_) => checkAvailabilities$.pipe(tap(console.log.bind(window, 'checkAvailabilities$')))),
|
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((_) => setBuyer$.pipe(tap(console.log.bind(window, 'setBuyer$')))),
|
||||||
mergeMap((_) => setNotificationChannels$.pipe(tap(console.log.bind(window, 'setNotificationChannels$')))),
|
mergeMap((_) => setNotificationChannels$.pipe(tap(console.log.bind(window, 'setNotificationChannels$')))),
|
||||||
mergeMap((_) => setPayer$.pipe(tap(console.log.bind(window, 'setPayer$')))),
|
mergeMap((_) => setPayer$.pipe(tap(console.log.bind(window, 'setPayer$')))),
|
||||||
mergeMap((_) => setPaymentType$.pipe(tap(console.log.bind(window, 'setPaymentType$')))),
|
mergeMap((_) => setPaymentType$.pipe(tap(console.log.bind(window, 'setPaymentType$')))),
|
||||||
mergeMap((_) => setDestination$.pipe(tap(console.log.bind(window, 'setDestination$'))))
|
mergeMap((_) => setDestination$.pipe(tap(console.log.bind(window, 'setDestination$')))),
|
||||||
)
|
mergeMap((_) => completeOrder$.pipe(tap(console.log.bind(window, 'completeOrder$'))))
|
||||||
.pipe(mergeMap((_) => completeOrder$.pipe(tap(console.log.bind(window, 'completeOrder$')))));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
completeKulturpassOrder({
|
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 { CustomerDTO } from '@swagger/crm';
|
||||||
import { DisplayOrderDTO } from '@swagger/oms';
|
import { DisplayOrderDTO } from '@swagger/oms';
|
||||||
|
|
||||||
@@ -14,4 +22,5 @@ export interface CheckoutEntity {
|
|||||||
specialComment: string;
|
specialComment: string;
|
||||||
notificationChannels: NotificationChannel;
|
notificationChannels: NotificationChannel;
|
||||||
olaErrorIds: number[];
|
olaErrorIds: number[];
|
||||||
|
itemAvailabilityTimestamp: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ShippingAddressDTO,
|
ShippingAddressDTO,
|
||||||
BuyerDTO,
|
BuyerDTO,
|
||||||
PayerDTO,
|
PayerDTO,
|
||||||
|
AvailabilityDTO,
|
||||||
} from '@swagger/checkout';
|
} from '@swagger/checkout';
|
||||||
import { CustomerDTO } from '@swagger/crm';
|
import { CustomerDTO } from '@swagger/crm';
|
||||||
import { DisplayOrderDTO, DisplayOrderItemDTO } from '@swagger/oms';
|
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 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 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,
|
initialCheckoutState,
|
||||||
on(DomainCheckoutActions.setShoppingCart, (s, { processId, shoppingCart }) => {
|
on(DomainCheckoutActions.setShoppingCart, (s, { processId, shoppingCart }) => {
|
||||||
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
|
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.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);
|
return storeCheckoutAdapter.setOne(entity, s);
|
||||||
}),
|
}),
|
||||||
on(DomainCheckoutActions.setCheckout, (s, { processId, checkout }) => {
|
on(DomainCheckoutActions.setCheckout, (s, { processId, checkout }) => {
|
||||||
@@ -100,7 +115,40 @@ const _domainCheckoutReducer = createReducer(
|
|||||||
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
|
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
|
||||||
entity.customer = customer;
|
entity.customer = customer;
|
||||||
return storeCheckoutAdapter.setOne(entity, s);
|
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) {
|
export function domainCheckoutReducer(state, action) {
|
||||||
@@ -123,8 +171,20 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
|
|||||||
notificationChannels: 0,
|
notificationChannels: 0,
|
||||||
olaErrorIds: [],
|
olaErrorIds: [],
|
||||||
customer: undefined,
|
customer: undefined,
|
||||||
|
// availabilityHistory: [],
|
||||||
|
itemAvailabilityTimestamp: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...entity };
|
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": {
|
"@core/logger": {
|
||||||
"logLevel": "debug"
|
"logLevel": "debug"
|
||||||
},
|
},
|
||||||
|
"@domain/checkout": {
|
||||||
|
"olaExpiration": "5m"
|
||||||
|
},
|
||||||
"@swagger/isa": {
|
"@swagger/isa": {
|
||||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
"@core/logger": {
|
"@core/logger": {
|
||||||
"logLevel": "debug"
|
"logLevel": "debug"
|
||||||
},
|
},
|
||||||
|
"@domain/checkout": {
|
||||||
|
"olaExpiration": "5m"
|
||||||
|
},
|
||||||
"@swagger/isa": {
|
"@swagger/isa": {
|
||||||
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
|
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -47,6 +47,9 @@
|
|||||||
"@swagger/wws": {
|
"@swagger/wws": {
|
||||||
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
|
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
|
||||||
},
|
},
|
||||||
|
"@domain/checkout": {
|
||||||
|
"olaExpiration": "30s"
|
||||||
|
},
|
||||||
"hubs": {
|
"hubs": {
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
"@core/logger": {
|
"@core/logger": {
|
||||||
"logLevel": "debug"
|
"logLevel": "debug"
|
||||||
},
|
},
|
||||||
|
"@domain/checkout": {
|
||||||
|
"olaExpiration": "5m"
|
||||||
|
},
|
||||||
"@swagger/isa": {
|
"@swagger/isa": {
|
||||||
"rootUrl": "https://isa.paragon-systems.de/isa/v1"
|
"rootUrl": "https://isa.paragon-systems.de/isa/v1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
"@core/logger": {
|
"@core/logger": {
|
||||||
"logLevel": "debug"
|
"logLevel": "debug"
|
||||||
},
|
},
|
||||||
|
"@domain/checkout": {
|
||||||
|
"olaExpiration": "5m"
|
||||||
|
},
|
||||||
"@swagger/isa": {
|
"@swagger/isa": {
|
||||||
"rootUrl": "https://isa-staging.paragon-systems.de/isa/v1"
|
"rootUrl": "https://isa-staging.paragon-systems.de/isa/v1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
"@core/logger": {
|
"@core/logger": {
|
||||||
"logLevel": "debug"
|
"logLevel": "debug"
|
||||||
},
|
},
|
||||||
|
"@domain/checkout": {
|
||||||
|
"olaExpiration": "5m"
|
||||||
|
},
|
||||||
"@swagger/isa": {
|
"@swagger/isa": {
|
||||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
"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">
|
<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' }}
|
{{ price?.value?.value | currency: price?.value?.currency:'code' }}
|
||||||
</div>
|
</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">
|
<div class="page-article-details__product-points self-end" *ngIf="store.promotionPoints$ | async; let promotionPoints">
|
||||||
{{ promotionPoints }} Lesepunkte
|
{{ promotionPoints }} Lesepunkte
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TODO: Ticket PREISGEBUNDEN -->
|
|
||||||
<div class="page-article-details__product-price-bound self-end"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="page-article-details__product-origin-infos flex flex-col mb-4">
|
<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" />
|
<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 #description class="page-article-details__product-description flex flex-col flex-grow" *ngIf="item.texts?.length > 0">
|
||||||
<div class="whitespace-pre-line">
|
<page-article-details-text [text]="item.texts[0]"> </page-article-details-text>
|
||||||
{{ item.texts[0].value }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="font-bold flex flex-row text-[#0556B4] items-center mt-2"
|
class="font-bold flex flex-row text-[#0556B4] items-center mt-2"
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
'image contributors contributors contributors'
|
'image contributors contributors contributors'
|
||||||
'image title title print'
|
'image title title print'
|
||||||
'image title title .'
|
'image title title .'
|
||||||
'image misc misc price'
|
'image misc price price'
|
||||||
'image misc misc price'
|
'image misc price price'
|
||||||
'image origin origin stock'
|
'image origin origin stock'
|
||||||
'image origin origin stock'
|
'image origin origin stock'
|
||||||
'image specs availabilities availabilities'
|
'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([
|
price$ = combineLatest([
|
||||||
this.store.item$,
|
this.store.item$,
|
||||||
this.store.takeAwayAvailability$,
|
this.store.takeAwayAvailability$,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { UiTooltipModule } from '@ui/tooltip';
|
|||||||
import { UiCommonModule } from '@ui/common';
|
import { UiCommonModule } from '@ui/common';
|
||||||
import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
|
import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
|
||||||
import { IconModule } from '@shared/components/icon';
|
import { IconModule } from '@shared/components/icon';
|
||||||
|
import { ArticleDetailsTextComponent } from './article-details-text/article-details-text.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -26,6 +27,7 @@ import { IconModule } from '@shared/components/icon';
|
|||||||
IconModule,
|
IconModule,
|
||||||
PipesModule,
|
PipesModule,
|
||||||
OrderDeadlinePipeModule,
|
OrderDeadlinePipeModule,
|
||||||
|
ArticleDetailsTextComponent,
|
||||||
],
|
],
|
||||||
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||||
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cta-wrapper {
|
.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,
|
.cta-reset-filter,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { TrimPipe } from './trim.pipe';
|
import { TrimPipe } from './trim.pipe';
|
||||||
|
import { VatPipe } from './vat.pipe';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [],
|
imports: [],
|
||||||
exports: [TrimPipe],
|
exports: [TrimPipe, VatPipe],
|
||||||
declarations: [TrimPipe],
|
declarations: [TrimPipe, VatPipe],
|
||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
export class PipesModule {}
|
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]="
|
[disabled]="
|
||||||
showOrderButtonSpinner ||
|
showOrderButtonSpinner ||
|
||||||
((primaryCtaLabel$ | async) === 'Bestellen' && !(checkNotificationChannelControl$ | async)) ||
|
((primaryCtaLabel$ | async) === 'Bestellen' && !(checkNotificationChannelControl$ | async)) ||
|
||||||
notificationsControl?.invalid
|
notificationsControl?.invalid ||
|
||||||
|
((primaryCtaLabel$ | async) === 'Bestellen' && ((checkingOla$ | async) || (checkoutIsInValid$ | async)))
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<ui-spinner [show]="showOrderButtonSpinner">
|
<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 { Router } from '@angular/router';
|
||||||
import { ApplicationService } from '@core/application';
|
import { ApplicationService } from '@core/application';
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
import { DomainAvailabilityService } from '@domain/availability';
|
||||||
@@ -6,8 +6,8 @@ import { DomainCheckoutService } from '@domain/checkout';
|
|||||||
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||||
import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
||||||
import { first, map, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
|
import { catchError, debounceTime, delay, first, map, shareReplay, switchMap, take, takeUntil, tap } from 'rxjs/operators';
|
||||||
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
|
import { Subject, NEVER, combineLatest, BehaviorSubject, interval, of, merge } from 'rxjs';
|
||||||
import { DomainCatalogService } from '@domain/catalog';
|
import { DomainCatalogService } from '@domain/catalog';
|
||||||
import { BreadcrumbService } from '@core/breadcrumb';
|
import { BreadcrumbService } from '@core/breadcrumb';
|
||||||
import { DomainPrinterService } from '@domain/printer';
|
import { DomainPrinterService } from '@domain/printer';
|
||||||
@@ -17,6 +17,8 @@ import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-mod
|
|||||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||||
import { EnvironmentService } from '@core/environment';
|
import { EnvironmentService } from '@core/environment';
|
||||||
import { CheckoutReviewStore } from './checkout-review.store';
|
import { CheckoutReviewStore } from './checkout-review.store';
|
||||||
|
import { ToasterService } from '@shared/shell';
|
||||||
|
import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-item.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-checkout-review',
|
selector: 'page-checkout-review',
|
||||||
@@ -25,6 +27,8 @@ import { CheckoutReviewStore } from './checkout-review.store';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||||
|
checkingOla$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
payer$ = this._store.payer$;
|
payer$ = this._store.payer$;
|
||||||
|
|
||||||
buyer$ = this._store.buyer$;
|
buyer$ = this._store.buyer$;
|
||||||
@@ -149,6 +153,11 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
|||||||
loadingOnQuantityChangeById$ = new Subject<number>();
|
loadingOnQuantityChangeById$ = new Subject<number>();
|
||||||
showOrderButtonSpinner: boolean;
|
showOrderButtonSpinner: boolean;
|
||||||
|
|
||||||
|
checkoutIsInValid$ = this.applicationService.activatedProcessId$.pipe(
|
||||||
|
switchMap((processId) => this.domainCheckoutService.checkoutIsValid({ processId })),
|
||||||
|
map((valid) => !valid)
|
||||||
|
);
|
||||||
|
|
||||||
get productSearchBasePath() {
|
get productSearchBasePath() {
|
||||||
return this._productNavigationService.getArticleSearchBasePath(this.applicationService.activatedProcessId);
|
return this._productNavigationService.getArticleSearchBasePath(this.applicationService.activatedProcessId);
|
||||||
}
|
}
|
||||||
@@ -159,6 +168,9 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private _onDestroy$ = new Subject<void>();
|
private _onDestroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
@ViewChildren(ShoppingCartItemComponent)
|
||||||
|
private _shoppingCartItems: QueryList<ShoppingCartItemComponent>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private domainCheckoutService: DomainCheckoutService,
|
private domainCheckoutService: DomainCheckoutService,
|
||||||
public applicationService: ApplicationService,
|
public applicationService: ApplicationService,
|
||||||
@@ -173,7 +185,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
|||||||
private _productNavigationService: ProductCatalogNavigationService,
|
private _productNavigationService: ProductCatalogNavigationService,
|
||||||
private _navigationService: CheckoutNavigationService,
|
private _navigationService: CheckoutNavigationService,
|
||||||
private _environmentService: EnvironmentService,
|
private _environmentService: EnvironmentService,
|
||||||
private _store: CheckoutReviewStore
|
private _store: CheckoutReviewStore,
|
||||||
|
private _toaster: ToasterService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -183,6 +196,12 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
await this.removeBreadcrumbs();
|
await this.removeBreadcrumbs();
|
||||||
await this.updateBreadcrumb();
|
await this.updateBreadcrumb();
|
||||||
|
|
||||||
|
this.registerOlaCechk();
|
||||||
|
|
||||||
|
window['Checkout'] = {
|
||||||
|
refreshAvailabilities: this.refreshAvailabilities.bind(this),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@@ -191,6 +210,32 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
|||||||
this._onDestroy$.complete();
|
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() {
|
async updateBreadcrumb() {
|
||||||
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||||
key: this.applicationService.activatedProcessId,
|
key: this.applicationService.activatedProcessId,
|
||||||
@@ -476,6 +521,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
|||||||
title: 'Hinweis',
|
title: 'Hinweis',
|
||||||
data: { message: message.trim() },
|
data: { message: message.trim() },
|
||||||
});
|
});
|
||||||
|
} else if (error) {
|
||||||
|
this.uiModal.error('Fehler beim abschließen der Bestellung', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.status === 409) {
|
if (error.status === 409) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { CheckoutReviewDetailsComponent } from './details/checkout-review-detail
|
|||||||
import { CheckoutReviewStore } from './checkout-review.store';
|
import { CheckoutReviewStore } from './checkout-review.store';
|
||||||
import { IconModule } from '@shared/components/icon';
|
import { IconModule } from '@shared/components/icon';
|
||||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||||
|
import { LoaderComponent } from '@shared/components/loader';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -39,6 +40,7 @@ import { TextFieldModule } from '@angular/cdk/text-field';
|
|||||||
UiCheckboxModule,
|
UiCheckboxModule,
|
||||||
SharedNotificationChannelControlModule,
|
SharedNotificationChannelControlModule,
|
||||||
TextFieldModule,
|
TextFieldModule,
|
||||||
|
LoaderComponent,
|
||||||
],
|
],
|
||||||
exports: [CheckoutReviewComponent, CheckoutReviewDetailsComponent],
|
exports: [CheckoutReviewComponent, CheckoutReviewDetailsComponent],
|
||||||
declarations: [CheckoutReviewComponent, SpecialCommentComponent, ShoppingCartItemComponent, CheckoutReviewDetailsComponent],
|
declarations: [CheckoutReviewComponent, SpecialCommentComponent, ShoppingCartItemComponent, CheckoutReviewDetailsComponent],
|
||||||
|
|||||||
@@ -40,16 +40,26 @@
|
|||||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||||
{{ item?.product?.publicationDate | date }}
|
{{ item?.product?.publicationDate | date }}
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="notAvailable$ | async">
|
||||||
<div class="item-date" *ngIf="orderType === 'Abholung'">Abholung ab {{ item?.availability?.estimatedShippingDate | date }}</div>
|
<span class="text-brand item-date">Nicht verfügbar</span>
|
||||||
<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>
|
</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">
|
<div class="item-availability-message" *ngIf="olaError$ | async">
|
||||||
Artikel nicht verfügbar
|
Artikel nicht verfügbar
|
||||||
|
|||||||
@@ -101,8 +101,16 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ssc-changed {
|
||||||
|
@apply text-dark-goldenrod;
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep page-shopping-cart-item ui-quantity-dropdown {
|
::ng-deep page-shopping-cart-item ui-quantity-dropdown {
|
||||||
.current-quantity {
|
.current-quantity {
|
||||||
font-weight: normal !important;
|
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 { ApplicationService } from '@core/application';
|
||||||
import { EnvironmentService } from '@core/environment';
|
import { EnvironmentService } from '@core/environment';
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
import { DomainAvailabilityService } from '@domain/availability';
|
||||||
@@ -6,6 +6,7 @@ import { DomainCheckoutService } from '@domain/checkout';
|
|||||||
import { ComponentStore } from '@ngrx/component-store';
|
import { ComponentStore } from '@ngrx/component-store';
|
||||||
import { ProductCatalogNavigationService } from '@shared/services';
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
import { ItemType, ShoppingCartItemDTO } from '@swagger/checkout';
|
import { ItemType, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
import { combineLatest } from 'rxjs';
|
import { combineLatest } from 'rxjs';
|
||||||
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -14,6 +15,9 @@ export interface ShoppingCartItemComponentState {
|
|||||||
orderType: string;
|
orderType: string;
|
||||||
loadingOnItemChangeById?: number;
|
loadingOnItemChangeById?: number;
|
||||||
loadingOnQuantityChangeById?: number;
|
loadingOnQuantityChangeById?: number;
|
||||||
|
refreshingAvailability: boolean;
|
||||||
|
sscChanged: boolean;
|
||||||
|
sscTextChanged: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -23,6 +27,8 @@ export interface ShoppingCartItemComponentState {
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemComponentState> implements OnInit {
|
export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemComponentState> implements OnInit {
|
||||||
|
private _zone = inject(NgZone);
|
||||||
|
|
||||||
@Output() changeItem = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO }>();
|
@Output() changeItem = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO }>();
|
||||||
@Output() changeDummyItem = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO }>();
|
@Output() changeDummyItem = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO }>();
|
||||||
@Output() changeQuantity = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO; quantity: number }>();
|
@Output() changeQuantity = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO; quantity: number }>();
|
||||||
@@ -127,14 +133,35 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
|||||||
return this._environment.matchTablet();
|
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(
|
constructor(
|
||||||
private availabilityService: DomainAvailabilityService,
|
private availabilityService: DomainAvailabilityService,
|
||||||
private checkoutService: DomainCheckoutService,
|
private checkoutService: DomainCheckoutService,
|
||||||
public application: ApplicationService,
|
public application: ApplicationService,
|
||||||
private _productNavigationService: ProductCatalogNavigationService,
|
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() {}
|
ngOnInit() {}
|
||||||
@@ -147,4 +174,48 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
|||||||
onChangeQuantity(quantity: number) {
|
onChangeQuantity(quantity: number) {
|
||||||
this.changeQuantity.emit({ shoppingCartItem: this.item, quantity });
|
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"
|
[attr.data-selected]="uiStartOption?.selected"
|
||||||
>
|
>
|
||||||
<shared-input-control>
|
<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>
|
<shared-input-control-suffix>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -27,7 +34,14 @@
|
|||||||
[attr.data-selected]="uiStopOption?.selected"
|
[attr.data-selected]="uiStopOption?.selected"
|
||||||
>
|
>
|
||||||
<shared-input-control>
|
<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>
|
<shared-input-control-suffix>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -40,25 +54,27 @@
|
|||||||
</shared-input-control-suffix>
|
</shared-input-control-suffix>
|
||||||
</shared-input-control>
|
</shared-input-control>
|
||||||
</shared-form-control>
|
</shared-form-control>
|
||||||
</div>
|
|
||||||
|
|
||||||
<ui-datepicker
|
<ui-datepicker
|
||||||
class="dp-left"
|
class="dp-left"
|
||||||
#dpStart
|
#dpStart
|
||||||
yPosition="below"
|
yPosition="below"
|
||||||
xPosition="after"
|
xPosition="after"
|
||||||
[ngModel]="uiStartOption?.value"
|
formControlName="start"
|
||||||
saveLabel="Übernehmen"
|
[max]="maxDate"
|
||||||
(save)="uiStartOption?.setValue($event)"
|
saveLabel="Übernehmen"
|
||||||
>
|
(save)="setStratValue($event)"
|
||||||
</ui-datepicker>
|
>
|
||||||
<ui-datepicker
|
</ui-datepicker>
|
||||||
class="dp-right"
|
<ui-datepicker
|
||||||
yPosition="below"
|
class="dp-right"
|
||||||
xPosition="after"
|
yPosition="below"
|
||||||
#dpStop
|
xPosition="after"
|
||||||
[ngModel]="uiStopOption?.value"
|
#dpStop
|
||||||
(save)="setStopValue($event)"
|
[min]="minDate"
|
||||||
saveLabel="Übernehmen"
|
formControlName="stop"
|
||||||
>
|
(save)="setStopValue($event)"
|
||||||
</ui-datepicker>
|
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 { Subscription } from 'rxjs';
|
||||||
import { IOption, Option } from '../../../tree';
|
import { IOption, Option } from '../../../tree';
|
||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { UiValidators } from '@ui/validators';
|
import { DateValidator } from '@shared/forms';
|
||||||
|
import { UiDatepickerComponent } from '@ui/datepicker';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'shared-input-option-date-range',
|
selector: 'shared-input-option-date-range',
|
||||||
@@ -11,11 +12,15 @@ import { UiValidators } from '@ui/validators';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class FilterInputOptionDateRangeComponent {
|
export class FilterInputOptionDateRangeComponent {
|
||||||
|
@ViewChild('dpStart', { static: true }) startDatepicker: UiDatepickerComponent;
|
||||||
|
|
||||||
|
@ViewChild('dpStop', { static: true }) stopDatepicker: UiDatepickerComponent;
|
||||||
|
|
||||||
private _options: Option[];
|
private _options: Option[];
|
||||||
|
|
||||||
formGroup = new FormGroup({
|
formGroup = new FormGroup({
|
||||||
start: new FormControl<Date>(undefined, UiValidators.date),
|
start: new FormControl<Date>(undefined, DateValidator),
|
||||||
stop: new FormControl<Date>(undefined, UiValidators.date),
|
stop: new FormControl<Date>(undefined, DateValidator),
|
||||||
});
|
});
|
||||||
|
|
||||||
get startControl(): FormControl<Date> {
|
get startControl(): FormControl<Date> {
|
||||||
@@ -47,6 +52,22 @@ export class FilterInputOptionDateRangeComponent {
|
|||||||
|
|
||||||
optionChangeSubscription: Subscription;
|
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) {}
|
constructor(private cdr: ChangeDetectorRef) {}
|
||||||
|
|
||||||
subscribeChanges() {
|
subscribeChanges() {
|
||||||
@@ -55,44 +76,33 @@ export class FilterInputOptionDateRangeComponent {
|
|||||||
this.formGroup.patchValue({ start: (this.uiStartOption.value as any) as Date });
|
this.formGroup.patchValue({ start: (this.uiStartOption.value as any) as Date });
|
||||||
|
|
||||||
this.optionChangeSubscription.add(
|
this.optionChangeSubscription.add(
|
||||||
this.uiStartOption.changes.subscribe(() => {
|
this.uiStartOption.changes.subscribe(({ target, keys }) => {
|
||||||
if (new Date(this.uiStartOption.value) !== new Date(this.formGroup.get('start').value)) {
|
if (keys.includes('value')) {
|
||||||
this.formGroup.patchValue({ start: (this.uiStartOption.value as any) as Date });
|
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.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) {
|
if (this.uiStopOption) {
|
||||||
this.formGroup.patchValue({ stop: (this.uiStopOption.value as any) as Date });
|
this.formGroup.patchValue({ stop: (this.uiStopOption.value as any) as Date });
|
||||||
|
|
||||||
this.optionChangeSubscription.add(
|
this.optionChangeSubscription.add(
|
||||||
this.uiStopOption.changes.subscribe(() => {
|
this.uiStopOption.changes.subscribe(({ target, keys }) => {
|
||||||
if (new Date(this.uiStopOption.value) !== new Date(this.formGroup.get('stop').value)) {
|
if (keys.includes('value')) {
|
||||||
this.formGroup.patchValue({ stop: (this.uiStopOption.value as any) as Date });
|
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.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();
|
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) {
|
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);
|
const stopDate = new Date(date);
|
||||||
stopDate.setHours(23, 59, 59, 999);
|
stopDate.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
if (this.stopDate === date) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.uiStopOption?.setValue(stopDate);
|
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 { IconComponent } from '@shared/components/icon';
|
||||||
import { InputControlModule } from '@shared/components/input-control';
|
import { InputControlModule } from '@shared/components/input-control';
|
||||||
import { UiDateInputDirective } from '@ui/input';
|
import { UiDateInputDirective } from '@ui/input';
|
||||||
|
import { DateInputDirective } from '@shared/forms';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,6 +23,7 @@ import { UiDateInputDirective } from '@ui/input';
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
IconComponent,
|
IconComponent,
|
||||||
UiDateInputDirective,
|
UiDateInputDirective,
|
||||||
|
DateInputDirective,
|
||||||
],
|
],
|
||||||
exports: [FilterInputOptionDateRangeComponent],
|
exports: [FilterInputOptionDateRangeComponent],
|
||||||
declarations: [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`));
|
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.renderError();
|
||||||
this.renderIndicator();
|
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.helpers';
|
||||||
export * from './purchase-options.selectors';
|
|
||||||
export * from './purchase-options.service';
|
export * from './purchase-options.service';
|
||||||
export * from './purchase-options.state';
|
export * from './purchase-options.state';
|
||||||
export * from './purchase-options.store';
|
export * from './purchase-options.store';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PriceDTO, PriceValueDTO } from '@swagger/checkout';
|
import { PriceDTO } from '@swagger/checkout';
|
||||||
import { DEFAULT_PRICE_DTO, DEFAULT_PRICE_VALUE, GIFT_CARD_MAX_PRICE, GIFT_CARD_TYPE, PURCHASE_OPTIONS } from '../constants';
|
import { DEFAULT_PRICE_DTO, GIFT_CARD_MAX_PRICE, PURCHASE_OPTIONS } from '../constants';
|
||||||
import { isArchive, isGiftCard, isItemDTO } from './purchase-options.helpers';
|
import { isGiftCard, isItemDTO } from './purchase-options.helpers';
|
||||||
import { PurchaseOptionsState } from './purchase-options.state';
|
import { PurchaseOptionsState } from './purchase-options.state';
|
||||||
import { ActionType, Availability, Branch, CanAdd, FetchingAvailability, Item, PurchaseOption } from './purchase-options.types';
|
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 {
|
export function getCanEditPrice(itemId: number): (state: PurchaseOptionsState) => boolean {
|
||||||
return (state) => {
|
return (state) => {
|
||||||
const item = getItems(state).find((item) => item.id === itemId);
|
const item = getItems(state).find((item) => item.id === itemId);
|
||||||
@@ -241,6 +249,13 @@ export function getAvailabilityPriceForPurchaseOption(
|
|||||||
purchaseOption: PurchaseOption
|
purchaseOption: PurchaseOption
|
||||||
): (state: PurchaseOptionsState) => (PriceDTO & { fromCatalogue?: boolean }) | undefined {
|
): (state: PurchaseOptionsState) => (PriceDTO & { fromCatalogue?: boolean }) | undefined {
|
||||||
return (state) => {
|
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 item = getItems(state).find((item) => item.id === itemId);
|
||||||
const type = getType(state);
|
const type = getType(state);
|
||||||
let availabilities = getAvailabilitiesForItem(itemId)(state);
|
let availabilities = getAvailabilitiesForItem(itemId)(state);
|
||||||
|
|||||||
@@ -8,11 +8,7 @@
|
|||||||
<div class="hr"></div>
|
<div class="hr"></div>
|
||||||
<ui-datepicker-body></ui-datepicker-body>
|
<ui-datepicker-body></ui-datepicker-body>
|
||||||
<div class="text-center mb-px-10">
|
<div class="text-center mb-px-10">
|
||||||
<button
|
<button *ngIf="!content" class="rounded-full font-bold text-white bg-brand py-px-15 px-px-25" (click)="onSave()">
|
||||||
*ngIf="!content"
|
|
||||||
class="rounded-full font-bold text-white bg-brand py-px-15 px-px-25"
|
|
||||||
(click)="save.emit(selectedDate); close()"
|
|
||||||
>
|
|
||||||
<ng-container>{{ saveLabel }}</ng-container>
|
<ng-container>{{ saveLabel }}</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
|
|||||||
@@ -85,9 +85,8 @@ export class UiDatepickerComponent extends Datepicker implements UiOverlayTrigge
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setSelected(date, { emit: false });
|
this.setSelected(date, { emit: false });
|
||||||
if (isDate(date)) {
|
|
||||||
this.setDisplayed(date, { emit: false });
|
this.setDisplayed(date ?? new Date(), { emit: false });
|
||||||
}
|
|
||||||
|
|
||||||
this._cdr.markForCheck();
|
this._cdr.markForCheck();
|
||||||
}
|
}
|
||||||
@@ -101,4 +100,11 @@ export class UiDatepickerComponent extends Datepicker implements UiOverlayTrigge
|
|||||||
}
|
}
|
||||||
|
|
||||||
setDisabledState?(isDisabled: boolean): void {}
|
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 { EventEmitter, Input, Output, Directive } from '@angular/core';
|
||||||
import { DateAdapter } from '@ui/common';
|
import { DateAdapter } from '@ui/common';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
interface SetPropertyOptions {
|
interface SetPropertyOptions {
|
||||||
emit: boolean;
|
emit: boolean;
|
||||||
@@ -26,6 +27,7 @@ export abstract class Datepicker {
|
|||||||
return this.displayedSubject.value;
|
return this.displayedSubject.value;
|
||||||
}
|
}
|
||||||
set displayed(val: Date) {
|
set displayed(val: Date) {
|
||||||
|
console.log('setDisplayed', val);
|
||||||
this.setDisplayed(val, { emit: false });
|
this.setDisplayed(val, { emit: false });
|
||||||
}
|
}
|
||||||
@Output()
|
@Output()
|
||||||
@@ -65,7 +67,7 @@ export abstract class Datepicker {
|
|||||||
return this.selectedSubject.asObservable();
|
return this.selectedSubject.asObservable();
|
||||||
}
|
}
|
||||||
get displayed$() {
|
get displayed$() {
|
||||||
return this.displayedSubject.asObservable();
|
return this.displayedSubject.asObservable()?.pipe(map((date) => date ?? this.dateAdapter.today()));
|
||||||
}
|
}
|
||||||
get min$() {
|
get min$() {
|
||||||
return this.minSubject.asObservable();
|
return this.minSubject.asObservable();
|
||||||
|
|||||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -34,6 +34,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"ng2-pdf-viewer": "^9.1.3",
|
"ng2-pdf-viewer": "^9.1.3",
|
||||||
|
"parse-duration": "^1.1.0",
|
||||||
"rxjs": "^6.6.7",
|
"rxjs": "^6.6.7",
|
||||||
"scandit-sdk": "^5.13.2",
|
"scandit-sdk": "^5.13.2",
|
||||||
"socket.io": "^4.5.4",
|
"socket.io": "^4.5.4",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"ng2-pdf-viewer": "^9.1.3",
|
"ng2-pdf-viewer": "^9.1.3",
|
||||||
|
"parse-duration": "^1.1.0",
|
||||||
"rxjs": "^6.6.7",
|
"rxjs": "^6.6.7",
|
||||||
"scandit-sdk": "^5.13.2",
|
"scandit-sdk": "^5.13.2",
|
||||||
"socket.io": "^4.5.4",
|
"socket.io": "^4.5.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user