mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merge branch 'release/1.7'
This commit is contained in:
40
angular.json
40
angular.json
@@ -3296,6 +3296,46 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ui/branch-dropdown": {
|
||||
"projectType": "library",
|
||||
"root": "apps/ui/branch-dropdown",
|
||||
"sourceRoot": "apps/ui/branch-dropdown/src",
|
||||
"prefix": "lib",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||
"options": {
|
||||
"tsConfig": "apps/ui/branch-dropdown/tsconfig.lib.json",
|
||||
"project": "apps/ui/branch-dropdown/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "apps/ui/branch-dropdown/tsconfig.lib.prod.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "apps/ui/branch-dropdown/src/test.ts",
|
||||
"tsConfig": "apps/ui/branch-dropdown/tsconfig.spec.json",
|
||||
"karmaConfig": "apps/ui/branch-dropdown/karma.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"apps/ui/branch-dropdown/tsconfig.lib.json",
|
||||
"apps/ui/branch-dropdown/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "sales"
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, StoreCheckoutService, SupplierDTO } from '@swagger/checkout';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AvailabilityService as SwaggerAvailabilityService } from '@swagger/availability';
|
||||
import { combineLatest, Observable, of } from 'rxjs';
|
||||
import {
|
||||
AvailabilityRequestDTO,
|
||||
AvailabilityService as SwaggerAvailabilityService,
|
||||
AvailabilityDTO as SwaggerAvailabilityDTO,
|
||||
AvailabilityType,
|
||||
} from '@swagger/availability';
|
||||
import { AvailabilityDTO as CatAvailabilityDTO } from '@swagger/cat';
|
||||
import { map, shareReplay, switchMap, withLatestFrom, mergeMap, timeout } from 'rxjs/operators';
|
||||
import { isArray, memorize } from '@utils/common';
|
||||
import { OrderService } from '@swagger/oms';
|
||||
@@ -60,6 +66,14 @@ export class DomainAvailabilityService {
|
||||
);
|
||||
}
|
||||
|
||||
@memorize({})
|
||||
getLogisticians() {
|
||||
return this.orderService.OrderGetLogisticians({}).pipe(
|
||||
map((response) => response.result?.find((l) => l.logisticianNumber === '2470')),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
getTakeAwayAvailabilityByBranches({
|
||||
branchIds,
|
||||
itemId,
|
||||
@@ -117,8 +131,10 @@ export class DomainAvailabilityService {
|
||||
price: PriceDTO;
|
||||
quantity: number;
|
||||
}): Observable<AvailabilityDTO> {
|
||||
return this._stock.StockStockRequest({ stockRequest: { branchIds: [branch.id], itemId } }).pipe(
|
||||
withLatestFrom(this.getTakeAwaySupplier()),
|
||||
return combineLatest([
|
||||
this._stock.StockStockRequest({ stockRequest: { branchIds: [branch.id], itemId } }),
|
||||
this.getTakeAwaySupplier(),
|
||||
]).pipe(
|
||||
map(([response, supplier]) => {
|
||||
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
|
||||
}),
|
||||
@@ -168,29 +184,7 @@ export class DomainAvailabilityService {
|
||||
},
|
||||
])
|
||||
.pipe(
|
||||
map((r) => {
|
||||
const availabilities = r.result;
|
||||
|
||||
if (isArray(availabilities)) {
|
||||
const preferred = availabilities.find((f) => f.preferred === 1);
|
||||
const totalAvailable = availabilities.reduce((sum, av) => sum + (av?.qty || 0), 0);
|
||||
|
||||
const availability: AvailabilityDTO = {
|
||||
availabilityType: preferred?.status,
|
||||
ssc: preferred?.ssc,
|
||||
sscText: preferred?.sscText,
|
||||
supplier: { id: preferred?.supplierId },
|
||||
isPrebooked: preferred?.isPrebooked,
|
||||
estimatedShippingDate: preferred?.at,
|
||||
price: preferred?.price,
|
||||
inStock: totalAvailable,
|
||||
supplierProductNumber: preferred?.supplierProductNumber,
|
||||
supplierInfo: preferred?.requestStatusCode,
|
||||
lastRequest: preferred?.requested,
|
||||
};
|
||||
return availability;
|
||||
}
|
||||
}),
|
||||
map((r) => this._mapToPickUpAvailability(r.result)?.find((_) => true)),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
@@ -208,23 +202,7 @@ export class DomainAvailabilityService {
|
||||
])
|
||||
.pipe(
|
||||
timeout(5000),
|
||||
map((r) => {
|
||||
const availabilities = r.result;
|
||||
const preferred = availabilities.find((f) => f.preferred === 1);
|
||||
|
||||
const availability: AvailabilityDTO = {
|
||||
availabilityType: preferred?.status,
|
||||
ssc: preferred?.ssc,
|
||||
sscText: preferred?.sscText,
|
||||
isPrebooked: preferred?.isPrebooked,
|
||||
estimatedShippingDate: preferred?.at,
|
||||
price: preferred?.price,
|
||||
supplierProductNumber: preferred?.supplierProductNumber,
|
||||
supplierInfo: preferred?.requestStatusCode,
|
||||
lastRequest: preferred?.requested,
|
||||
};
|
||||
return availability;
|
||||
}),
|
||||
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
@@ -244,7 +222,7 @@ export class DomainAvailabilityService {
|
||||
timeout(5000),
|
||||
map((r) => {
|
||||
const availabilities = r.result;
|
||||
const preferred = availabilities.find((f) => f.preferred === 1);
|
||||
const preferred = availabilities?.find((f) => f.preferred === 1);
|
||||
|
||||
const availability: AvailabilityDTO = {
|
||||
availabilityType: preferred?.status,
|
||||
@@ -252,7 +230,8 @@ export class DomainAvailabilityService {
|
||||
sscText: preferred?.sscText,
|
||||
supplier: { id: preferred?.supplierId },
|
||||
isPrebooked: preferred?.isPrebooked,
|
||||
estimatedShippingDate: preferred?.at,
|
||||
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
|
||||
estimatedDelivery: preferred?.estimatedDelivery,
|
||||
price: preferred?.price,
|
||||
logistician: { id: preferred?.logisticianId },
|
||||
supplierProductNumber: preferred?.supplierProductNumber,
|
||||
@@ -267,9 +246,7 @@ export class DomainAvailabilityService {
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
getB2bDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
|
||||
const logistician$ = this.orderService
|
||||
.OrderGetLogisticians({})
|
||||
.pipe(map((response) => response.result.find((l) => l.logisticianNumber === '2470')));
|
||||
const logistician$ = this.getLogisticians();
|
||||
|
||||
const currentBranch$ = this.getCurrentBranch();
|
||||
|
||||
@@ -298,7 +275,7 @@ export class DomainAvailabilityService {
|
||||
.pipe(
|
||||
map((r) => {
|
||||
const availabilities = r.result;
|
||||
const preferred = availabilities.find((f) => f.preferred === 1);
|
||||
const preferred = availabilities?.find((f) => f.preferred === 1);
|
||||
|
||||
const availability: AvailabilityDTO = {
|
||||
availabilityType: preferred?.status,
|
||||
@@ -306,7 +283,7 @@ export class DomainAvailabilityService {
|
||||
sscText: preferred?.sscText,
|
||||
supplier: { id: preferred?.supplierId },
|
||||
isPrebooked: preferred?.isPrebooked,
|
||||
estimatedShippingDate: preferred?.at,
|
||||
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
|
||||
price: preferred?.price,
|
||||
supplierProductNumber: preferred?.supplierProductNumber,
|
||||
logistician: { id: preferred?.logisticianId },
|
||||
@@ -320,18 +297,83 @@ export class DomainAvailabilityService {
|
||||
}
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
getStoreAvailabilities({ item, branch, quantity }: { item: ItemData; quantity: number; branch: BranchDTO }) {
|
||||
return this.swaggerAvailabilityService
|
||||
.AvailabilityStoreAvailability([
|
||||
{
|
||||
qty: quantity,
|
||||
ean: item?.ean,
|
||||
itemId: item?.itemId ? String(item?.itemId) : null,
|
||||
shopId: branch?.id,
|
||||
price: item?.price,
|
||||
},
|
||||
])
|
||||
.pipe(map((response) => response.result));
|
||||
getTakeAwayAvailabilities(items: { id: number; price: PriceDTO }[], branchId: number) {
|
||||
return this._stock.StockGetStocksByBranch({ branchId }).pipe(
|
||||
map((req) => req.result?.find((_) => true)?.id),
|
||||
switchMap((stockId) =>
|
||||
stockId
|
||||
? this._stock.StockInStock({ articleIds: items.map((i) => i.id), stockId })
|
||||
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO)
|
||||
),
|
||||
timeout(20000),
|
||||
withLatestFrom(this.getTakeAwaySupplier()),
|
||||
map(([response, supplier]) => {
|
||||
return response.result?.map((stockInfo) =>
|
||||
this._mapToTakeAwayAvailabilities({
|
||||
stockInfo,
|
||||
supplier,
|
||||
quantity: 1,
|
||||
price: items?.find((i) => i.id === stockInfo.itemId)?.price,
|
||||
})
|
||||
);
|
||||
}),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
getPickUpAvailabilities(payload: AvailabilityRequestDTO[], preferred?: boolean) {
|
||||
return this.swaggerAvailabilityService.AvailabilityStoreAvailability(payload).pipe(
|
||||
timeout(20000),
|
||||
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result))
|
||||
);
|
||||
}
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
||||
return this.swaggerAvailabilityService.AvailabilityShippingAvailability(payload).pipe(
|
||||
timeout(20000),
|
||||
map((response) => this._mapToShippingAvailability(response.result))
|
||||
);
|
||||
}
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
||||
return this.swaggerAvailabilityService.AvailabilityShippingAvailability(payload).pipe(
|
||||
timeout(20000),
|
||||
map((response) => this._mapToShippingAvailability(response.result))
|
||||
);
|
||||
}
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
getB2bDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
||||
const logistician$ = this.getLogisticians();
|
||||
|
||||
return this.getPickUpAvailabilities(payload, true).pipe(
|
||||
timeout(20000),
|
||||
switchMap((availability) =>
|
||||
logistician$.pipe(map((logistician) => ({ availability: [...availability], logistician: { id: logistician.id } })))
|
||||
),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
getPriceForAvailability(
|
||||
purchasingOption: string,
|
||||
catalogAvailability: CatAvailabilityDTO | AvailabilityDTO,
|
||||
availability: AvailabilityDTO
|
||||
): PriceDTO {
|
||||
switch (purchasingOption) {
|
||||
case 'take-away':
|
||||
return availability?.price || availability?.retailPrice;
|
||||
case 'delivery':
|
||||
case 'dig-delivery':
|
||||
if (catalogAvailability?.price?.value?.value < availability?.price?.value?.value) {
|
||||
return catalogAvailability?.price;
|
||||
}
|
||||
return availability?.price || catalogAvailability?.price;
|
||||
}
|
||||
return availability?.price;
|
||||
}
|
||||
|
||||
isAvailable({ availability }: { availability: AvailabilityDTO }) {
|
||||
@@ -377,7 +419,7 @@ export class DomainAvailabilityService {
|
||||
quantity: number;
|
||||
price: PriceDTO;
|
||||
}): AvailabilityDTO {
|
||||
const stockInfo = response.result.find((si) => si.branchId === branch.id);
|
||||
const stockInfo = response.result?.find((si) => si.branchId === branch.id);
|
||||
const inStock = stockInfo?.inStock ?? 0;
|
||||
const availability: AvailabilityDTO = {
|
||||
availabilityType: quantity <= inStock ? 1024 : 1, // 1024 (=Available)
|
||||
@@ -390,4 +432,73 @@ export class DomainAvailabilityService {
|
||||
};
|
||||
return availability;
|
||||
}
|
||||
|
||||
private _mapToTakeAwayAvailabilities({
|
||||
stockInfo,
|
||||
quantity,
|
||||
price,
|
||||
supplier,
|
||||
}: {
|
||||
stockInfo: StockInfoDTO;
|
||||
quantity: number;
|
||||
price: PriceDTO;
|
||||
supplier: SupplierDTO;
|
||||
}) {
|
||||
const inStock = stockInfo?.inStock ?? 0;
|
||||
|
||||
const availability = {
|
||||
itemId: stockInfo.itemId,
|
||||
availabilityType: quantity <= inStock ? (1024 as AvailabilityType) : (1 as AvailabilityType), // 1024 (=Available)
|
||||
inStock: inStock,
|
||||
supplierSSC: quantity <= inStock ? '999' : '',
|
||||
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
|
||||
price,
|
||||
supplier: { id: supplier?.id },
|
||||
};
|
||||
return availability;
|
||||
}
|
||||
|
||||
private _mapToPickUpAvailability(availabilities: SwaggerAvailabilityDTO[]) {
|
||||
if (isArray(availabilities)) {
|
||||
const preferred = availabilities.filter((f) => f.preferred === 1);
|
||||
const totalAvailable = availabilities.reduce((sum, av) => sum + (av?.qty || 0), 0);
|
||||
|
||||
return preferred.map((p) => {
|
||||
return {
|
||||
availabilityType: p?.status,
|
||||
ssc: p?.ssc,
|
||||
sscText: p?.sscText,
|
||||
supplier: { id: p?.supplierId },
|
||||
isPrebooked: p?.isPrebooked,
|
||||
estimatedShippingDate: p?.requestStatusCode === '32' ? p?.altAt : p?.at,
|
||||
price: p?.price,
|
||||
inStock: totalAvailable,
|
||||
supplierProductNumber: p?.supplierProductNumber,
|
||||
supplierInfo: p?.requestStatusCode,
|
||||
lastRequest: p?.requested,
|
||||
itemId: p.itemId,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _mapToShippingAvailability(availabilities: SwaggerAvailabilityDTO[]) {
|
||||
const preferred = availabilities.filter((f) => f.preferred === 1);
|
||||
|
||||
return preferred.map((p) => {
|
||||
return {
|
||||
availabilityType: p?.status,
|
||||
ssc: p?.ssc,
|
||||
sscText: p?.sscText,
|
||||
isPrebooked: p?.isPrebooked,
|
||||
estimatedShippingDate: p?.requestStatusCode === '32' ? p?.altAt : p?.at,
|
||||
estimatedDelivery: p?.estimatedDelivery,
|
||||
price: p?.price,
|
||||
supplierProductNumber: p?.supplierProductNumber,
|
||||
supplierInfo: p?.requestStatusCode,
|
||||
lastRequest: p?.requested,
|
||||
itemId: p.itemId,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ import {
|
||||
StoreCheckoutService,
|
||||
UpdateShoppingCartItemDTO,
|
||||
InputDTO,
|
||||
ItemPayload,
|
||||
} from '@swagger/checkout';
|
||||
import { DisplayOrderDTO, OrderCheckoutService, ReorderValues } 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, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { bufferCount, catchError, filter, first, map, mergeMap, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
import * as DomainCheckoutSelectors from './store/domain-checkout.selectors';
|
||||
import * as DomainCheckoutActions from './store/domain-checkout.actions';
|
||||
@@ -183,6 +184,48 @@ export class DomainCheckoutService {
|
||||
);
|
||||
}
|
||||
|
||||
canAddItems({ processId, payload, orderType }: { processId: number; payload: ItemPayload[]; orderType: string }) {
|
||||
return this.getShoppingCart({ processId }).pipe(
|
||||
first(),
|
||||
withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
|
||||
mergeMap(([shoppingCart, customerFeatures]) => {
|
||||
payload = payload?.map((p) => {
|
||||
return {
|
||||
...p,
|
||||
customerFeatures,
|
||||
orderType,
|
||||
};
|
||||
});
|
||||
return this.storeCheckoutService
|
||||
.StoreCheckoutCanAddItems({
|
||||
shoppingCartId: shoppingCart.id,
|
||||
payload,
|
||||
})
|
||||
.pipe(
|
||||
map((response) => {
|
||||
return response.result;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateShoppingCartItemAvailability({
|
||||
shoppingCartId,
|
||||
shoppingCartItemId,
|
||||
availability,
|
||||
}: {
|
||||
shoppingCartId: number;
|
||||
shoppingCartItemId: number;
|
||||
availability: AvailabilityDTO;
|
||||
}) {
|
||||
return this.storeCheckoutService.StoreCheckoutUpdateShoppingCartItemAvailability({
|
||||
shoppingCartId,
|
||||
shoppingCartItemId,
|
||||
availability,
|
||||
});
|
||||
}
|
||||
|
||||
updateItemInShoppingCart({
|
||||
processId,
|
||||
shoppingCartItemId,
|
||||
@@ -280,6 +323,10 @@ export class DomainCheckoutService {
|
||||
);
|
||||
}
|
||||
|
||||
getOlaErrors({ processId }: { processId: number }): Observable<number[]> {
|
||||
return this.store.select(DomainCheckoutSelectors.selectOlaErrorsByProcessId, { processId });
|
||||
}
|
||||
|
||||
setPayment({ processId, paymentType }: { processId: number; paymentType: PaymentType }): Observable<CheckoutDTO> {
|
||||
return this.getCheckout({ processId }).pipe(
|
||||
first(),
|
||||
@@ -349,6 +396,53 @@ export class DomainCheckoutService {
|
||||
);
|
||||
}
|
||||
|
||||
checkAvailabilities({ processId }: { processId: number }): Observable<any> {
|
||||
const shoppingCart$ = this.getShoppingCart({ processId }).pipe(first());
|
||||
const itemsToCheck$ = shoppingCart$.pipe(
|
||||
map((cart) => cart?.items?.filter((item) => item?.data?.features?.orderType === 'Download' && !item.data.availability.lastRequest))
|
||||
);
|
||||
|
||||
return itemsToCheck$.pipe(
|
||||
withLatestFrom(shoppingCart$),
|
||||
switchMap(async ([items, cart]) => {
|
||||
const errorIds = [];
|
||||
|
||||
for (const item of items) {
|
||||
const availability = await this.availabilityService
|
||||
.getDownloadAvailability({
|
||||
item: {
|
||||
ean: item.data.product.ean,
|
||||
itemId: Number(item.data.product.catalogProductNumber),
|
||||
price: item.data.availability.price,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
if (!availability || !this.availabilityService.isAvailable({ availability })) {
|
||||
errorIds.push(item.id);
|
||||
} else {
|
||||
await this.updateShoppingCartItemAvailability({
|
||||
shoppingCartId: cart.id,
|
||||
shoppingCartItemId: item.id,
|
||||
availability: {
|
||||
...availability,
|
||||
lastRequest: new Date().toISOString(),
|
||||
},
|
||||
}).toPromise();
|
||||
}
|
||||
}
|
||||
|
||||
this.setOlaErrors({ processId, errorIds });
|
||||
|
||||
if (errorIds.length > 0) {
|
||||
throw throwError(new Error(`Artikel nicht verfügbar`));
|
||||
} else {
|
||||
return of(undefined);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateAvailabilities({ processId }: { processId: number }): Observable<any> {
|
||||
const shoppingCart$ = this.getShoppingCart({ processId }).pipe(first());
|
||||
const itemsToUpdate$ = shoppingCart$.pipe(
|
||||
@@ -511,6 +605,8 @@ export class DomainCheckoutService {
|
||||
})
|
||||
);
|
||||
|
||||
const checkAvailabilities$ = this.checkAvailabilities({ processId });
|
||||
|
||||
const updateAvailabilities$ = this.updateAvailabilities({ processId });
|
||||
|
||||
const setPaymentType$ = itemOrderOptions$.pipe(
|
||||
@@ -576,6 +672,7 @@ export class DomainCheckoutService {
|
||||
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((_) => setBuyer$.pipe(tap(console.log.bind(window, 'setBuyer$')))),
|
||||
mergeMap((_) => setNotificationChannels$.pipe(tap(console.log.bind(window, 'setNotificationChannels$')))),
|
||||
@@ -731,6 +828,15 @@ export class DomainCheckoutService {
|
||||
this.store.dispatch(DomainCheckoutActions.setCustomerFeatures({ processId, customerFeatures }));
|
||||
}
|
||||
|
||||
setOlaErrors({ processId, errorIds }: { processId: number; errorIds: number[] }) {
|
||||
this.store.dispatch(
|
||||
DomainCheckoutActions.setOlaError({
|
||||
processId,
|
||||
olaErrorIds: errorIds,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getCustomerFeatures({ processId }: { processId: number }): Observable<{ [key: string]: string }> {
|
||||
return this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId });
|
||||
}
|
||||
|
||||
@@ -12,4 +12,5 @@ export interface CheckoutEntity {
|
||||
orders: DisplayOrderDTO[];
|
||||
specialComment: string;
|
||||
notificationChannels: NotificationChannel;
|
||||
olaErrorIds: number[];
|
||||
}
|
||||
|
||||
@@ -57,3 +57,5 @@ export const setBuyer = createAction(`${prefix} Set Buyer`, props<{ processId: n
|
||||
export const setPayer = createAction(`${prefix} Set Payer`, props<{ processId: number; payer: PayerDTO }>());
|
||||
|
||||
export const setSpecialComment = createAction(`${prefix} Set Agent Comment`, props<{ processId: number; agentComment: string }>());
|
||||
|
||||
export const setOlaError = createAction(`${prefix} Set Ola Error`, props<{ processId: number; olaErrorIds: number[] }>());
|
||||
|
||||
@@ -72,7 +72,12 @@ const _domainCheckoutReducer = createReducer(
|
||||
return storeCheckoutAdapter.setOne(entity, s);
|
||||
}),
|
||||
on(DomainCheckoutActions.removeProcess, (s, { processId }) => storeCheckoutAdapter.removeOne(processId, s)),
|
||||
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders }))
|
||||
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders })),
|
||||
on(DomainCheckoutActions.setOlaError, (s, { processId, olaErrorIds }) => {
|
||||
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
|
||||
entity.olaErrorIds = olaErrorIds;
|
||||
return storeCheckoutAdapter.setOne(entity, s);
|
||||
})
|
||||
);
|
||||
|
||||
export function domainCheckoutReducer(state, action) {
|
||||
@@ -94,6 +99,7 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
|
||||
buyer: undefined,
|
||||
specialComment: '',
|
||||
notificationChannels: 0,
|
||||
olaErrorIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -56,3 +56,8 @@ export const selectBuyerCommunicationDetails = createSelector(
|
||||
);
|
||||
|
||||
export const selectOrders = createSelector(storeFeatureSelector, (s) => s.orders);
|
||||
|
||||
export const selectOlaErrorsByProcessId = createSelector(
|
||||
selectEntities,
|
||||
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => entities[processId]?.olaErrorIds
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('ReorderModalComponent', () => {
|
||||
mockProvider(DomainCheckoutService),
|
||||
mockProvider(DomainAvailabilityService, {
|
||||
getCurrentBranch: jasmine.createSpy().and.returnValue(of({})),
|
||||
getStoreAvailabilities: jasmine.createSpy().and.returnValue(of(storeAvailabilitiesMock)),
|
||||
getPickUpAvailabilities: jasmine.createSpy().and.returnValue(of(storeAvailabilitiesMock)),
|
||||
getTakeAwayAvailability: jasmine.createSpy().and.returnValue(of(takeAwayAvailabiltyMock)),
|
||||
}),
|
||||
mockProvider(DomainOmsService),
|
||||
@@ -76,11 +76,7 @@ describe('ReorderModalComponent', () => {
|
||||
spectator.component.patchState({ orderItem });
|
||||
await spectator.detectComponentChanges();
|
||||
|
||||
expect(domainAvailabilityServiceMock.getStoreAvailabilities).toHaveBeenCalledWith({
|
||||
item: { ean: orderItem.product.ean, itemId: +orderItem.product.catalogProductNumber, price: orderItem.retailPrice },
|
||||
quantity: 1,
|
||||
branch: {},
|
||||
});
|
||||
expect(domainAvailabilityServiceMock.getPickUpAvailabilities).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set checkedAvailability to the preferred availability', async () => {
|
||||
|
||||
@@ -69,11 +69,15 @@ export class ReorderModalComponent extends ComponentStore<GoodsInListReorderModa
|
||||
readonly storeAvailabilities$ = combineLatest([this.orderItem$, this.currentBranch$]).pipe(
|
||||
switchMap(([item, branch]) =>
|
||||
this.domainAvailabilityService
|
||||
.getStoreAvailabilities({
|
||||
item: { ean: item.product.ean, itemId: +item.product?.catalogProductNumber, price: item.retailPrice },
|
||||
branch,
|
||||
quantity: item.quantity,
|
||||
})
|
||||
.getPickUpAvailabilities([
|
||||
{
|
||||
qty: item.quantity,
|
||||
ean: item.product.ean,
|
||||
itemId: item.product?.catalogProductNumber,
|
||||
shopId: branch.id,
|
||||
price: item.retailPrice,
|
||||
},
|
||||
])
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
this.patchState({ storeAvailabilityError: true });
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface ArticleSearchState {
|
||||
searchState: '' | 'fetching' | 'empty' | 'error';
|
||||
items: ItemDTO[];
|
||||
hits: number;
|
||||
selectedItemIds: number[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -48,6 +49,12 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
|
||||
searchboxHint$ = this.select((s) => (s.searchState === 'empty' ? 'Keine Suchergebnisse' : undefined));
|
||||
|
||||
selectedItemIds$ = this.select((s) => s.selectedItemIds);
|
||||
|
||||
get selectedItemIds() {
|
||||
return this.get((s) => s.selectedItemIds);
|
||||
}
|
||||
|
||||
hits$ = this.select((s) => s.hits);
|
||||
|
||||
get hits() {
|
||||
@@ -61,6 +68,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
items: [],
|
||||
processId: 0,
|
||||
searchState: '',
|
||||
selectedItemIds: [],
|
||||
});
|
||||
this.setDefaultFilter();
|
||||
}
|
||||
@@ -94,6 +102,20 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
this.patchState({ filter });
|
||||
}
|
||||
|
||||
setSelected({ selected, itemId }: { selected: boolean; itemId: number }) {
|
||||
const included = this.selectedItemIds.includes(itemId);
|
||||
|
||||
if (!included && selected) {
|
||||
this.patchState({
|
||||
selectedItemIds: [...this.selectedItemIds, itemId],
|
||||
});
|
||||
} else if (included && !selected) {
|
||||
this.patchState({
|
||||
selectedItemIds: this.selectedItemIds.filter((id) => id !== itemId),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
search = this.effect((options$: Observable<{ clear?: boolean }>) =>
|
||||
options$.pipe(
|
||||
tap((options) => {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<p class="can-add-message" *ngIf="ref.data.canAddMessage">{{ ref.data.canAddMessage }}</p>
|
||||
|
||||
<div class="actions">
|
||||
<button (click)="continue()" class="cta cta-action-secondary">
|
||||
Weiter Einkaufen
|
||||
</button>
|
||||
<button (click)="toCart()" class="cta cta-action-primary">
|
||||
Zum Warenkorb
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
.can-add-message {
|
||||
@apply text-center text-xl text-dark-goldenrod font-semibold;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply flex flex-row justify-center items-center pb-4;
|
||||
|
||||
.cta {
|
||||
@apply border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none self-end whitespace-nowrap no-underline;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch border-inactive-branch text-white;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-action-primary {
|
||||
@apply bg-brand text-white ml-2;
|
||||
}
|
||||
|
||||
.cta-action-secondary {
|
||||
@apply bg-white text-brand mr-2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { UiModalRef } from '@ui/modal';
|
||||
|
||||
@Component({
|
||||
selector: 'added-to-cart-modal',
|
||||
templateUrl: 'added-to-cart-modal.component.html',
|
||||
styleUrls: ['added-to-cart-modal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddedToCartModalComponent {
|
||||
constructor(public ref: UiModalRef<any, any>, private readonly _router: Router) {}
|
||||
continue() {
|
||||
this._router.navigate(['/product/search']);
|
||||
this.ref.close();
|
||||
}
|
||||
|
||||
toCart() {
|
||||
this._router.navigate(['/cart/review']);
|
||||
this.ref.close();
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,10 @@
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectable" class="item-data-selector">
|
||||
<ui-select-bullet [ngModel]="selected" (ngModelChange)="setSelected($event)"></ui-select-bullet>
|
||||
</div>
|
||||
|
||||
<div class="item-stock"><ui-icon icon="home" size="1em"></ui-icon> {{ item?.stockInfos | stockInfos }} x</div>
|
||||
|
||||
<div class="item-ssc" [class.xs]="item?.catalogAvailability?.sscText?.length >= 60">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
grid-template-areas:
|
||||
'item-thumbnail item-contributors item-contributors'
|
||||
'item-thumbnail item-title item-price'
|
||||
'item-thumbnail item-title item-data-selector'
|
||||
'item-thumbnail item-format item-stock'
|
||||
'item-thumbnail item-misc item-ssc';
|
||||
}
|
||||
@@ -96,6 +97,15 @@
|
||||
@apply font-bold text-xs;
|
||||
}
|
||||
|
||||
.item-data-selector {
|
||||
@apply w-full flex justify-end;
|
||||
grid-area: item-data-selector;
|
||||
}
|
||||
|
||||
ui-select-bullet {
|
||||
@apply p-4 -m-4 z-dropdown;
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
.item-contributors {
|
||||
max-width: 780px;
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { isEqual } from 'lodash';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
|
||||
export interface SearchResultItemComponentState {
|
||||
item?: ItemDTO;
|
||||
selected: boolean;
|
||||
selectable: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'search-result-item',
|
||||
@@ -9,9 +18,42 @@ import { DateAdapter } from '@ui/common';
|
||||
styleUrls: ['search-result-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SearchResultItemComponent {
|
||||
export class SearchResultItemComponent extends ComponentStore<SearchResultItemComponentState> {
|
||||
@Input()
|
||||
item: ItemDTO;
|
||||
get item() {
|
||||
return this.get((s) => s.item);
|
||||
}
|
||||
set item(item: ItemDTO) {
|
||||
if (!isEqual(this.item, item)) {
|
||||
this.patchState({ item });
|
||||
}
|
||||
}
|
||||
|
||||
readonly item$ = this.select((s) => s.item);
|
||||
|
||||
@Input()
|
||||
get selected() {
|
||||
return this.get((s) => s.selected);
|
||||
}
|
||||
set selected(selected: boolean) {
|
||||
if (this.selected !== selected) {
|
||||
this.patchState({ selected });
|
||||
}
|
||||
}
|
||||
readonly selected$ = this.select((s) => s.selected);
|
||||
|
||||
@Input()
|
||||
get selectable() {
|
||||
return this.get((s) => s.selectable);
|
||||
}
|
||||
set selectable(selectable: boolean) {
|
||||
if (this.selectable !== selectable) {
|
||||
this.patchState({ selectable });
|
||||
}
|
||||
}
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<boolean>();
|
||||
|
||||
get contributors() {
|
||||
return this.item?.product?.contributors?.split(';').map((val) => val.trim());
|
||||
@@ -30,5 +72,14 @@ export class SearchResultItemComponent {
|
||||
return '';
|
||||
}
|
||||
|
||||
constructor(private _dateAdapter: DateAdapter, private _datePipe: DatePipe) {}
|
||||
constructor(private _dateAdapter: DateAdapter, private _datePipe: DatePipe, private _articleSearchService: ArticleSearchService) {
|
||||
super({
|
||||
selected: false,
|
||||
selectable: false,
|
||||
});
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
this._articleSearchService.setSelected({ selected, itemId: this.item?.id });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,22 @@
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<div class="product-list-result" *cdkVirtualFor="let item of results$ | async" cdkVirtualForTrackBy="trackByItemId">
|
||||
<search-result-item [item]="item"></search-result-item>
|
||||
<search-result-item
|
||||
[selected]="item | searchResultSelected: searchService.selectedItemIds"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
></search-result-item>
|
||||
</div>
|
||||
<page-search-result-item-loading *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addSelectedItemsToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -26,3 +26,21 @@
|
||||
@apply mx-auto;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply fixed bottom-28 inline-grid grid-flow-col gap-7;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
.cta-cart {
|
||||
@apply border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none self-end whitespace-nowrap no-underline;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch border-inactive-branch text-white;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-action-primary {
|
||||
@apply bg-brand text-white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, ViewChildren, QueryList } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AddToShoppingCartDTO } from '@swagger/checkout';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { UiErrorModalComponent, UiModalRef, UiModalService } from '@ui/modal';
|
||||
import { CacheService } from 'apps/core/cache/src/public-api';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, first } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, first, map, tap } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
||||
import { SearchResultItemComponent } from './search-result-item.component';
|
||||
|
||||
@Component({
|
||||
selector: 'page-search-results',
|
||||
@@ -18,6 +23,7 @@ import { ArticleSearchService } from '../article-search.store';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
@ViewChildren(SearchResultItemComponent) listItems: QueryList<SearchResultItemComponent>;
|
||||
@ViewChild('scrollContainer', { static: true })
|
||||
scrollContainer: CdkVirtualScrollViewport;
|
||||
|
||||
@@ -27,16 +33,28 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
filter$ = this.searchService.filter$;
|
||||
|
||||
trackByItemId = (item: ItemDTO) => item.id;
|
||||
selectedItemIds$ = this.searchService.selectedItemIds$;
|
||||
|
||||
selectedItems$ = combineLatest([this.results$, this.selectedItemIds$]).pipe(
|
||||
map(([items, selectedItemIds]) => {
|
||||
return items?.filter((item) => selectedItemIds?.find((selectedItemId) => item.id === selectedItemId));
|
||||
})
|
||||
);
|
||||
|
||||
loading$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private subscriptions = new Subscription();
|
||||
|
||||
trackByItemId = (item: ItemDTO) => item.id;
|
||||
|
||||
constructor(
|
||||
private searchService: ArticleSearchService,
|
||||
public searchService: ArticleSearchService,
|
||||
private route: ActivatedRoute,
|
||||
private application: ApplicationService,
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private cache: CacheService
|
||||
private cache: CacheService,
|
||||
private _uiModal: UiModalService,
|
||||
private _checkoutService: DomainCheckoutService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -68,6 +86,12 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
this.search();
|
||||
} else {
|
||||
this.scrollTop(Number(queryParams.scroll_position ?? 0));
|
||||
const selectedItemIds: Array<string> = queryParams?.selected_item_ids?.split(',') ?? [];
|
||||
for (const id of selectedItemIds) {
|
||||
if (id) {
|
||||
this.searchService.setSelected({ selected: true, itemId: Number(id) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +107,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.cacheCurrentData(this.searchService.processId, this.searchService.filter.getQueryParams());
|
||||
this.updateBreadcrumbs(this.searchService.processId, this.searchService.filter.getQueryParams());
|
||||
this.unselectAll();
|
||||
}
|
||||
|
||||
search() {
|
||||
@@ -119,6 +144,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams()
|
||||
) {
|
||||
const scroll_position = this.scrollContainer.measureScrollOffset('top');
|
||||
const selected_item_ids = this.searchService?.selectedItemIds?.toString();
|
||||
|
||||
if (queryParams) {
|
||||
const crumbs = await this.breadcrumb
|
||||
@@ -127,7 +153,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
.toPromise();
|
||||
|
||||
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
|
||||
const params = { ...queryParams, scroll_position };
|
||||
const params = { ...queryParams, scroll_position, selected_item_ids };
|
||||
|
||||
for (const crumb of crumbs) {
|
||||
this.breadcrumb.patchBreadcrumb(crumb.id, {
|
||||
@@ -181,6 +207,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
delete clean['scroll_position'];
|
||||
delete clean['selected_item_ids'];
|
||||
|
||||
for (const key in clean) {
|
||||
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||
@@ -192,4 +219,108 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
isSelectable(item: ItemDTO) {
|
||||
// Zeige Select Radio Button nicht an wenn Item Archivartikel oder Fortsetzungsartikel ist
|
||||
const isArchiv = item?.catalogAvailability?.status === 1;
|
||||
const isFortsetzung = item?.features?.find((i) => i?.key === 'PFO');
|
||||
return !(isArchiv || isFortsetzung);
|
||||
}
|
||||
|
||||
unselectAll() {
|
||||
this.listItems.forEach((listItem) => this.searchService.setSelected({ selected: false, itemId: listItem.item.id }));
|
||||
this.searchService.patchState({ selectedItemIds: [] });
|
||||
}
|
||||
|
||||
async addSelectedItemsToCart() {
|
||||
this.loading$.next(true);
|
||||
const selectedItems = await this.selectedItems$.pipe(first()).toPromise();
|
||||
const items: AddToShoppingCartDTO[] = [];
|
||||
const canAddItemsPayload = [];
|
||||
|
||||
for (const item of selectedItems) {
|
||||
const isDownload = item?.product?.format === 'EB' || item?.product?.format === 'DL';
|
||||
const shoppingCartItem: AddToShoppingCartDTO = {
|
||||
quantity: 1,
|
||||
availability: {
|
||||
availabilityType: item?.catalogAvailability?.status,
|
||||
price: item?.catalogAvailability?.price,
|
||||
supplierProductNumber: item?.ids?.dig ? String(item.ids?.dig) : item?.product?.supplierProductNumber,
|
||||
},
|
||||
product: {
|
||||
catalogProductNumber: String(item?.id),
|
||||
...item?.product,
|
||||
},
|
||||
promotion: { points: item?.promoPoints },
|
||||
};
|
||||
|
||||
if (isDownload) {
|
||||
shoppingCartItem.destination = { data: { target: 16 } };
|
||||
canAddItemsPayload.push({
|
||||
availabilities: [{ ...item.catalogAvailability, format: 'DL' }],
|
||||
id: item.product.catalogProductNumber,
|
||||
});
|
||||
}
|
||||
|
||||
items.push(shoppingCartItem);
|
||||
}
|
||||
|
||||
if (canAddItemsPayload.length > 0) {
|
||||
try {
|
||||
const response = await this._checkoutService
|
||||
.canAddItems({
|
||||
processId: this.application.activatedProcessId,
|
||||
payload: canAddItemsPayload,
|
||||
orderType: 'Download',
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
if (response) {
|
||||
const cantAdd = (response as any)?.filter((r) => r.status >= 2);
|
||||
if (cantAdd?.length > 0) {
|
||||
this.openModal({ itemLength: cantAdd.length, canAddMessage: cantAdd[0].message });
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.openModal({ itemLength: selectedItems?.length, error });
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this._checkoutService
|
||||
.addItemToShoppingCart({
|
||||
processId: this.application.activatedProcessId,
|
||||
items,
|
||||
})
|
||||
.toPromise();
|
||||
this.openModal({ itemLength: selectedItems?.length });
|
||||
} catch (error) {
|
||||
this.openModal({ itemLength: selectedItems?.length, error });
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
openModal({ itemLength, canAddMessage, error }: { itemLength: number; canAddMessage?: string; error?: Error }) {
|
||||
const modal = this._uiModal.open({
|
||||
title:
|
||||
!error && !canAddMessage
|
||||
? `${itemLength} Artikel ${itemLength > 1 ? 'wurden' : 'wurde'} in den Warenkorb gelegt`
|
||||
: `Artikel ${itemLength > 1 ? 'konnten' : 'konnte'} nicht in den Warenkorb gelegt werden`,
|
||||
content: !error ? AddedToCartModalComponent : UiErrorModalComponent,
|
||||
data: error ? error : { canAddMessage },
|
||||
config: { showScrollbarY: false },
|
||||
});
|
||||
this.subscriptions.add(
|
||||
modal.afterClosed$.subscribe(() => {
|
||||
if (!error) {
|
||||
this.unselectAll();
|
||||
}
|
||||
this.loading$.next(false);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { DomainCatalogModule } from '@domain/catalog';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
import { UiOrderByFilterModule } from 'apps/ui/filter/src/lib/next/order-by-filter/order-by-filter.module';
|
||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
||||
import { StockInfosPipe } from './order-by-filter/stick-infos.pipe';
|
||||
import { SearchResultItemLoadingComponent } from './search-result-item-loading.component';
|
||||
import { SearchResultItemComponent } from './search-result-item.component';
|
||||
import { ArticleSearchResultsComponent } from './search-results.component';
|
||||
import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule, DomainCatalogModule, UiCommonModule, UiIconModule, UiOrderByFilterModule, ScrollingModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
RouterModule,
|
||||
DomainCatalogModule,
|
||||
UiCommonModule,
|
||||
UiIconModule,
|
||||
UiSelectBulletModule,
|
||||
UiSpinnerModule,
|
||||
UiOrderByFilterModule,
|
||||
ScrollingModule,
|
||||
],
|
||||
exports: [ArticleSearchResultsComponent, SearchResultItemComponent],
|
||||
declarations: [ArticleSearchResultsComponent, SearchResultItemComponent, StockInfosPipe, SearchResultItemLoadingComponent],
|
||||
declarations: [
|
||||
ArticleSearchResultsComponent,
|
||||
SearchResultItemComponent,
|
||||
StockInfosPipe,
|
||||
SearchResultItemLoadingComponent,
|
||||
SearchResultSelectedPipe,
|
||||
AddedToCartModalComponent,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
export class SearchResultsModule {}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
|
||||
@Pipe({
|
||||
name: 'searchResultSelected',
|
||||
})
|
||||
export class SearchResultSelectedPipe implements PipeTransform {
|
||||
transform(item: ItemDTO, selectedItemIds: number[]): any {
|
||||
return selectedItemIds?.includes(item?.id);
|
||||
}
|
||||
}
|
||||
@@ -30,21 +30,33 @@
|
||||
</div>
|
||||
<h1 class="header">Warenkorb</h1>
|
||||
<h5 class="sub-header">Überprüfen Sie die Details.</h5>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="label">
|
||||
Rechnungsadresse
|
||||
|
||||
<ng-container *ngIf="payer$ | async">
|
||||
<hr />
|
||||
<div class="row">
|
||||
<ng-container *ngIf="showBillingAddress$ | async; else customerName">
|
||||
<div class="label">
|
||||
Rechnungsadresse
|
||||
</div>
|
||||
<div class="value">
|
||||
{{ payer$ | async | payerAddress | trim: 55 }}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #customerName>
|
||||
<div class="label">
|
||||
Name, Vorname
|
||||
</div>
|
||||
<div class="value" *ngIf="payer$ | async; let payer">{{ payer.lastName }}, {{ payer.firstName }}</div>
|
||||
</ng-template>
|
||||
|
||||
<div class="grow"></div>
|
||||
<div>
|
||||
<button *ngIf="payer$ | async" (click)="changeAddress()" class="cta-edit">
|
||||
Ändern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
{{ payer$ | async | payerAddress | trim: 55 }}
|
||||
</div>
|
||||
<div class="grow"></div>
|
||||
<div>
|
||||
<button (click)="changeAddress()" class="cta-edit">
|
||||
Ändern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<hr />
|
||||
<ng-container *ngIf="showNotificationChannels$ | async">
|
||||
<div class="row">
|
||||
@@ -57,32 +69,41 @@
|
||||
(ngModelChange)="setAgentComment($event)"
|
||||
(isDirtyChange)="specialCommentIsDirty = $event"
|
||||
></page-special-comment>
|
||||
<hr />
|
||||
<ng-container *ngFor="let group of groupedItems$ | async; let lastGroup = last">
|
||||
<div class="row item-group-header">
|
||||
<ui-icon
|
||||
*ngIf="group.orderType !== 'Dummy'"
|
||||
class="icon-order-type"
|
||||
[size]="group.orderType === 'B2B-Versand' ? '50px' : '25px'"
|
||||
[icon]="
|
||||
group.orderType === 'Abholung'
|
||||
? 'box_out'
|
||||
: group.orderType === 'Versand'
|
||||
? 'truck'
|
||||
: group.orderType === 'Rücklage'
|
||||
? 'shopping_bag'
|
||||
: group.orderType === 'B2B-Versand'
|
||||
? 'truck_b2b'
|
||||
: group.orderType === 'Download'
|
||||
? 'download'
|
||||
: 'truck'
|
||||
"
|
||||
></ui-icon>
|
||||
<div class="label" [class.dummy]="group.orderType === 'Dummy'">
|
||||
{{ group.orderType !== 'Dummy' ? group.orderType : 'Manuelle Anlage / Dummy Bestellung' }}
|
||||
<button *ngIf="group.orderType === 'Dummy'" class="cta-secondary" (click)="openDummyModal()">Hinzufügen</button>
|
||||
<ng-container *ngIf="group?.orderType !== undefined">
|
||||
<hr />
|
||||
<div class="row item-group-header">
|
||||
<ui-icon
|
||||
*ngIf="group.orderType !== 'Dummy'"
|
||||
class="icon-order-type"
|
||||
[size]="group.orderType === 'B2B-Versand' ? '50px' : '25px'"
|
||||
[icon]="
|
||||
group.orderType === 'Abholung'
|
||||
? 'box_out'
|
||||
: group.orderType === 'Versand'
|
||||
? 'truck'
|
||||
: group.orderType === 'Rücklage'
|
||||
? 'shopping_bag'
|
||||
: group.orderType === 'B2B-Versand'
|
||||
? 'truck_b2b'
|
||||
: group.orderType === 'Download'
|
||||
? 'download'
|
||||
: 'truck'
|
||||
"
|
||||
></ui-icon>
|
||||
<div class="label" [class.dummy]="group.orderType === 'Dummy'">
|
||||
{{ group.orderType !== 'Dummy' ? group.orderType : 'Manuelle Anlage / Dummy Bestellung' }}
|
||||
<button *ngIf="group.orderType === 'Dummy'" class="cta-secondary" (click)="openDummyModal()">Hinzufügen</button>
|
||||
</div>
|
||||
|
||||
<div class="grow"></div>
|
||||
<div *ngIf="group.orderType !== 'Download' && group.orderType !== 'Dummy'">
|
||||
<button class="cta-edit" (click)="showPurchasingListModal(group.items)">
|
||||
Ändern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="group.orderType === 'Versand' || group.orderType === 'B2B-Versand' || group.orderType === 'DIG-Versand'">
|
||||
<hr />
|
||||
<div class="row">
|
||||
@@ -102,7 +123,9 @@
|
||||
</ng-container>
|
||||
<hr />
|
||||
<ng-container *ngFor="let item of group.items; let lastItem = last; let i = index">
|
||||
<ng-container *ngIf="item.features?.orderType === 'Abholung' || item.features?.orderType === 'Rücklage'">
|
||||
<ng-container
|
||||
*ngIf="group?.orderType !== undefined && (item.features?.orderType === 'Abholung' || item.features?.orderType === 'Rücklage')"
|
||||
>
|
||||
<ng-container *ngIf="item?.destination?.data?.targetBranch?.data; let targetBranch">
|
||||
<ng-container *ngIf="i === 0 || targetBranch.id !== group.items[i - 1].destination?.data?.targetBranch?.data.id">
|
||||
<div class="row">
|
||||
@@ -114,78 +137,19 @@
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<div class="row product-container">
|
||||
<div class="product-image">
|
||||
<img [src]="item?.product?.ean | productImage: 50:50:true" alt="product-image" />
|
||||
</div>
|
||||
<div class="product-name">
|
||||
<a [routerLink]="['/product', 'details', 'ean', item?.product?.ean]">{{ item?.product?.name }}</a>
|
||||
</div>
|
||||
<div
|
||||
class="product-misc-container"
|
||||
[style]="group.orderType !== 'Rücklage' && group.orderType !== 'Download' ? 'margin-top: 1.25rem' : ''"
|
||||
>
|
||||
<div class="product-misc">
|
||||
<img class="book-icon" [src]="'/assets/images/Icon_' + item?.product?.format + '.svg'" alt="book-icon" />
|
||||
{{ item?.product?.manufacturer }}
|
||||
<page-shopping-cart-item
|
||||
(changeItem)="changeItem($event)"
|
||||
(changeDummyItem)="changeDummyItem($event)"
|
||||
(changeQuantity)="updateItemQuantity($event)"
|
||||
[quantityError]="(quantityError$ | async)[item.product.catalogProductNumber]"
|
||||
[item]="item"
|
||||
[orderType]="group?.orderType"
|
||||
[loadingOnItemChangeById]="loadingOnItemChangeById$ | async"
|
||||
[loadingOnQuantityChangeById]="loadingOnQuantityChangeById$ | async"
|
||||
></page-shopping-cart-item>
|
||||
|
||||
<ng-container *ngIf="item?.product?.contributors">
|
||||
{{ ' | ' + item?.product?.contributors | trim: 30 }}
|
||||
</ng-container>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="group.orderType !== 'Rücklage' && group.orderType !== 'Download' && group.orderType !== 'Dummy'"
|
||||
class="product-delivery"
|
||||
>
|
||||
{{ group.orderType === 'DIG-Versand' ? 'Versand' : group.orderType }} {{ group.orderType === 'Abholung' ? 'ab' : '' }}
|
||||
{{ item?.availability?.estimatedShippingDate | date }}
|
||||
</div>
|
||||
<div *ngIf="group.orderType === 'Dummy'" class="product-delivery">
|
||||
Abholung {{ group.orderType === 'Dummy' ? 'ab' : '' }}
|
||||
{{ item?.availability?.estimatedShippingDate ? (item?.availability?.estimatedShippingDate | date) : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-price">
|
||||
{{ item?.unitPrice?.value?.value | currency: item?.unitPrice?.value?.currency:'code' }}
|
||||
</div>
|
||||
<div class="product-quantity">
|
||||
<ui-quantity-dropdown
|
||||
*ngIf="group.orderType !== 'Dummy'; else quantityDummy"
|
||||
[ngModel]="item?.quantity"
|
||||
(ngModelChange)="updateItemQuantity(item, $event)"
|
||||
[showSpinner]="showQuantityControlSpinnerItemId === item.id"
|
||||
>
|
||||
</ui-quantity-dropdown>
|
||||
<ng-template #quantityDummy>
|
||||
{{ item?.quantity }}
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="product-actions">
|
||||
<button
|
||||
*ngIf="group.orderType === 'Dummy'"
|
||||
class="cta-edit"
|
||||
(click)="changeDummyItem(item)"
|
||||
[disabled]="showChangeButtonSpinnerItemId"
|
||||
>
|
||||
<ui-spinner [show]="showChangeButtonSpinnerItemId === item.id">
|
||||
Ändern
|
||||
</ui-spinner>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="group.orderType !== 'Download' && group.orderType !== 'Dummy'"
|
||||
class="cta-edit"
|
||||
(click)="changeItem(item)"
|
||||
[disabled]="showChangeButtonSpinnerItemId"
|
||||
>
|
||||
<ui-spinner [show]="showChangeButtonSpinnerItemId === item.id">
|
||||
Ändern
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr *ngIf="!lastItem" />
|
||||
</ng-container>
|
||||
<hr *ngIf="!lastGroup" />
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="card footer row">
|
||||
@@ -202,14 +166,9 @@
|
||||
</strong>
|
||||
<span class="shipping-cost-info">ohne Versandkosten</span>
|
||||
</div>
|
||||
<button
|
||||
class="cta-primary"
|
||||
(click)="order()"
|
||||
[disabled]="showOrderButtonSpinner || specialCommentIsDirty"
|
||||
[class.special-comment-dirty]="specialCommentIsDirty"
|
||||
>
|
||||
<button class="cta-primary" (click)="order()" [disabled]="showOrderButtonSpinner">
|
||||
<ui-spinner [show]="showOrderButtonSpinner">
|
||||
Bestellen
|
||||
{{ primaryCtaLabel$ | async }}
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
max-height: calc(100vh - 390px);
|
||||
}
|
||||
|
||||
button {
|
||||
@apply p-0;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white rounded-card shadow-card;
|
||||
}
|
||||
@@ -54,26 +58,26 @@
|
||||
}
|
||||
|
||||
.cta-print-wrapper {
|
||||
@apply pl-4 pr-2 pt-4 text-right;
|
||||
@apply pl-4 pr-4 pt-4 text-right;
|
||||
}
|
||||
|
||||
.cta-print,
|
||||
.cta-edit {
|
||||
@apply bg-transparent text-brand font-bold text-xl outline-none border-none;
|
||||
@apply bg-transparent text-brand font-bold text-lg outline-none border-none;
|
||||
}
|
||||
|
||||
.cta-primary {
|
||||
@apply bg-brand text-white font-bold text-lg outline-none border-brand border-solid border-2 rounded-full px-6 py-3;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-customer border-none;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-secondary {
|
||||
@apply bg-white text-brand border-none font-bold text-lg outline-none px-6 py-3 mt-4;
|
||||
}
|
||||
|
||||
.cta-order.special-comment-dirty {
|
||||
@apply bg-active-branch text-white;
|
||||
}
|
||||
|
||||
.cta-edit {
|
||||
@apply text-lg;
|
||||
}
|
||||
@@ -129,61 +133,22 @@ hr {
|
||||
@apply text-regular overflow-hidden overflow-ellipsis ml-4;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
@apply overflow-ellipsis whitespace-nowrap overflow-hidden text-active-customer text-base font-bold;
|
||||
width: 130px;
|
||||
|
||||
a {
|
||||
@apply text-active-customer no-underline;
|
||||
}
|
||||
}
|
||||
|
||||
.book-icon {
|
||||
@apply mr-2 w-px-15;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.product-misc-container {
|
||||
@apply flex flex-col;
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
.product-misc {
|
||||
@apply overflow-ellipsis whitespace-nowrap overflow-hidden;
|
||||
}
|
||||
|
||||
.product-delivery {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
@apply w-px-50 h-px-50 text-center overflow-hidden;
|
||||
}
|
||||
|
||||
.product-container {
|
||||
@apply py-0 px-4 flex flex-row justify-between;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.product-actions {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@apply absolute bottom-0 left-0 right-0 p-7;
|
||||
box-shadow: 0px -2px 24px 0px #dce2e9;
|
||||
}
|
||||
|
||||
.total-container {
|
||||
@apply flex flex-col;
|
||||
@apply flex flex-col ml-4;
|
||||
}
|
||||
|
||||
.total-cta-container {
|
||||
@apply flex flex-row;
|
||||
@apply flex flex-row whitespace-nowrap;
|
||||
}
|
||||
|
||||
.shipping-cost-info {
|
||||
@@ -197,13 +162,3 @@ hr {
|
||||
.total-item-reading-points {
|
||||
@apply text-base font-bold text-ucla-blue;
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
.product-misc-container {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
width: 225px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,27 @@ import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { AvailabilityDTO, DestinationDTO, ShoppingCartDTO, 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 { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { SsoService } from 'sso';
|
||||
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from '../modals/purchasing-options-modal';
|
||||
import { PurchasingOptions } from '../modals/purchasing-options-modal/purchasing-options-modal.store';
|
||||
import { Subject, NEVER } from 'rxjs';
|
||||
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
import { CheckoutDummyComponent } from '../checkout-dummy/checkout-dummy.component';
|
||||
import { ResponseArgsOfItemDTO } from '@swagger/cat';
|
||||
import { PurchasingOptionsListModalComponent } from '../modals/purchasing-options-list-modal';
|
||||
import { PurchasingOptionsListModalData } from '../modals/purchasing-options-list-modal/purchasing-options-list-modal.data';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
|
||||
export interface CheckoutReviewComponentState {
|
||||
shoppingCart: ShoppingCartDTO;
|
||||
shoppingCartItems: ShoppingCartItemDTO[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-review',
|
||||
@@ -23,18 +31,29 @@ import { ResponseArgsOfItemDTO } from '@swagger/cat';
|
||||
styleUrls: ['checkout-review.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewComponent implements OnInit {
|
||||
export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewComponentState> implements OnInit {
|
||||
private _orderCompleted = new Subject<void>();
|
||||
|
||||
shoppingCart$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getShoppingCart({ processId, latest: true })),
|
||||
shareReplay()
|
||||
);
|
||||
get shoppingCart() {
|
||||
return this.get((s) => s.shoppingCart);
|
||||
}
|
||||
set shoppingCart(shoppingCart: ShoppingCartDTO) {
|
||||
this.patchState({ shoppingCart });
|
||||
}
|
||||
readonly shoppingCart$ = this.select((s) => s.shoppingCart);
|
||||
|
||||
get shoppingCartItems() {
|
||||
return this.get((s) => s.shoppingCartItems);
|
||||
}
|
||||
set shoppingCartItems(shoppingCartItems: ShoppingCartItemDTO[]) {
|
||||
this.patchState({ shoppingCartItems });
|
||||
}
|
||||
readonly shoppingCartItems$ = this.select((s) => s.shoppingCartItems);
|
||||
|
||||
payer$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getPayer({ processId }))
|
||||
switchMap((processId) => this.domainCheckoutService.getPayer({ processId })),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
shippingAddress$ = this.applicationService.activatedProcessId$.pipe(
|
||||
@@ -42,12 +61,12 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
switchMap((processId) => this.domainCheckoutService.getShippingAddress({ processId }))
|
||||
);
|
||||
|
||||
items$ = this.shoppingCart$.pipe(
|
||||
shoppingCartItemsWithoutOrderType$ = this.shoppingCartItems$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map((shoppingCart) => shoppingCart?.items?.map((item) => item.data) || [])
|
||||
map((items) => items?.filter((item) => item?.features?.orderType === undefined))
|
||||
);
|
||||
|
||||
groupedItems$ = this.items$.pipe(
|
||||
groupedItems$ = this.shoppingCartItems$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map((items) =>
|
||||
items.reduce((grouped, item) => {
|
||||
@@ -65,15 +84,18 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
orderType:
|
||||
item?.availability?.supplyChannel === 'MANUALLY'
|
||||
? 'Dummy'
|
||||
: item.features.orderType === 'DIG-Versand'
|
||||
: item?.features?.orderType === 'DIG-Versand'
|
||||
? 'Versand'
|
||||
: item.features.orderType,
|
||||
destination: item.destination?.data,
|
||||
: item?.features?.orderType,
|
||||
destination: item?.destination?.data,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
group.items = [...group.items, item]?.sort((a, b) => a.destination?.data?.targetBranch?.id - b.destination?.data?.targetBranch?.id);
|
||||
group.items = [...group.items, item]?.sort(
|
||||
(a, b) =>
|
||||
a.destination?.data?.targetBranch?.id - b.destination?.data?.targetBranch?.id || a.product?.name.localeCompare(b.product?.name)
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
grouped[index] = group;
|
||||
@@ -81,26 +103,21 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
grouped.push(group);
|
||||
}
|
||||
|
||||
return [...grouped];
|
||||
return [...grouped].sort((a, b) => (a?.orderType === undefined ? -1 : b?.orderType === undefined ? 1 : 0));
|
||||
}, [] as { orderType: string; destination: DestinationDTO; items: ShoppingCartItemDTO[] }[])
|
||||
)
|
||||
);
|
||||
|
||||
hasItemsForDelivery$ = this.items$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map((items) => items.some((item) => item.features?.orderType === 'Versand' || item.features?.orderType === 'B2B-Versand'))
|
||||
);
|
||||
|
||||
specialComment$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.domainCheckoutService.getSpecialComment({ processId }))
|
||||
);
|
||||
|
||||
totalItemCount$ = this.items$.pipe(
|
||||
totalItemCount$ = this.shoppingCartItems$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map((items) => items.reduce((total, item) => total + item.quantity, 0))
|
||||
);
|
||||
|
||||
totalReadingPoints$ = this.items$.pipe(
|
||||
totalReadingPoints$ = this.shoppingCartItems$.pipe(
|
||||
switchMap((displayOrders) => {
|
||||
if (displayOrders.length === 0) {
|
||||
return NEVER;
|
||||
@@ -133,14 +150,56 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
switchMap((processId) => this.domainCheckoutService.getCustomerFeatures({ processId }))
|
||||
);
|
||||
|
||||
showNotificationChannels$ = this.items$.pipe(
|
||||
showBillingAddress$ = this.shoppingCartItems$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map((items) => items.some((item) => item.features?.orderType === 'Rücklage' || item.features?.orderType === 'Abholung'))
|
||||
withLatestFrom(this.customerFeatures$),
|
||||
map(
|
||||
([items, customerFeatures]) =>
|
||||
items.some(
|
||||
(item) =>
|
||||
item.features?.orderType === 'Versand' ||
|
||||
item.features?.orderType === 'B2B-Versand' ||
|
||||
item.features?.orderType === 'DIG-Versand'
|
||||
) || !!customerFeatures?.b2b
|
||||
)
|
||||
);
|
||||
|
||||
showQuantityControlSpinnerItemId: number;
|
||||
showNotificationChannels$ = combineLatest([this.shoppingCartItems$, this.payer$]).pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map(
|
||||
([items, payer]) =>
|
||||
!!payer && items.some((item) => item.features?.orderType === 'Rücklage' || item.features?.orderType === 'Abholung')
|
||||
)
|
||||
);
|
||||
|
||||
quantityError$ = new BehaviorSubject<{ [key: string]: string }>({});
|
||||
|
||||
primaryCtaLabel$ = combineLatest([this.payer$, this.shoppingCartItemsWithoutOrderType$]).pipe(
|
||||
map(([payer, shoppingCartItemsWithoutOrderType]) => {
|
||||
if (shoppingCartItemsWithoutOrderType?.length > 0) {
|
||||
return 'Kaufoptionen';
|
||||
}
|
||||
if (!payer) {
|
||||
return 'Weiter';
|
||||
}
|
||||
return 'Bestellen';
|
||||
})
|
||||
);
|
||||
|
||||
primaryCtaDisabled$ = this.quantityError$.pipe(
|
||||
map((quantityError) => {
|
||||
for (const key in quantityError) {
|
||||
if (Object.prototype.hasOwnProperty.call(quantityError, key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this.showOrderButtonSpinner;
|
||||
})
|
||||
);
|
||||
|
||||
loadingOnItemChangeById$ = new Subject<number>();
|
||||
loadingOnQuantityChangeById$ = new Subject<number>();
|
||||
showOrderButtonSpinner: boolean;
|
||||
showChangeButtonSpinnerItemId: number;
|
||||
|
||||
constructor(
|
||||
private domainCheckoutService: DomainCheckoutService,
|
||||
@@ -153,13 +212,54 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
private domainCatalogService: DomainCatalogService,
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private domainPrinterService: DomainPrinterService
|
||||
) {}
|
||||
) {
|
||||
super({
|
||||
shoppingCart: undefined,
|
||||
shoppingCartItems: [],
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.applicationService.activatedProcessId$.pipe(takeUntil(this._orderCompleted)).subscribe((_) => {
|
||||
this.loadShoppingCart();
|
||||
});
|
||||
|
||||
await this.removeBreadcrumbs();
|
||||
await this.updateBreadcrumb();
|
||||
}
|
||||
|
||||
loadShoppingCart = this.effect(($) =>
|
||||
$.pipe(
|
||||
withLatestFrom(this.applicationService.activatedProcessId$),
|
||||
switchMap(([_, processId]) => {
|
||||
return this.domainCheckoutService.getShoppingCart({ processId, latest: true }).pipe(
|
||||
tapResponse(
|
||||
(shoppingCart) => {
|
||||
const shoppingCartItems = shoppingCart?.items?.map((item) => item.data) || [];
|
||||
this.patchState({
|
||||
shoppingCart,
|
||||
shoppingCartItems,
|
||||
});
|
||||
this.checkQuantityErrors(shoppingCartItems);
|
||||
},
|
||||
(err) => {},
|
||||
() => {}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
checkQuantityErrors(shoppingCartItems: ShoppingCartItemDTO[]) {
|
||||
shoppingCartItems.forEach((item) => {
|
||||
if (item.features?.orderType === 'Rücklage') {
|
||||
this.setQuantityError(item, item.availability, item.quantity > item.availability?.inStock);
|
||||
} else {
|
||||
this.setQuantityError(item, item.availability, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async updateBreadcrumb() {
|
||||
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
@@ -187,7 +287,7 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
changeDummyItem(shoppingCartItem: ShoppingCartItemDTO) {
|
||||
changeDummyItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
||||
const data = {
|
||||
price: shoppingCartItem?.availability?.price?.value?.value,
|
||||
vat: shoppingCartItem?.availability?.price?.vat?.vatType,
|
||||
@@ -202,8 +302,8 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
this.openDummyModal(data);
|
||||
}
|
||||
|
||||
async changeItem(shoppingCartItem: ShoppingCartItemDTO) {
|
||||
this.showChangeButtonSpinnerItemId = shoppingCartItem.id;
|
||||
async changeItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
||||
this.loadingOnItemChangeById$.next(shoppingCartItem.id);
|
||||
|
||||
const quantity = shoppingCartItem.quantity;
|
||||
|
||||
@@ -329,11 +429,11 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.showChangeButtonSpinnerItemId = undefined;
|
||||
this.loadingOnItemChangeById$.next(undefined);
|
||||
this.cdr.markForCheck();
|
||||
|
||||
const itemId = Number(shoppingCartItem.product.catalogProductNumber);
|
||||
this.uiModal.open({
|
||||
const modal = this.uiModal.open({
|
||||
content: PurchasingOptionsModalComponent,
|
||||
data: {
|
||||
availableOptions,
|
||||
@@ -353,6 +453,10 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
availabilities,
|
||||
} as PurchasingOptionsModalData,
|
||||
});
|
||||
|
||||
modal.afterClosed$.pipe(takeUntil(this._orderCompleted)).subscribe(() => {
|
||||
this.setQuantityError(shoppingCartItem, undefined, false);
|
||||
});
|
||||
}
|
||||
|
||||
setAgentComment(agentComment: string) {
|
||||
@@ -374,8 +478,12 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
async updateItemQuantity(shoppingCartItem: ShoppingCartItemDTO, quantity: number) {
|
||||
this.showQuantityControlSpinnerItemId = shoppingCartItem.id;
|
||||
async updateItemQuantity({ shoppingCartItem, quantity }: { shoppingCartItem: ShoppingCartItemDTO; quantity: number }) {
|
||||
if (quantity === shoppingCartItem.quantity) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingOnQuantityChangeById$.next(shoppingCartItem.id);
|
||||
|
||||
const shoppingCartItemPrice = shoppingCartItem?.availability?.price?.value?.value;
|
||||
const orderType = shoppingCartItem?.features?.orderType;
|
||||
@@ -397,6 +505,8 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
quantity,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
|
||||
break;
|
||||
case 'Abholung':
|
||||
availability = await this.availabilityService
|
||||
@@ -473,6 +583,7 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
this.setQuantityError(shoppingCartItem, availability, false);
|
||||
} else if (availability) {
|
||||
await this.domainCheckoutService
|
||||
.updateItemInShoppingCart({
|
||||
@@ -485,9 +596,29 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
})
|
||||
.toPromise();
|
||||
} else {
|
||||
// TODO: Set Prev Quantity
|
||||
await this.domainCheckoutService
|
||||
.updateItemInShoppingCart({
|
||||
processId: this.applicationService.activatedProcessId,
|
||||
shoppingCartItemId: shoppingCartItem.id,
|
||||
update: {
|
||||
quantity,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
this.loadingOnQuantityChangeById$.next(undefined);
|
||||
}
|
||||
|
||||
setQuantityError(item: ShoppingCartItemDTO, availability: AvailabilityDTO, error: boolean) {
|
||||
const quantityErrors: { [key: string]: string } = this.quantityError$.value;
|
||||
if (error) {
|
||||
quantityErrors[item.product.catalogProductNumber] = `${availability.inStock} Exemplar(e) sofort lieferbar`;
|
||||
this.quantityError$.next({ ...quantityErrors });
|
||||
} else {
|
||||
delete quantityErrors[item.product.catalogProductNumber];
|
||||
this.quantityError$.next({ ...quantityErrors });
|
||||
}
|
||||
this.showQuantityControlSpinnerItemId = undefined;
|
||||
}
|
||||
|
||||
// Bei unbekannten Kunden und DIG Bestellung findet ein Vergleich der Preise statt
|
||||
@@ -534,7 +665,28 @@ export class CheckoutReviewComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
async showPurchasingListModal(shoppingCartItems: ShoppingCartItemDTO[]) {
|
||||
const customerFeatures = await this.customerFeatures$.pipe(first()).toPromise();
|
||||
this.uiModal.open({
|
||||
content: PurchasingOptionsListModalComponent,
|
||||
title: 'Wie möchten Sie die Artikel erhalten?',
|
||||
config: { showScrollbarY: false },
|
||||
data: {
|
||||
processId: this.applicationService.activatedProcessId,
|
||||
shoppingCartItems: shoppingCartItems,
|
||||
customerFeatures,
|
||||
} as PurchasingOptionsListModalData,
|
||||
});
|
||||
}
|
||||
|
||||
async order() {
|
||||
const shoppingCartItemsWithoutOrderType = await this.shoppingCartItemsWithoutOrderType$.pipe(first()).toPromise();
|
||||
|
||||
if (shoppingCartItemsWithoutOrderType?.length > 0) {
|
||||
this.showPurchasingListModal(shoppingCartItemsWithoutOrderType);
|
||||
return;
|
||||
}
|
||||
|
||||
const processId = this.applicationService.activatedProcessId;
|
||||
const customer = await this.domainCheckoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
if (!customer) {
|
||||
|
||||
@@ -17,10 +17,13 @@ import { NotificationEditComponent } from './notification-channels/notification-
|
||||
import { UiCheckboxModule } from '@ui/checkbox';
|
||||
import { SpecialCommentComponent } from './special-comment/special-comment.component';
|
||||
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-item.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
UiCommonModule,
|
||||
RouterModule,
|
||||
PageCheckoutPipeModule,
|
||||
UiIconModule,
|
||||
@@ -41,6 +44,7 @@ import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
NotificationEditComponent,
|
||||
NotificationCheckboxComponent,
|
||||
SpecialCommentComponent,
|
||||
ShoppingCartItemComponent,
|
||||
],
|
||||
})
|
||||
export class CheckoutReviewModule {}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<div class="item-thumbnail">
|
||||
<a [routerLink]="['/product', 'details', 'ean', item?.product?.ean]">
|
||||
<img loading="lazy" *ngIf="item?.product?.ean | productImage; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.title" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="item-contributors">
|
||||
<a
|
||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||
[routerLink]="['/product/search/results']"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="item-title"
|
||||
[class.xl]="item?.product?.name?.length >= 35"
|
||||
[class.lg]="item?.product?.name?.length >= 40"
|
||||
[class.md]="item?.product?.name?.length >= 50"
|
||||
[class.sm]="item?.product?.name?.length >= 60"
|
||||
[class.xs]="item?.product?.name?.length >= 100"
|
||||
>
|
||||
<a [routerLink]="['/product', 'details', 'ean', item?.product?.ean]">{{ item?.product?.name }}</a>
|
||||
</div>
|
||||
|
||||
<div class="item-format">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail }}
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }} <br />
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ item?.product?.publicationDate | date }}
|
||||
|
||||
<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>
|
||||
|
||||
<div class="item-availability-message" *ngIf="olaError$ | async">
|
||||
Artikel nicht verfügbar
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-price-stock">
|
||||
<div>{{ item?.availability?.price?.value?.value | currency: 'EUR':'code' }}</div>
|
||||
<div>
|
||||
<ui-quantity-dropdown
|
||||
*ngIf="!(isDummy$ | async); else quantityDummy"
|
||||
[ngModel]="item?.quantity"
|
||||
(ngModelChange)="onChangeQuantity($event)"
|
||||
[showSpinner]="(loadingOnQuantityChangeById$ | async) === item?.id"
|
||||
[disabled]="(loadingOnItemChangeById$ | async) === item?.id"
|
||||
[range]="quantityRange$ | async"
|
||||
>
|
||||
</ui-quantity-dropdown>
|
||||
<ng-template #quantityDummy>
|
||||
{{ item?.quantity }}
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="quantity-error" *ngIf="quantityError">
|
||||
{{ quantityError }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions" *ngIf="orderType !== 'Download'">
|
||||
<button
|
||||
[disabled]="(loadingOnQuantityChangeById$ | async) === item?.id || (loadingOnItemChangeById$ | async) === item?.id"
|
||||
(click)="onChangeItem()"
|
||||
*ngIf="!(hasOrderType$ | async)"
|
||||
>
|
||||
<ui-spinner [show]="(loadingOnItemChangeById$ | async) === item?.id">
|
||||
Auswählen
|
||||
</ui-spinner>
|
||||
</button>
|
||||
<button
|
||||
[disabled]="(loadingOnQuantityChangeById$ | async) === item?.id || (loadingOnItemChangeById$ | async) === item?.id"
|
||||
(click)="onChangeItem()"
|
||||
*ngIf="(isDummy$ | async) || (hasOrderType$ | async)"
|
||||
>
|
||||
<ui-spinner [show]="(loadingOnItemChangeById$ | async) === item?.id">
|
||||
Ändern
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,158 @@
|
||||
:host {
|
||||
@apply text-black no-underline grid p-4;
|
||||
grid-template-columns: 102px 50% auto;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas:
|
||||
'item-thumbnail item-contributors item-contributors'
|
||||
'item-thumbnail item-title item-price-stock'
|
||||
'item-thumbnail item-format item-price-stock'
|
||||
'item-thumbnail item-info actions'
|
||||
'item-thumbnail item-date actions'
|
||||
'item-thumbnail item-ssc actions'
|
||||
'item-thumbnail item-availability actions';
|
||||
}
|
||||
|
||||
button {
|
||||
@apply p-0;
|
||||
}
|
||||
|
||||
.item-thumbnail {
|
||||
grid-area: item-thumbnail;
|
||||
width: 70px;
|
||||
@apply mr-8;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
@apply rounded-card shadow-cta;
|
||||
}
|
||||
}
|
||||
|
||||
.item-contributors {
|
||||
grid-area: item-contributors;
|
||||
height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 600px;
|
||||
white-space: nowrap;
|
||||
|
||||
a {
|
||||
@apply text-active-customer font-bold no-underline;
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
grid-area: item-title;
|
||||
@apply font-bold text-lg mb-4;
|
||||
max-height: 64px;
|
||||
|
||||
a {
|
||||
@apply text-active-customer no-underline;
|
||||
}
|
||||
}
|
||||
|
||||
.item-title.xl {
|
||||
@apply font-bold text-xl;
|
||||
}
|
||||
|
||||
.item-title.lg {
|
||||
@apply font-bold text-lg;
|
||||
}
|
||||
|
||||
.item-title.md {
|
||||
@apply font-bold text-base;
|
||||
}
|
||||
|
||||
.item-title.sm {
|
||||
@apply font-bold text-sm;
|
||||
}
|
||||
|
||||
.item-title.xs {
|
||||
@apply font-bold text-xs;
|
||||
}
|
||||
|
||||
.item-format {
|
||||
grid-area: item-format;
|
||||
@apply flex flex-row items-center font-bold text-lg whitespace-nowrap;
|
||||
|
||||
img {
|
||||
@apply mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.item-price-stock {
|
||||
grid-area: item-price-stock;
|
||||
@apply font-bold text-xl text-right;
|
||||
|
||||
.quantity {
|
||||
@apply flex flex-row justify-end items-center;
|
||||
}
|
||||
|
||||
.quantity-error {
|
||||
@apply text-dark-goldenrod font-bold text-sm whitespace-nowrap;
|
||||
}
|
||||
|
||||
ui-quantity-dropdown {
|
||||
@apply flex justify-end mt-2;
|
||||
}
|
||||
}
|
||||
|
||||
.item-stock {
|
||||
grid-area: item-stock;
|
||||
@apply flex flex-row justify-end items-baseline font-bold text-lg;
|
||||
|
||||
ui-icon {
|
||||
@apply text-active-customer mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.item-info {
|
||||
grid-area: item-info;
|
||||
|
||||
.item-date {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
.item-availability-message {
|
||||
@apply text-dark-goldenrod font-bold text-sm whitespace-nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.item-availability {
|
||||
@apply flex flex-row items-center mt-4;
|
||||
grid-area: item-availability;
|
||||
|
||||
.fetching {
|
||||
@apply w-52 h-px-20;
|
||||
background-color: #e6eff9;
|
||||
animation: load 0.75s linear infinite;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply mr-4;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply text-dark-cerulean mx-1;
|
||||
}
|
||||
|
||||
div {
|
||||
@apply ml-2 flex items-center;
|
||||
}
|
||||
|
||||
.truck {
|
||||
@apply -mb-px-5 -mt-px-5;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply flex items-center justify-end;
|
||||
grid-area: actions;
|
||||
|
||||
button {
|
||||
@apply bg-transparent text-brand font-bold text-lg outline-none border-none;
|
||||
|
||||
&:disabled {
|
||||
@apply text-disabled-customer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
|
||||
export interface ShoppingCartItemComponentState {
|
||||
item: ShoppingCartItemDTO;
|
||||
orderType: string;
|
||||
loadingOnItemChangeById?: number;
|
||||
loadingOnQuantityChangeById?: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-shopping-cart-item',
|
||||
templateUrl: 'shopping-cart-item.component.html',
|
||||
styleUrls: ['shopping-cart-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemComponentState> implements OnInit {
|
||||
@Output() changeItem = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO }>();
|
||||
@Output() changeDummyItem = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO }>();
|
||||
@Output() changeQuantity = new EventEmitter<{ shoppingCartItem: ShoppingCartItemDTO; quantity: number }>();
|
||||
|
||||
@Input()
|
||||
get item() {
|
||||
return this.get((s) => s.item);
|
||||
}
|
||||
set item(item: ShoppingCartItemDTO) {
|
||||
if (this.item !== item) {
|
||||
this.patchState({ item });
|
||||
}
|
||||
}
|
||||
readonly item$ = this.select((s) => s.item);
|
||||
|
||||
readonly contributors$ = this.item$.pipe(map((item) => item?.product?.contributors?.split(';').map((val) => val.trim())));
|
||||
|
||||
@Input()
|
||||
get orderType() {
|
||||
return this.get((s) => s.orderType);
|
||||
}
|
||||
set orderType(orderType: string) {
|
||||
if (this.orderType !== orderType) {
|
||||
this.patchState({ orderType });
|
||||
}
|
||||
}
|
||||
readonly orderType$ = this.select((s) => s.orderType);
|
||||
|
||||
@Input()
|
||||
get loadingOnItemChangeById() {
|
||||
return this.get((s) => s.loadingOnItemChangeById);
|
||||
}
|
||||
set loadingOnItemChangeById(loadingOnItemChangeById: number) {
|
||||
if (this.loadingOnItemChangeById !== loadingOnItemChangeById) {
|
||||
this.patchState({ loadingOnItemChangeById });
|
||||
}
|
||||
}
|
||||
readonly loadingOnItemChangeById$ = this.select((s) => s.loadingOnItemChangeById).pipe(shareReplay());
|
||||
|
||||
@Input()
|
||||
get loadingOnQuantityChangeById() {
|
||||
return this.get((s) => s.loadingOnQuantityChangeById);
|
||||
}
|
||||
set loadingOnQuantityChangeById(loadingOnQuantityChangeById: number) {
|
||||
if (this.loadingOnQuantityChangeById !== loadingOnQuantityChangeById) {
|
||||
this.patchState({ loadingOnQuantityChangeById });
|
||||
}
|
||||
}
|
||||
readonly loadingOnQuantityChangeById$ = this.select((s) => s.loadingOnQuantityChangeById).pipe(shareReplay());
|
||||
|
||||
@Input()
|
||||
quantityError: string;
|
||||
|
||||
isDummy$ = this.item$.pipe(
|
||||
map((item) => item?.availability?.supplyChannel === 'MANUALLY'),
|
||||
shareReplay()
|
||||
);
|
||||
hasOrderType$ = this.orderType$.pipe(
|
||||
map((orderType) => orderType !== undefined),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
quantityRange$ = combineLatest([this.orderType$, this.item$]).pipe(
|
||||
map(([orderType, item]) => (orderType === 'Rücklage' ? item.availability?.inStock : 999))
|
||||
);
|
||||
|
||||
isDownloadAvailable$ = combineLatest([this.item$, this.orderType$]).pipe(
|
||||
filter(([_, orderType]) => orderType === 'Download'),
|
||||
switchMap(([item]) =>
|
||||
this.availabilityService.getDownloadAvailability({
|
||||
item: { ean: item.product.ean, price: item.availability.price, itemId: +item.product.catalogProductNumber },
|
||||
})
|
||||
),
|
||||
map((availability) => availability && this.availabilityService.isAvailable({ availability }))
|
||||
);
|
||||
|
||||
olaError$ = this.checkoutService
|
||||
.getOlaErrors({ processId: this.application.activatedProcessId })
|
||||
.pipe(map((ids) => ids?.find((id) => id === this.item.id)));
|
||||
|
||||
constructor(
|
||||
private availabilityService: DomainAvailabilityService,
|
||||
private checkoutService: DomainCheckoutService,
|
||||
private application: ApplicationService
|
||||
) {
|
||||
super({ item: undefined, orderType: '' });
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
async onChangeItem() {
|
||||
const isDummy = await this.isDummy$.pipe(first()).toPromise();
|
||||
isDummy ? this.changeDummyItem.emit({ shoppingCartItem: this.item }) : this.changeItem.emit({ shoppingCartItem: this.item });
|
||||
}
|
||||
|
||||
onChangeQuantity(quantity: number) {
|
||||
this.changeQuantity.emit({ shoppingCartItem: this.item, quantity });
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
<label for="agent-comment">Anmerkung</label>
|
||||
<textarea #input type="text" id="agent-comment" name="agent-comment" [(ngModel)]="value" [rows]="rows" (ngModelChange)="check()"></textarea>
|
||||
<textarea
|
||||
#input
|
||||
type="text"
|
||||
id="agent-comment"
|
||||
name="agent-comment"
|
||||
[(ngModel)]="value"
|
||||
[rows]="rows"
|
||||
(ngModelChange)="check()"
|
||||
(blur)="save()"
|
||||
></textarea>
|
||||
|
||||
<div class="action-wrapper">
|
||||
<button type="button" *ngIf="!disabled && !!value" (click)="clear()">
|
||||
<ui-icon icon="close" size="14px"></ui-icon>
|
||||
</button>
|
||||
<button type="button" *ngIf="!disabled && isDirty" (click)="save()">Speichern</button>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{{ displayOrder?.buyer | buyerName }}
|
||||
</div>
|
||||
<div class="grow"></div>
|
||||
<button [disabled]="displayOrder?.features?.orderType === 'B2B-Versand'" class="cta-print" (click)="openPrintModal(displayOrder.id)">
|
||||
<button class="cta-print" (click)="openPrintModal(displayOrder.id)">
|
||||
Drucken
|
||||
</button>
|
||||
</div>
|
||||
@@ -82,7 +82,15 @@
|
||||
<ng-container *ngSwitchCase="['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(order?.features?.orderType) > -1">
|
||||
<span class="supplier">{{ (order?.subsetItems)[0].supplierLabel }}</span>
|
||||
<span class="separator">|</span>
|
||||
<span class="order-type">Versanddatum {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}</span>
|
||||
<ng-container *ngIf="(order?.subsetItems)[0]?.estimatedDelivery; else estimatedShippingDate">
|
||||
<span class="order-type"
|
||||
>Zustellung zwischen {{ ((order?.subsetItems)[0]?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}</span
|
||||
>
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate>
|
||||
<span class="order-type">Versanddatum {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}</span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<ng-container *ngIf="(order?.subsetItems)[0].supplierLabel; let supplierLabel">
|
||||
|
||||
@@ -127,12 +127,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.quantity {
|
||||
@apply w-px-30 text-right font-semibold;
|
||||
}
|
||||
.product-price {
|
||||
@apply whitespace-nowrap ml-4;
|
||||
|
||||
.price {
|
||||
@apply w-px-100 font-bold text-right ml-5;
|
||||
.quantity {
|
||||
@apply w-px-30 text-right font-semibold;
|
||||
}
|
||||
|
||||
.price {
|
||||
@apply w-px-100 font-bold text-right ml-4;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
||||
@@ -20,7 +20,16 @@ import { NEVER } from 'rxjs';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutSummaryComponent {
|
||||
displayOrders$ = this.domainCheckoutService.getOrders();
|
||||
displayOrders$ = this.domainCheckoutService.getOrders().pipe(
|
||||
map((orders) =>
|
||||
orders.map((order) => {
|
||||
return {
|
||||
...order,
|
||||
items: [...order.items]?.sort((a, b) => a.product?.name.localeCompare(b.product?.name)),
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
totalItemCount$ = this.displayOrders$.pipe(
|
||||
map((displayOrders) =>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="option-icon">
|
||||
<ui-icon size="50px" icon="truck"></ui-icon>
|
||||
</div>
|
||||
<button
|
||||
class="option-chip"
|
||||
[disabled]="optionChipDisabled$ | async"
|
||||
(click)="optionChange('delivery')"
|
||||
[class.selected]="(selectedOption$ | async) === 'delivery'"
|
||||
>
|
||||
Versand
|
||||
</button>
|
||||
<p>Möchten Sie die Artikel<br />geliefert bekommen?</p>
|
||||
<p>Versandkostenfrei</p>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { PurchasingOptionsListModalStore } from '../purchasing-options-list-modal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-delivery-option-list',
|
||||
templateUrl: 'delivery-option-list.component.html',
|
||||
styleUrls: ['../list-options.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DeliveryOptionListComponent {
|
||||
selectedOption$ = this._store.selectedFilterOption$;
|
||||
optionChipDisabled$ = this._store.fetchingAvailabilities$;
|
||||
|
||||
constructor(private _store: PurchasingOptionsListModalStore) {}
|
||||
|
||||
optionChange(option: string) {
|
||||
if (this._store.selectedFilterOption === option) {
|
||||
this._store.selectedFilterOption = undefined;
|
||||
} else {
|
||||
this._store.selectedFilterOption = option;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './delivery-option-list.component';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,7 @@
|
||||
// start:ng42.barrel
|
||||
export * from './delivery-option';
|
||||
export * from './pick-up-option';
|
||||
export * from './take-away-option';
|
||||
export * from './purchasing-options-list-modal.component';
|
||||
export * from './purchasing-options-list-modal.module';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,35 @@
|
||||
:host {
|
||||
@apply block w-72;
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
@apply text-ucla-blue mx-auto;
|
||||
width: 40px;
|
||||
|
||||
.truck-b2b {
|
||||
margin-top: -21px;
|
||||
margin-bottom: -12px;
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.option-chip {
|
||||
@apply rounded-full text-base px-4 py-3 bg-glitter text-inactive-customer border-none font-bold;
|
||||
|
||||
&.selected {
|
||||
@apply bg-active-customer text-white;
|
||||
}
|
||||
}
|
||||
|
||||
.option-description {
|
||||
@apply my-2;
|
||||
}
|
||||
|
||||
.option-select {
|
||||
@apply mt-4 mb-4 border-2 border-solid border-brand text-brand text-cta-l font-bold bg-white rounded-full py-3 px-6;
|
||||
}
|
||||
|
||||
::ng-deep page-purchasing-options-list-modal ui-branch-dropdown .wrapper {
|
||||
@apply mx-auto;
|
||||
width: 80%;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './pick-up-option-list.component';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="option-icon">
|
||||
<ui-icon size="50px" icon="box_out"></ui-icon>
|
||||
</div>
|
||||
<button
|
||||
class="option-chip"
|
||||
[disabled]="optionChipDisabled$ | async"
|
||||
(click)="optionChange('pick-up')"
|
||||
[class.selected]="(selectedOption$ | async) === 'pick-up'"
|
||||
>
|
||||
Abholung
|
||||
</button>
|
||||
<p>Möchten Sie die Artikel<br />in einer unserer Filialen<br />abholen?</p>
|
||||
|
||||
<ui-branch-dropdown
|
||||
[branches]="branches$ | async"
|
||||
[selected]="(selectedBranch$ | async)?.name"
|
||||
(selectBranch)="selectBranch($event)"
|
||||
></ui-branch-dropdown>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { PurchasingOptionsListModalStore } from '../purchasing-options-list-modal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-pick-up-option-list',
|
||||
templateUrl: 'pick-up-option-list.component.html',
|
||||
styleUrls: ['../list-options.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PickUpOptionListComponent {
|
||||
branches$ = this._store.branches$;
|
||||
selectedBranch$ = this._store.selectedPickUpBranch$;
|
||||
selectedOption$ = this._store.selectedFilterOption$;
|
||||
optionChipDisabled$ = this._store.fetchingAvailabilities$;
|
||||
|
||||
constructor(private _store: PurchasingOptionsListModalStore) {}
|
||||
|
||||
optionChange(option: string) {
|
||||
if (this._store.selectedFilterOption === option) {
|
||||
this._store.selectedFilterOption = undefined;
|
||||
} else {
|
||||
this._store.selectedFilterOption = option;
|
||||
}
|
||||
}
|
||||
|
||||
async selectBranch(branch: BranchDTO) {
|
||||
this._store.lastSelectedFilterOption$.next(undefined);
|
||||
|
||||
this._store.selectedPickUpBranch = branch;
|
||||
|
||||
const shoppingCartItems = await this._store.shoppingCartItems$.pipe(first()).toPromise();
|
||||
shoppingCartItems.forEach((item) => this._store.loadPickUpAvailability({ item }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<div class="item-thumbnail">
|
||||
<img loading="lazy" *ngIf="item?.product?.ean | productImage; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.title" />
|
||||
</div>
|
||||
|
||||
<div class="item-contributors">
|
||||
{{ item.product.contributors }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="item-title"
|
||||
[class.xl]="item?.product?.name?.length >= 35"
|
||||
[class.lg]="item?.product?.name?.length >= 40"
|
||||
[class.md]="item?.product?.name?.length >= 50"
|
||||
[class.sm]="item?.product?.name?.length >= 60"
|
||||
[class.xs]="item?.product?.name?.length >= 100"
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="canAdd$ | async; let canAdd">
|
||||
<div class="item-can-add" *ngIf="canAdd !== true">
|
||||
{{ canAdd }}
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="item-format">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail }}
|
||||
</div>
|
||||
|
||||
<div class="item-info">
|
||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }} <br />
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ item?.product?.publicationDate | date: 'dd. MMMM yyyy' }}
|
||||
</div>
|
||||
|
||||
<div class="item-price-stock">
|
||||
<div class="price">
|
||||
<ng-container *ngIf="showTooltip$ | async">
|
||||
<button [uiOverlayTrigger]="tooltipContent" #tooltip="uiOverlayTrigger" class="info-tooltip-button" type="button">
|
||||
i
|
||||
</button>
|
||||
<ui-tooltip #tooltipContent yPosition="above" xPosition="after" [yOffset]="-16">
|
||||
Günstigerer Preis aus Hugendubel Katalog wird übernommen
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ui-quantity-dropdown
|
||||
[disabled]="fetchingAvailabilities$ | async"
|
||||
[ngModel]="item.quantity"
|
||||
(ngModelChange)="changeQuantity($event)"
|
||||
[range]="quantityRange$ | async"
|
||||
>
|
||||
</ui-quantity-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-select">
|
||||
<ui-select-bullet
|
||||
*ngIf="selectVisible$ | async"
|
||||
[disabled]="selectDisabled$ | async"
|
||||
[ngModel]="isSelected$ | async"
|
||||
(ngModelChange)="selected($event)"
|
||||
></ui-select-bullet>
|
||||
</div>
|
||||
|
||||
<div class="item-availability">
|
||||
<div class="fetching" *ngIf="fetchingAvailabilities$ | async; else availabilities"></div>
|
||||
<ng-template #availabilities>
|
||||
<ng-container *ngIf="notAvailable$ | async; else available">
|
||||
<span class="hint">Derzeit nicht bestellbar</span>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #available>
|
||||
<span>Verfügbar als</span>
|
||||
<div *ngIf="takeAwayAvailabilities$ | async; let takeAwayAvailabilites">
|
||||
<ui-icon icon="shopping_bag" size="18px"></ui-icon>
|
||||
<span class="instock">{{ takeAwayAvailabilites?.inStock }}x</span> ab sofort
|
||||
</div>
|
||||
|
||||
<div *ngIf="!!(pickUpAvailabilities$ | async)">
|
||||
<ui-icon icon="box_out" size="18px"></ui-icon>
|
||||
{{ (pickUpAvailabilities$ | async)?.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="!!(deliveryDigAvailabilities$ | async); else b2b">
|
||||
<ui-icon class="truck" icon="truck" size="30px"></ui-icon>
|
||||
<ng-container *ngIf="deliveryDigAvailabilities$ | async; let deliveryDigAvailabilities">
|
||||
<ng-container *ngIf="deliveryDigAvailabilities?.estimatedDelivery; else estimatedShippingDate">
|
||||
{{ (deliveryDigAvailabilities?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} -
|
||||
{{ (deliveryDigAvailabilities?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate>
|
||||
{{ deliveryDigAvailabilities.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-template #b2b>
|
||||
<div *ngIf="!!(deliveryB2bAvailabilities$ | async)">
|
||||
<ui-icon class="truck-b2b" icon="truck_b2b" size="40px"></ui-icon>
|
||||
{{ (deliveryB2bAvailabilities$ | async)?.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -0,0 +1,180 @@
|
||||
:host {
|
||||
@apply text-black no-underline grid py-4;
|
||||
grid-template-columns: 102px 60% auto;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas:
|
||||
'item-thumbnail item-contributors item-contributors'
|
||||
'item-thumbnail item-title item-price-stock'
|
||||
'item-thumbnail item-can-add item-price-stock'
|
||||
'item-thumbnail item-format item-price-stock'
|
||||
'item-thumbnail item-info item-select'
|
||||
'item-thumbnail item-date item-select'
|
||||
'item-thumbnail item-ssc item-select'
|
||||
'item-thumbnail item-availability item-select';
|
||||
}
|
||||
|
||||
.item-thumbnail {
|
||||
grid-area: item-thumbnail;
|
||||
width: 70px;
|
||||
@apply mr-8;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
@apply rounded-card shadow-cta;
|
||||
}
|
||||
}
|
||||
|
||||
.item-contributors {
|
||||
@apply font-bold no-underline;
|
||||
grid-area: item-contributors;
|
||||
height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 600px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
grid-area: item-title;
|
||||
@apply font-bold text-lg mb-4;
|
||||
max-height: 64px;
|
||||
}
|
||||
|
||||
.item-title.xl {
|
||||
@apply font-bold text-xl;
|
||||
}
|
||||
|
||||
.item-title.lg {
|
||||
@apply font-bold text-lg;
|
||||
}
|
||||
|
||||
.item-title.md {
|
||||
@apply font-bold text-base;
|
||||
}
|
||||
|
||||
.item-title.sm {
|
||||
@apply font-bold text-sm;
|
||||
}
|
||||
|
||||
.item-title.xs {
|
||||
@apply font-bold text-xs;
|
||||
}
|
||||
|
||||
.item-format {
|
||||
grid-area: item-format;
|
||||
@apply flex flex-row items-center font-bold text-lg whitespace-nowrap;
|
||||
|
||||
img {
|
||||
@apply mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.item-price-stock {
|
||||
grid-area: item-price-stock;
|
||||
@apply font-bold text-xl text-right;
|
||||
|
||||
.price {
|
||||
@apply flex flex-row justify-end items-center;
|
||||
}
|
||||
|
||||
.info-tooltip-button {
|
||||
@apply border-font-customer bg-white rounded-full text-base font-bold mr-3;
|
||||
border-style: outset;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.quantity-btn {
|
||||
@apply flex flex-row items-center p-0 w-full text-right outline-none border-none bg-transparent text-lg;
|
||||
}
|
||||
|
||||
.quantity-btn-icon {
|
||||
@apply inline-flex ml-2;
|
||||
transition: transform 200ms ease-in-out;
|
||||
}
|
||||
|
||||
ui-quantity-dropdown {
|
||||
@apply flex justify-end mt-2;
|
||||
|
||||
&.disabled {
|
||||
@apply cursor-not-allowed bg-inactive-branch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-stock {
|
||||
grid-area: item-stock;
|
||||
@apply flex flex-row justify-end items-baseline font-bold text-lg;
|
||||
|
||||
ui-icon {
|
||||
@apply text-active-customer mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.item-info {
|
||||
grid-area: item-info;
|
||||
}
|
||||
|
||||
.item-availability {
|
||||
@apply flex flex-row items-center mt-4 whitespace-nowrap text-sm;
|
||||
grid-area: item-availability;
|
||||
|
||||
.fetching {
|
||||
@apply w-52 h-px-20;
|
||||
background-color: #e6eff9;
|
||||
animation: load 0.75s linear infinite;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply mr-4;
|
||||
}
|
||||
|
||||
.instock {
|
||||
@apply mr-2 font-bold;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply text-dark-cerulean mx-2;
|
||||
}
|
||||
|
||||
div {
|
||||
@apply mr-4 flex items-center;
|
||||
}
|
||||
|
||||
.truck {
|
||||
@apply -mb-px-5 -mt-px-5;
|
||||
}
|
||||
|
||||
.truck-b2b {
|
||||
@apply -mb-px-10 -mt-px-10;
|
||||
}
|
||||
}
|
||||
|
||||
.item-can-add {
|
||||
@apply text-xl text-dark-goldenrod font-semibold;
|
||||
grid-area: item-can-add;
|
||||
}
|
||||
|
||||
.item-select {
|
||||
@apply flex items-center justify-end;
|
||||
grid-area: item-select;
|
||||
|
||||
ui-select-bullet {
|
||||
@apply cursor-pointer p-4 -m-4 z-dropdown;
|
||||
|
||||
&.disabled {
|
||||
@apply cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hint {
|
||||
@apply text-xl text-dark-goldenrod font-semibold;
|
||||
}
|
||||
|
||||
@screen desktop {
|
||||
.item-availability {
|
||||
@apply text-base;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { AvailabilityDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { filter, map, shareReplay, withLatestFrom } from 'rxjs/operators';
|
||||
import { PurchasingOptionsListModalStore } from '../purchasing-options-list-modal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-purchasing-options-list-item',
|
||||
templateUrl: 'purchasing-options-list-item.component.html',
|
||||
styleUrls: ['purchasing-options-list-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PurchasingOptionsListItemComponent {
|
||||
@Input()
|
||||
item: ShoppingCartItemDTO;
|
||||
|
||||
isSelected$ = this._store.selectedShoppingCartItems$.pipe(
|
||||
map((selectedShoppingCartItems) => !!selectedShoppingCartItems?.find((item) => item.id === this.item.id))
|
||||
);
|
||||
|
||||
fetchingAvailabilities$ = combineLatest([
|
||||
this._store.takeAwayAvailabilities$,
|
||||
this._store.pickUpAvailabilities$,
|
||||
this._store.deliveryAvailabilities$,
|
||||
this._store.deliveryDigAvailabilities$,
|
||||
this._store.deliveryB2bAvailabilities$,
|
||||
]).pipe(
|
||||
map(
|
||||
([takeAway, pickUp, delivery, digDelivery, b2bDelivery]) =>
|
||||
!takeAway ||
|
||||
takeAway[this.item.product.catalogProductNumber] === true ||
|
||||
!pickUp ||
|
||||
pickUp[this.item.product.catalogProductNumber] === true ||
|
||||
!delivery ||
|
||||
delivery[this.item.product.catalogProductNumber] === true ||
|
||||
!digDelivery ||
|
||||
digDelivery[this.item.product.catalogProductNumber] === true ||
|
||||
!b2bDelivery ||
|
||||
b2bDelivery[this.item.product.catalogProductNumber] === true
|
||||
)
|
||||
);
|
||||
|
||||
takeAwayAvailabilities$ = this._store.takeAwayAvailabilities$.pipe(
|
||||
map((takeAwayAvailabilities) => (!!takeAwayAvailabilities ? takeAwayAvailabilities[this.item.product?.catalogProductNumber] : [])),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
pickUpAvailabilities$ = this._store.pickUpAvailabilities$.pipe(
|
||||
map((pickUpAvailabilities) => (!!pickUpAvailabilities ? pickUpAvailabilities[this.item.product?.catalogProductNumber] : [])),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
deliveryAvailabilities$ = this._store.deliveryAvailabilities$.pipe(
|
||||
map((shippingAvailabilities) => (!!shippingAvailabilities ? shippingAvailabilities[this.item.product?.catalogProductNumber] : [])),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
deliveryDigAvailabilities$ = this._store.deliveryDigAvailabilities$.pipe(
|
||||
map((shippingAvailabilities) => (!!shippingAvailabilities ? shippingAvailabilities[this.item.product?.catalogProductNumber] : [])),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
deliveryB2bAvailabilities$ = this._store.deliveryB2bAvailabilities$.pipe(
|
||||
map((shippingAvailabilities) => (!!shippingAvailabilities ? shippingAvailabilities[this.item.product?.catalogProductNumber] : [])),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
notAvailable$ = combineLatest([
|
||||
this.fetchingAvailabilities$,
|
||||
this.takeAwayAvailabilities$,
|
||||
this.pickUpAvailabilities$,
|
||||
this.deliveryAvailabilities$,
|
||||
this.deliveryDigAvailabilities$,
|
||||
this.deliveryB2bAvailabilities$,
|
||||
]).pipe(
|
||||
map(
|
||||
([fetching, takeAway, store, delivery, deliveryDig, deliveryB2b]) =>
|
||||
!fetching && !takeAway && !store && !delivery && !deliveryDig && !deliveryB2b
|
||||
)
|
||||
);
|
||||
|
||||
showTooltip$ = this._store.selectedFilterOption$.pipe(
|
||||
withLatestFrom(this.deliveryAvailabilities$, this.deliveryDigAvailabilities$),
|
||||
map(([option, delivery, deliveryDig]) => {
|
||||
if (option === 'delivery') {
|
||||
const deliveryAvailability = (deliveryDig as AvailabilityDTO) || (delivery as AvailabilityDTO);
|
||||
|
||||
const shippingPrice = deliveryAvailability?.price?.value?.value;
|
||||
const catalogPrice = this.item?.availability?.price?.value?.value;
|
||||
return catalogPrice < shippingPrice;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
);
|
||||
|
||||
price$ = this.fetchingAvailabilities$.pipe(
|
||||
filter((fetching) => !fetching),
|
||||
withLatestFrom(
|
||||
this._store.selectedFilterOption$,
|
||||
this.takeAwayAvailabilities$,
|
||||
this.pickUpAvailabilities$,
|
||||
this.deliveryAvailabilities$,
|
||||
this.deliveryDigAvailabilities$,
|
||||
this.deliveryB2bAvailabilities$
|
||||
),
|
||||
map(([_, option, takeAway, pickUp, delivery, deliveryDig, deliveryB2b]) => {
|
||||
let availability;
|
||||
switch (option) {
|
||||
case 'take-away':
|
||||
availability = takeAway;
|
||||
break;
|
||||
case 'pick-up':
|
||||
availability = pickUp;
|
||||
break;
|
||||
case 'delivery':
|
||||
availability = deliveryDig || delivery || deliveryB2b;
|
||||
break;
|
||||
default:
|
||||
return this.item.availability?.price;
|
||||
}
|
||||
|
||||
return this._availabilityService.getPriceForAvailability(option, this.item.availability, availability);
|
||||
})
|
||||
);
|
||||
|
||||
selectDisabled$ = this._store.selectedFilterOption$.pipe(map((selectedFilterOption) => !selectedFilterOption));
|
||||
|
||||
selectVisible$ = combineLatest([this._store.canAdd$, this._store.selectedShoppingCartItems$]).pipe(
|
||||
withLatestFrom(
|
||||
this._store.selectedFilterOption$,
|
||||
this._store.deliveryAvailabilities$,
|
||||
this._store.deliveryDigAvailabilities$,
|
||||
this._store.deliveryB2bAvailabilities$,
|
||||
this._store.fetchingAvailabilities$
|
||||
),
|
||||
map(([[canAdd, items], option, delivery, deliveryDig, deliveryB2b, fetching]) => {
|
||||
if (!option || fetching) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select immer sichtbar bei ausgewählten Items
|
||||
if (items?.find((item) => item.product?.catalogProductNumber === this.item.product?.catalogProductNumber)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Select nur anzeigen, wenn ein anderes ausgewähltes Item die gleiche Verfügbarkeit hat (B2B Versand z.B.)
|
||||
if (items?.length > 0 && option === 'delivery' && canAdd[this.item.product.catalogProductNumber]?.status < 2) {
|
||||
if (items.every((item) => delivery[item.product?.catalogProductNumber]) && delivery[this.item.product?.catalogProductNumber]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
items.every((item) => deliveryDig[item.product?.catalogProductNumber]) &&
|
||||
deliveryDig[this.item.product?.catalogProductNumber]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
items.every((item) => deliveryB2b[item.product?.catalogProductNumber]) &&
|
||||
deliveryB2b[this.item.product?.catalogProductNumber]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return canAdd && canAdd[this.item.product.catalogProductNumber]?.status < 2;
|
||||
})
|
||||
);
|
||||
|
||||
canAdd$ = this._store.canAdd$.pipe(
|
||||
filter((canAdd) => !!this.item && !!canAdd),
|
||||
map((canAdd) => canAdd[this.item.product.catalogProductNumber]?.message)
|
||||
);
|
||||
|
||||
quantityRange$ = combineLatest([this._store.selectedFilterOption$, this.takeAwayAvailabilities$]).pipe(
|
||||
map(([option, availability]) => (option === 'take-away' ? (availability as AvailabilityDTO)?.inStock : 999))
|
||||
);
|
||||
|
||||
constructor(private _store: PurchasingOptionsListModalStore, private _availabilityService: DomainAvailabilityService) {}
|
||||
|
||||
selected(value: boolean) {
|
||||
this._store.selectShoppingCartItem([this.item], value);
|
||||
}
|
||||
|
||||
changeQuantity(quantity: number) {
|
||||
if (quantity === 0) {
|
||||
this._store.removeShoppingCartItem(this.item);
|
||||
} else {
|
||||
this._store.updateItemQuantity({ itemId: this.item.id, quantity });
|
||||
this._store.loadAvailabilities({ items: [{ ...this.item, quantity }] });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<div class="options">
|
||||
<page-take-away-option-list></page-take-away-option-list>
|
||||
<page-pick-up-option-list></page-pick-up-option-list>
|
||||
<page-delivery-option-list></page-delivery-option-list>
|
||||
</div>
|
||||
|
||||
<div class="items" *ngIf="shoppingCartItems$ | async; let shoppingCartItems">
|
||||
<div class="item-actions">
|
||||
<ng-container>
|
||||
<button
|
||||
*ngIf="!(allShoppingCartItemsSelected$ | async); else unselectAll"
|
||||
class="cta-select-all"
|
||||
[disabled]="selectAllCtaDisabled$ | async"
|
||||
(click)="selectAll(shoppingCartItems, true)"
|
||||
>
|
||||
Alle auswählen
|
||||
</button>
|
||||
|
||||
<ng-template #unselectAll>
|
||||
<button class="cta-select-all" [disabled]="selectAllCtaDisabled$ | async" (click)="selectAll(shoppingCartItems, false)">
|
||||
Alle abwählen
|
||||
</button>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<br />
|
||||
{{ (selectedShoppingCartItems$ | async)?.length || 0 }} von {{ shoppingCartItems?.length || 0 }} Artikeln
|
||||
</div>
|
||||
|
||||
<div class="item-list scroll-bar" *ngIf="shoppingCartItems?.length > 0; else emptyMessage">
|
||||
<hr />
|
||||
<ng-container *ngFor="let item of shoppingCartItems">
|
||||
<page-purchasing-options-list-item [item]="item"></page-purchasing-options-list-item>
|
||||
<hr />
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-template #emptyMessage>
|
||||
<div class="empty-message">Keine Artikel für die ausgewählte Kaufoption verfügbar</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="cta-apply" [disabled]="applyCtaDisabled$ | async" (click)="apply()">
|
||||
<ui-spinner [show]="addItemsLoader$ | async">
|
||||
Übernehmen
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,49 @@
|
||||
:host {
|
||||
@apply block box-border;
|
||||
}
|
||||
|
||||
.options {
|
||||
@apply flex flex-row box-border text-center justify-center mt-4;
|
||||
}
|
||||
|
||||
.items {
|
||||
min-height: 440px;
|
||||
|
||||
.item-actions {
|
||||
@apply text-right;
|
||||
|
||||
.cta-select-all {
|
||||
@apply text-brand bg-transparent text-base font-bold outline-none border-none px-4 py-4 -mr-4;
|
||||
|
||||
&:disabled {
|
||||
@apply text-inactive-branch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-list {
|
||||
@apply overflow-y-scroll overflow-x-hidden -ml-4;
|
||||
max-height: calc(100vh - 580px);
|
||||
width: calc(100% + 2rem);
|
||||
|
||||
page-purchasing-options-list-item {
|
||||
@apply px-4;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
@apply text-inactive-branch my-8 text-center font-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply flex justify-center mt-8;
|
||||
|
||||
.cta-apply {
|
||||
@apply text-white border-2 border-solid border-brand bg-brand font-bold text-lg px-4 py-2 rounded-full;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch border-inactive-branch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ShoppingCartItemDTO, UpdateShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { UiErrorModalComponent, UiModalRef, UiModalService } from '@ui/modal';
|
||||
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
|
||||
import { debounceTime, filter, first, map, shareReplay, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { PurchasingOptionsListModalData } from './purchasing-options-list-modal.data';
|
||||
import { PurchasingOptionsListModalStore } from './purchasing-options-list-modal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-purchasing-options-list-modal',
|
||||
templateUrl: 'purchasing-options-list-modal.component.html',
|
||||
styleUrls: ['purchasing-options-list-modal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [PurchasingOptionsListModalStore],
|
||||
})
|
||||
export class PurchasingOptionsListModalComponent implements OnInit {
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
addItemsLoader$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
shoppingCartItems$ = combineLatest([
|
||||
this._store.fetchingAvailabilities$,
|
||||
this._store.selectedFilterOption$,
|
||||
this._store.shoppingCartItems$,
|
||||
]).pipe(
|
||||
withLatestFrom(
|
||||
this._store.takeAwayAvailabilities$,
|
||||
this._store.pickUpAvailabilities$,
|
||||
this._store.deliveryAvailabilities$,
|
||||
this._store.deliveryDigAvailabilities$,
|
||||
this._store.deliveryB2bAvailabilities$
|
||||
),
|
||||
map(
|
||||
([
|
||||
[_, selectedFilterOption, shoppingCartItems],
|
||||
takeAwayAvailability,
|
||||
pickUpAvailability,
|
||||
deliveryAvailability,
|
||||
deliveryDigAvailability,
|
||||
deliveryB2bAvailability,
|
||||
]) => {
|
||||
if (!!takeAwayAvailability && !!pickUpAvailability && !!deliveryAvailability) {
|
||||
switch (selectedFilterOption) {
|
||||
case 'take-away':
|
||||
return shoppingCartItems.filter((item) => !!takeAwayAvailability[item.product?.catalogProductNumber]);
|
||||
case 'pick-up':
|
||||
return shoppingCartItems.filter((item) => !!pickUpAvailability[item.product?.catalogProductNumber]);
|
||||
case 'delivery':
|
||||
return shoppingCartItems.filter(
|
||||
(item) =>
|
||||
!!deliveryAvailability[item.product?.catalogProductNumber] ||
|
||||
!!deliveryDigAvailability[item.product?.catalogProductNumber] ||
|
||||
!!deliveryB2bAvailability[item.product?.catalogProductNumber]
|
||||
);
|
||||
}
|
||||
}
|
||||
return shoppingCartItems;
|
||||
}
|
||||
),
|
||||
map((shoppingCartItems) => shoppingCartItems?.sort((a, b) => a.product?.name.localeCompare(b.product?.name))),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
selectedShoppingCartItems$ = this._store.selectedShoppingCartItems$;
|
||||
|
||||
allShoppingCartItemsSelected$ = combineLatest([this.shoppingCartItems$, this.selectedShoppingCartItems$]).pipe(
|
||||
map(
|
||||
([shoppingCartItems, selectedShoppingCartItems]) =>
|
||||
shoppingCartItems.every((item) => selectedShoppingCartItems.find((i) => item.id === i.id)) && shoppingCartItems?.length > 0
|
||||
)
|
||||
);
|
||||
|
||||
canAddItems$ = this._store.canAdd$.pipe(
|
||||
map((canAdd) => {
|
||||
for (const key in canAdd) {
|
||||
if (Object.prototype.hasOwnProperty.call(canAdd, key)) {
|
||||
if (!!canAdd[key]?.message) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
selectAllCtaDisabled$ = combineLatest([this._store.selectedFilterOption$, this.canAddItems$]).pipe(
|
||||
withLatestFrom(this.shoppingCartItems$),
|
||||
map(([[selectedFilterOption, canAddItems], items]) => !selectedFilterOption || items?.length === 0 || !canAddItems)
|
||||
);
|
||||
|
||||
applyCtaDisabled$ = combineLatest([this.addItemsLoader$, this._store.selectedFilterOption$, this._store.selectedShoppingCartItems$]).pipe(
|
||||
withLatestFrom(this.shoppingCartItems$),
|
||||
map(
|
||||
([[addItemsLoader, selectedFilterOption, selectedShoppingCartItems], shoppingCartItems]) =>
|
||||
addItemsLoader || !selectedFilterOption || shoppingCartItems?.length === 0 || selectedShoppingCartItems?.length === 0
|
||||
)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private _modalRef: UiModalRef<any, PurchasingOptionsListModalData>,
|
||||
private _modal: UiModalService,
|
||||
private _store: PurchasingOptionsListModalStore,
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _checkout: DomainCheckoutService
|
||||
) {
|
||||
this._store.shoppingCartItems = _modalRef.data.shoppingCartItems;
|
||||
this._store.customerFeatures = _modalRef.data.customerFeatures;
|
||||
this._store.processId = _modalRef.data.processId;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._store.loadBranches();
|
||||
|
||||
// Beim Wechsel der ausgewählten Filteroption oder der Branches die Auswahl leeren
|
||||
combineLatest([this._store.selectedFilterOption$, this._store.selectedTakeAwayBranch$, this._store.selectedPickUpBranch$])
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe(() => this._store.clearSelectedShoppingCartItems());
|
||||
|
||||
this._store.selectedFilterOption$
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.shoppingCartItems$))
|
||||
.subscribe(([option, items]) => this.checkCanAdd(option, items));
|
||||
|
||||
this._store.fetchingAvailabilities$
|
||||
.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
debounceTime(250),
|
||||
filter((fetching) => !fetching),
|
||||
withLatestFrom(this.shoppingCartItems$, this._store.selectedFilterOption$)
|
||||
)
|
||||
.subscribe(([_, items, option]) => this.checkCanAdd(option, items));
|
||||
|
||||
this.canAddItems$
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.shoppingCartItems$, this._store.selectedFilterOption$))
|
||||
.subscribe(([showSelectAll, items, option]) => {
|
||||
if (items?.length > 0 && this._store.lastSelectedFilterOption$.value !== option) {
|
||||
this.selectAll(items, showSelectAll && !!option);
|
||||
}
|
||||
|
||||
// Nach dem Übernehmen von Items wird eine neue CanAdd Abfrage ausgeführt, in diesem Fall soll aber nicht alles ausgewählt werden
|
||||
this._store.lastSelectedFilterOption$.next(option);
|
||||
});
|
||||
}
|
||||
|
||||
checkCanAdd(selectedFilterOption: string, items: ShoppingCartItemDTO[]) {
|
||||
if (!!selectedFilterOption && items?.length > 0) {
|
||||
this._store.checkCanAddItems(items);
|
||||
} else {
|
||||
this._store.patchState({ canAdd: {} });
|
||||
}
|
||||
}
|
||||
|
||||
async selectAll(items: ShoppingCartItemDTO[], value: boolean) {
|
||||
this._store.selectShoppingCartItem(items, value);
|
||||
}
|
||||
|
||||
async apply() {
|
||||
this.addItemsLoader$.next(true);
|
||||
|
||||
try {
|
||||
const shoppingCartItems = await this._store.shoppingCartItems$.pipe(first()).toPromise();
|
||||
const items = await this._store.selectedShoppingCartItems$.pipe(first()).toPromise();
|
||||
const takeAwayAvailabilities = await this._store.takeAwayAvailabilities$.pipe(first()).toPromise();
|
||||
const pickupAvailabilities = await this._store.pickUpAvailabilities$.pipe(first()).toPromise();
|
||||
const deliveryAvailabilities = await this._store.deliveryAvailabilities$.pipe(first()).toPromise();
|
||||
const deliveryB2bAvailabilities = await this._store.deliveryB2bAvailabilities$.pipe(first()).toPromise();
|
||||
const deliveryDigAvailabilities = await this._store.deliveryDigAvailabilities$.pipe(first()).toPromise();
|
||||
const selectedTakeAwayBranch = await this._store.selectedTakeAwayBranch$.pipe(first()).toPromise();
|
||||
const selectedPickUpBranch = await this._store.selectedPickUpBranch$.pipe(first()).toPromise();
|
||||
|
||||
for (const item of items) {
|
||||
let availability;
|
||||
switch (this._store.selectedFilterOption) {
|
||||
case 'take-away':
|
||||
availability = takeAwayAvailabilities[item.product.catalogProductNumber];
|
||||
break;
|
||||
case 'pick-up':
|
||||
availability = pickupAvailabilities[item.product.catalogProductNumber];
|
||||
break;
|
||||
case 'delivery':
|
||||
if (
|
||||
deliveryDigAvailabilities[item.product.catalogProductNumber] &&
|
||||
deliveryB2bAvailabilities[item.product.catalogProductNumber] &&
|
||||
deliveryAvailabilities[item.product.catalogProductNumber]
|
||||
) {
|
||||
availability = deliveryAvailabilities[item.product.catalogProductNumber];
|
||||
} else if (deliveryDigAvailabilities[item.product.catalogProductNumber]) {
|
||||
availability = deliveryDigAvailabilities[item.product.catalogProductNumber];
|
||||
} else if (deliveryB2bAvailabilities[item.product.catalogProductNumber]) {
|
||||
availability = deliveryB2bAvailabilities[item.product.catalogProductNumber];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const price = this._availability.getPriceForAvailability(this._store.selectedFilterOption, item.availability, availability);
|
||||
|
||||
// Negative Preise und nicht vorhandene Availability ignorieren
|
||||
if (price?.value?.value < 0 || !availability) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updateItem: UpdateShoppingCartItemDTO = {
|
||||
quantity: item.quantity,
|
||||
availability: {
|
||||
...availability,
|
||||
price,
|
||||
},
|
||||
promotion: { points: item.promotion.points },
|
||||
};
|
||||
|
||||
switch (this._store.selectedFilterOption) {
|
||||
case 'take-away':
|
||||
updateItem.destination = {
|
||||
data: { target: 1, targetBranch: { id: selectedTakeAwayBranch.id } },
|
||||
};
|
||||
break;
|
||||
case 'pick-up':
|
||||
updateItem.destination = {
|
||||
data: { target: 1, targetBranch: { id: selectedPickUpBranch.id } },
|
||||
};
|
||||
break;
|
||||
case 'delivery':
|
||||
case 'dig-delivery':
|
||||
case 'b2b-delivery':
|
||||
updateItem.destination = {
|
||||
data: { target: 2, logistician: availability?.logistician },
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
await this._checkout
|
||||
.updateItemInShoppingCart({
|
||||
processId: this._modalRef.data.processId,
|
||||
shoppingCartItemId: item.id,
|
||||
update: {
|
||||
...updateItem,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
const remainingItems = shoppingCartItems.filter((i) => !items.find((j) => i.id === j.id));
|
||||
this._store.shoppingCartItems = [...remainingItems];
|
||||
|
||||
this._store.clearSelectedShoppingCartItems();
|
||||
|
||||
if (remainingItems?.length === 0) {
|
||||
this._modalRef.close();
|
||||
}
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim Hinzufügen zum Warenkorb' });
|
||||
} finally {
|
||||
this.addItemsLoader$.next(false);
|
||||
}
|
||||
|
||||
const shoppingCartItems = await this.shoppingCartItems$.pipe(first()).toPromise();
|
||||
if (shoppingCartItems?.length > 0) {
|
||||
this._store.checkCanAddItems(shoppingCartItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
|
||||
export interface PurchasingOptionsListModalData {
|
||||
processId: number;
|
||||
shoppingCartItems?: ShoppingCartItemDTO[];
|
||||
customerFeatures: { [key: string]: string };
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { PurchasingOptionsListModalComponent } from './purchasing-options-list-modal.component';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { ProductImageModule } from '@cdn/product-image';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
import { PickUpOptionListComponent } from './pick-up-option/pick-up-option-list.component';
|
||||
import { TakeAwayOptionListComponent } from './take-away-option/take-away-option-list.component';
|
||||
import { DeliveryOptionListComponent } from './delivery-option/delivery-option-list.component';
|
||||
import { PurchasingOptionsListItemComponent } from './purchasing-options-list-item/purchasing-options-list-item.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { UiBranchDropdownModule } from '@ui/branch-dropdown';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
UiCommonModule,
|
||||
UiIconModule,
|
||||
UiSelectBulletModule,
|
||||
UiQuantityDropdownModule,
|
||||
ProductImageModule,
|
||||
UiBranchDropdownModule,
|
||||
UiTooltipModule,
|
||||
UiSpinnerModule,
|
||||
],
|
||||
exports: [PurchasingOptionsListModalComponent],
|
||||
declarations: [
|
||||
PurchasingOptionsListModalComponent,
|
||||
PurchasingOptionsListItemComponent,
|
||||
PickUpOptionListComponent,
|
||||
TakeAwayOptionListComponent,
|
||||
DeliveryOptionListComponent,
|
||||
],
|
||||
})
|
||||
export class PurchasingOptionsListModalModule {}
|
||||
@@ -0,0 +1,574 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { AvailabilityDTO, BranchDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { map, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
|
||||
interface PurchasingOptionsListModalState {
|
||||
processId: number;
|
||||
shoppingCartItems: ShoppingCartItemDTO[];
|
||||
selectedFilterOption: string;
|
||||
takeAwayAvailabilities: { [key: string]: AvailabilityDTO | true };
|
||||
pickUpAvailabilities: { [key: string]: AvailabilityDTO | true };
|
||||
deliveryAvailabilities: { [key: string]: AvailabilityDTO | true };
|
||||
deliveryB2bAvailabilities: { [key: string]: AvailabilityDTO | true };
|
||||
deliveryDigAvailabilities: { [key: string]: AvailabilityDTO | true };
|
||||
customerFeatures: { [key: string]: string };
|
||||
canAdd: { [key: string]: { message: string; status: number } };
|
||||
selectedShoppingCartItems: ShoppingCartItemDTO[];
|
||||
branches: BranchDTO[];
|
||||
currentBranch: BranchDTO;
|
||||
selectedTakeAwayBranch: BranchDTO;
|
||||
selectedPickUpBranch: BranchDTO;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PurchasingOptionsListModalStore extends ComponentStore<PurchasingOptionsListModalState> {
|
||||
lastSelectedFilterOption$ = new BehaviorSubject<string>(undefined);
|
||||
|
||||
branches$ = this.select((s) => s.branches);
|
||||
currentBranch$ = this.select((s) => s.currentBranch);
|
||||
takeAwayAvailabilities$ = this.select((s) => s.takeAwayAvailabilities);
|
||||
pickUpAvailabilities$ = this.select((s) => s.pickUpAvailabilities);
|
||||
deliveryAvailabilities$ = this.select((s) => s.deliveryAvailabilities);
|
||||
deliveryB2bAvailabilities$ = this.select((s) => s.deliveryB2bAvailabilities);
|
||||
canAdd$ = this.select((s) => s.canAdd);
|
||||
deliveryDigAvailabilities$ = this.select((s) => s.deliveryDigAvailabilities);
|
||||
|
||||
shoppingCartItems$ = this.select((s) => s.shoppingCartItems);
|
||||
|
||||
set shoppingCartItems(shoppingCartItems: ShoppingCartItemDTO[]) {
|
||||
shoppingCartItems = shoppingCartItems.sort((a, b) => a.product?.name.localeCompare(b.product.name));
|
||||
this.patchState({ shoppingCartItems });
|
||||
}
|
||||
|
||||
processId$ = this.select((s) => s.processId);
|
||||
|
||||
set processId(processId: number) {
|
||||
this.patchState({ processId });
|
||||
}
|
||||
|
||||
customerFeatures$ = this.select((s) => s.customerFeatures);
|
||||
|
||||
set customerFeatures(customerFeatures: { [key: string]: string }) {
|
||||
this.patchState({ customerFeatures });
|
||||
}
|
||||
|
||||
selectedFilterOption$ = this.select((s) => s.selectedFilterOption);
|
||||
|
||||
set selectedFilterOption(selectedFilterOption: string) {
|
||||
this.patchState({ selectedFilterOption });
|
||||
}
|
||||
|
||||
get selectedFilterOption() {
|
||||
return this.get((s) => s.selectedFilterOption);
|
||||
}
|
||||
|
||||
selectedShoppingCartItems$ = this.select((s) => s.selectedShoppingCartItems);
|
||||
|
||||
get selectedShoppingCartItems() {
|
||||
return this.get((s) => s.selectedShoppingCartItems);
|
||||
}
|
||||
|
||||
selectedTakeAwayBranch$ = this.select((s) => s.selectedTakeAwayBranch);
|
||||
|
||||
set selectedTakeAwayBranch(selectedTakeAwayBranch: BranchDTO) {
|
||||
this.patchState({ selectedTakeAwayBranch });
|
||||
}
|
||||
|
||||
selectedPickUpBranch$ = this.select((s) => s.selectedPickUpBranch);
|
||||
|
||||
set selectedPickUpBranch(selectedPickUpBranch: BranchDTO) {
|
||||
this.patchState({ selectedPickUpBranch });
|
||||
}
|
||||
|
||||
fetchingAvailabilities$ = combineLatest([this.takeAwayAvailabilities$, this.pickUpAvailabilities$, this.deliveryAvailabilities$]).pipe(
|
||||
map(([takeAway, pickUp, delivery]) => {
|
||||
const fetchingCheck = (obj) => {
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const element = obj[key];
|
||||
if (typeof element === 'boolean') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return !takeAway || fetchingCheck(takeAway) || !pickUp || fetchingCheck(pickUp) || !delivery || fetchingCheck(delivery);
|
||||
})
|
||||
);
|
||||
|
||||
constructor(private _availabilityService: DomainAvailabilityService, private _checkoutService: DomainCheckoutService) {
|
||||
super({
|
||||
processId: undefined,
|
||||
shoppingCartItems: [],
|
||||
selectedFilterOption: undefined,
|
||||
pickUpAvailabilities: undefined,
|
||||
deliveryAvailabilities: undefined,
|
||||
takeAwayAvailabilities: undefined,
|
||||
deliveryB2bAvailabilities: undefined,
|
||||
deliveryDigAvailabilities: undefined,
|
||||
selectedShoppingCartItems: [],
|
||||
branches: [],
|
||||
currentBranch: undefined,
|
||||
selectedTakeAwayBranch: undefined,
|
||||
selectedPickUpBranch: undefined,
|
||||
customerFeatures: undefined,
|
||||
canAdd: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
loadAvailabilities(options: { items?: ShoppingCartItemDTO[] }) {
|
||||
const shoppingCartItems = options.items ?? this.get((s) => s.shoppingCartItems);
|
||||
|
||||
for (const item of shoppingCartItems) {
|
||||
this.loadTakeAwayAvailability({ item });
|
||||
this.loadPickUpAvailability({ item });
|
||||
this.loadDeliveryAvailability({ item });
|
||||
this.loadDeliveryB2bAvailability({ item });
|
||||
this.loadDeliveryDigAvailability({ item });
|
||||
}
|
||||
}
|
||||
|
||||
readonly setAvailabilityFetching = this.updater((state, { name, id, fetching }: { name: string; id: string; fetching?: boolean }) => {
|
||||
const availability = { ...state[name] };
|
||||
|
||||
if (fetching) {
|
||||
availability[id] = fetching;
|
||||
} else {
|
||||
delete availability[id];
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
[name]: {
|
||||
...availability,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
readonly setAvailability = this.updater((state, { name, availability }: { name: string; availability: any }) => {
|
||||
const av = { ...state[name] };
|
||||
|
||||
if (this._availabilityService.isAvailable({ availability })) {
|
||||
av[availability.itemId] = availability;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
[name]: av,
|
||||
};
|
||||
});
|
||||
|
||||
loadPickUpAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
||||
options$.pipe(
|
||||
withLatestFrom(this.selectedPickUpBranch$),
|
||||
mergeMap(([options, branch]) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'pickUpAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: true,
|
||||
});
|
||||
|
||||
return this._availabilityService
|
||||
.getPickUpAvailability({
|
||||
item: {
|
||||
itemId: +options.item.product.catalogProductNumber,
|
||||
ean: options.item.product.ean,
|
||||
price: options.item.availability.price,
|
||||
},
|
||||
branch,
|
||||
quantity: options.item.quantity,
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(availability) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'pickUpAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
});
|
||||
this.setAvailability({
|
||||
name: 'pickUpAvailabilities',
|
||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'pickUpAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: false,
|
||||
});
|
||||
this.setAvailability({ name: 'pickUpAvailabilities', availability: {} });
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
loadDeliveryAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
||||
options$.pipe(
|
||||
mergeMap((options) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: true,
|
||||
});
|
||||
|
||||
return this._availabilityService
|
||||
.getDeliveryAvailability({
|
||||
item: {
|
||||
itemId: +options.item.product.catalogProductNumber,
|
||||
ean: options.item.product.ean,
|
||||
price: options.item.availability.price,
|
||||
},
|
||||
quantity: options.item.quantity,
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(availability) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
});
|
||||
this.setAvailability({
|
||||
name: 'deliveryAvailabilities',
|
||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: false,
|
||||
});
|
||||
this.setAvailability({ name: 'deliveryAvailabilities', availability: {} });
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
loadDeliveryB2bAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
||||
options$.pipe(
|
||||
mergeMap((options) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryB2bAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: true,
|
||||
});
|
||||
|
||||
return this._availabilityService
|
||||
.getB2bDeliveryAvailability({
|
||||
item: {
|
||||
itemId: +options.item.product.catalogProductNumber,
|
||||
ean: options.item.product.ean,
|
||||
price: options.item.availability.price,
|
||||
},
|
||||
quantity: options.item.quantity,
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(availability) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryB2bAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
});
|
||||
|
||||
this.setAvailability({
|
||||
name: 'deliveryB2bAvailabilities',
|
||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryB2bAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: false,
|
||||
});
|
||||
this.setAvailability({ name: 'deliveryB2bAvailabilities', availability: {} });
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
loadDeliveryDigAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
||||
options$.pipe(
|
||||
mergeMap((options) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryDigAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: true,
|
||||
});
|
||||
|
||||
return this._availabilityService
|
||||
.getDigDeliveryAvailability({
|
||||
item: {
|
||||
itemId: +options.item.product.catalogProductNumber,
|
||||
ean: options.item.product.ean,
|
||||
price: options.item.availability.price,
|
||||
},
|
||||
quantity: options.item.quantity,
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(availability) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryDigAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
});
|
||||
this.setAvailability({
|
||||
name: 'deliveryDigAvailabilities',
|
||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'deliveryDigAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: false,
|
||||
});
|
||||
this.setAvailability({ name: 'deliveryDigAvailabilities', availability: {} });
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
loadTakeAwayAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
||||
options$.pipe(
|
||||
withLatestFrom(this.selectedTakeAwayBranch$),
|
||||
mergeMap(([options, branch]) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'takeAwayAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: true,
|
||||
});
|
||||
|
||||
return this._availabilityService
|
||||
.getTakeAwayAvailabilityByBranch({
|
||||
itemId: +options.item.product.catalogProductNumber,
|
||||
price: options.item.availability.price,
|
||||
quantity: options.item.quantity,
|
||||
branch,
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(availability) => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'takeAwayAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
});
|
||||
this.setAvailability({
|
||||
name: 'takeAwayAvailabilities',
|
||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setAvailabilityFetching({
|
||||
name: 'takeAwayAvailabilities',
|
||||
id: options.item.product.catalogProductNumber,
|
||||
fetching: false,
|
||||
});
|
||||
this.setAvailability({ name: 'takeAwayAvailabilities', availability: {} });
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
loadBranches = this.effect(($) =>
|
||||
$.pipe(
|
||||
switchMap(() =>
|
||||
this._availabilityService.getBranches().pipe(
|
||||
map((branches) =>
|
||||
branches.filter(
|
||||
(branch) => branch.status === 1 && branch.branchType === 1 && branch.isOnline === true && branch.isShippingEnabled === true
|
||||
)
|
||||
),
|
||||
withLatestFrom(this._availabilityService.getCurrentBranch()),
|
||||
tapResponse(
|
||||
([branches, currentBranch]) => {
|
||||
this.patchState({
|
||||
branches,
|
||||
selectedTakeAwayBranch: currentBranch,
|
||||
selectedPickUpBranch: currentBranch,
|
||||
currentBranch,
|
||||
});
|
||||
this.loadAvailabilities({});
|
||||
},
|
||||
() =>
|
||||
this.patchState({
|
||||
branches: [],
|
||||
selectedTakeAwayBranch: undefined,
|
||||
selectedPickUpBranch: undefined,
|
||||
currentBranch: undefined,
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
checkCanAddItems = this.effect((items$: Observable<ShoppingCartItemDTO[]>) =>
|
||||
items$.pipe(
|
||||
withLatestFrom(
|
||||
this.processId$,
|
||||
this.selectedFilterOption$,
|
||||
this.takeAwayAvailabilities$,
|
||||
this.pickUpAvailabilities$,
|
||||
this.deliveryAvailabilities$,
|
||||
this.deliveryB2bAvailabilities$,
|
||||
this.deliveryDigAvailabilities$
|
||||
),
|
||||
mergeMap(([items, processId, selectedOption, takeAway, pickUp, delivery, deliveryB2b, deliveryDig]) => {
|
||||
let orderType: string;
|
||||
const payload = items.map((item) => {
|
||||
switch (selectedOption) {
|
||||
case 'take-away':
|
||||
orderType = 'Rücklage';
|
||||
return {
|
||||
availabilities: [this.getOlaAvailability(takeAway[item.product.catalogProductNumber], item)],
|
||||
id: item.product.catalogProductNumber,
|
||||
};
|
||||
case 'pick-up':
|
||||
orderType = 'Abholung';
|
||||
return {
|
||||
availabilities: [this.getOlaAvailability(pickUp[item.product.catalogProductNumber], item)],
|
||||
id: item.product.catalogProductNumber,
|
||||
};
|
||||
case 'delivery':
|
||||
orderType = 'Versand';
|
||||
if (
|
||||
deliveryDig[item.product.catalogProductNumber] &&
|
||||
deliveryB2b[item.product.catalogProductNumber] &&
|
||||
delivery[item.product.catalogProductNumber]
|
||||
) {
|
||||
return {
|
||||
availabilities: [this.getOlaAvailability(delivery[item.product.catalogProductNumber], item)],
|
||||
id: item.product.catalogProductNumber,
|
||||
};
|
||||
} else if (deliveryDig[item.product.catalogProductNumber]) {
|
||||
return {
|
||||
availabilities: [this.getOlaAvailability(deliveryDig[item.product.catalogProductNumber], item)],
|
||||
id: item.product.catalogProductNumber,
|
||||
};
|
||||
} else if (deliveryB2b[item.product.catalogProductNumber]) {
|
||||
return {
|
||||
availabilities: [this.getOlaAvailability(deliveryB2b[item.product.catalogProductNumber], item)],
|
||||
id: item.product.catalogProductNumber,
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return this._checkoutService.canAddItems({ processId, payload, orderType }).pipe(
|
||||
tapResponse(
|
||||
(result: any) => {
|
||||
const canAdd = {};
|
||||
|
||||
result?.forEach((r) => {
|
||||
canAdd[r.id] = { message: r.message, status: r.status };
|
||||
});
|
||||
|
||||
this.patchState({ canAdd });
|
||||
},
|
||||
(error: Error) => {
|
||||
const canAdd = {};
|
||||
|
||||
items?.forEach((i) => {
|
||||
canAdd[i.product?.catalogProductNumber] = { message: error?.message };
|
||||
});
|
||||
|
||||
this.patchState({ canAdd });
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
getOlaAvailability(availability: AvailabilityDTO, item: ShoppingCartItemDTO) {
|
||||
return {
|
||||
qty: item.quantity,
|
||||
ean: item.product.ean,
|
||||
itemId: item.product.catalogProductNumber,
|
||||
format: item.product.format,
|
||||
at: availability?.estimatedShippingDate,
|
||||
isPrebooked: availability?.isPrebooked,
|
||||
status: availability?.availabilityType,
|
||||
logisticianId: availability?.logistician?.id,
|
||||
price: availability?.price,
|
||||
ssc: availability?.ssc,
|
||||
sscText: availability?.sscText,
|
||||
supplierId: availability?.supplier?.id,
|
||||
};
|
||||
}
|
||||
|
||||
readonly updateItemQuantity = this.updater((state, value: { itemId: number; quantity: number }) => {
|
||||
const itemToUpdate = state.shoppingCartItems.find((item) => item.id === value.itemId);
|
||||
const otherItems = state.shoppingCartItems.filter((item) => item.id !== value.itemId);
|
||||
const updatedItem = { ...itemToUpdate, quantity: value.quantity };
|
||||
const shoppingCartItems = [...otherItems, updatedItem].sort((a, b) => a.product?.name.localeCompare(b.product.name));
|
||||
|
||||
// Ausgewählte Items auch aktualisieren
|
||||
let selectedShoppingCartItems = state.selectedShoppingCartItems;
|
||||
if (state.selectedShoppingCartItems.find((item) => item.id === value.itemId)) {
|
||||
const selectedItems = state.selectedShoppingCartItems.filter((item) => item.id !== value.itemId);
|
||||
selectedShoppingCartItems = [...selectedItems, updatedItem].sort((a, b) => a.product?.name.localeCompare(b.product.name));
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
shoppingCartItems,
|
||||
selectedShoppingCartItems,
|
||||
};
|
||||
});
|
||||
|
||||
async removeShoppingCartItem(item: ShoppingCartItemDTO) {
|
||||
const items = this.get((s) => s.shoppingCartItems);
|
||||
const processId = this.get((s) => s.processId);
|
||||
|
||||
await this._checkoutService
|
||||
.updateItemInShoppingCart({
|
||||
processId,
|
||||
shoppingCartItemId: item.id,
|
||||
update: {
|
||||
quantity: 0,
|
||||
availability: null,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
this.selectShoppingCartItem([item], false);
|
||||
const shoppingCartItems = items.filter((i) => i.id !== item.id);
|
||||
this.patchState({ shoppingCartItems });
|
||||
}
|
||||
|
||||
selectShoppingCartItem(shoppingCartItems: ShoppingCartItemDTO[], selected: boolean) {
|
||||
if (selected) {
|
||||
this.patchState({
|
||||
selectedShoppingCartItems: [
|
||||
...this.selectedShoppingCartItems.filter((item) => !shoppingCartItems.find((i) => item.id === i.id)),
|
||||
...shoppingCartItems,
|
||||
],
|
||||
});
|
||||
} else {
|
||||
this.patchState({
|
||||
selectedShoppingCartItems: this.selectedShoppingCartItems.filter((item) => !shoppingCartItems.find((i) => item.id === i.id)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clearSelectedShoppingCartItems() {
|
||||
this.patchState({ selectedShoppingCartItems: [] });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './take-away-option-list.component';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="option-icon">
|
||||
<ui-icon size="50px" icon="shopping_bag"></ui-icon>
|
||||
</div>
|
||||
<button
|
||||
class="option-chip"
|
||||
[disabled]="optionChipDisabled$ | async"
|
||||
(click)="optionChange('take-away')"
|
||||
[class.selected]="(selectedOption$ | async) === 'take-away'"
|
||||
>
|
||||
Rücklage
|
||||
</button>
|
||||
<p>Möchten Sie die Artikel<br />zurücklegen lassen oder<br />sofort mitnehmen?</p>
|
||||
|
||||
<ui-branch-dropdown
|
||||
[branches]="branches$ | async"
|
||||
[selected]="(selectedBranch$ | async)?.name"
|
||||
(selectBranch)="selectBranch($event)"
|
||||
></ui-branch-dropdown>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { PurchasingOptionsListModalStore } from '../purchasing-options-list-modal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-take-away-option-list',
|
||||
templateUrl: 'take-away-option-list.component.html',
|
||||
styleUrls: ['../list-options.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TakeAwayOptionListComponent {
|
||||
branches$ = this._store.branches$;
|
||||
selectedBranch$ = this._store.selectedTakeAwayBranch$;
|
||||
selectedOption$ = this._store.selectedFilterOption$;
|
||||
optionChipDisabled$ = this._store.fetchingAvailabilities$;
|
||||
|
||||
constructor(private _store: PurchasingOptionsListModalStore) {}
|
||||
|
||||
optionChange(option: string) {
|
||||
if (this._store.selectedFilterOption === option) {
|
||||
this._store.selectedFilterOption = undefined;
|
||||
} else {
|
||||
this._store.selectedFilterOption = option;
|
||||
}
|
||||
}
|
||||
|
||||
async selectBranch(branch: BranchDTO) {
|
||||
this._store.lastSelectedFilterOption$.next(undefined);
|
||||
|
||||
this._store.selectedTakeAwayBranch = branch;
|
||||
|
||||
const shoppingCartItems = await this._store.shoppingCartItems$.pipe(first()).toPromise();
|
||||
shoppingCartItems.forEach((item) => this._store.loadTakeAwayAvailability({ item }));
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
<p>
|
||||
Als B2B Kunde können wir Ihnen den Artikel auch liefern.
|
||||
</p>
|
||||
<span class="price">{{ availability.price?.value?.value | currency: availability.price?.value?.currency:'code' }}</span>
|
||||
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
|
||||
<div class="grow"></div>
|
||||
<span class="delivery">Versandkostenfrei</span>
|
||||
<span class="date"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
|
||||
@@ -9,13 +11,17 @@ import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class B2BDeliveryOptionComponent {
|
||||
readonly item$ = this.purchasingOptionsModalStore.selectItem;
|
||||
readonly item$ = this._purchasingOptionsModalStore.selectItem;
|
||||
|
||||
readonly availability$ = this.purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['b2b-delivery']));
|
||||
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['b2b-delivery']));
|
||||
|
||||
constructor(private purchasingOptionsModalStore: PurchasingOptionsModalStore) {}
|
||||
readonly price$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
map(([availability, item]) => this._availabilityService.getPriceForAvailability('b2b-delivery', item.catalogAvailability, availability))
|
||||
);
|
||||
|
||||
constructor(private _purchasingOptionsModalStore: PurchasingOptionsModalStore, private _availabilityService: DomainAvailabilityService) {}
|
||||
|
||||
select() {
|
||||
this.purchasingOptionsModalStore.setOption('b2b-delivery');
|
||||
this._purchasingOptionsModalStore.setOption('b2b-delivery');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
Möchten Sie den Artikel geliefert bekommen?
|
||||
</p>
|
||||
<div class="price-wrapper">
|
||||
<span class="price">{{ price$ | async | currency: availability.price?.value?.currency:'code' }}</span>
|
||||
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
|
||||
<ng-container *ngIf="showTooltip$ | async">
|
||||
<button [uiOverlayTrigger]="tooltipContent" #tooltip="uiOverlayTrigger" class="info-tooltip-button" type="button">
|
||||
i
|
||||
@@ -20,9 +20,18 @@
|
||||
</div>
|
||||
<div class="grow"></div>
|
||||
<span class="delivery">Versandkostenfrei</span>
|
||||
<span class="date"
|
||||
>Versanddatum <strong>{{ availability?.estimatedShippingDate | date }}</strong></span
|
||||
>
|
||||
<span *ngIf="availability?.estimatedDelivery; else estimatedShippingDate" class="date">
|
||||
Zustellung zwischen <br />
|
||||
<strong
|
||||
>{{ (availability?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
|
||||
{{ (availability?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}</strong
|
||||
>
|
||||
</span>
|
||||
<ng-template #estimatedShippingDate>
|
||||
<span class="date">
|
||||
Versanddatum <strong>{{ estimatedShippingDate | date }}</strong>
|
||||
</span>
|
||||
</ng-template>
|
||||
<div>
|
||||
<button [disabled]="availability.price?.value?.value < 0" type="button" class="select-option" (click)="select()">
|
||||
Auswählen
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
|
||||
@@ -10,26 +11,25 @@ import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DeliveryOptionComponent {
|
||||
readonly item$ = this.purchasingOptionsModalStore.selectItem;
|
||||
readonly item$ = this._purchasingOptionsModalStore.selectItem;
|
||||
|
||||
readonly availability$ = this.purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['delivery']));
|
||||
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['delivery']));
|
||||
|
||||
showTooltip$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
price$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
readonly showTooltip$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
map(([availability, item]) => {
|
||||
const shippingPrice = availability?.price?.value?.value;
|
||||
const catalogPrice = item?.catalogAvailability?.price?.value?.value;
|
||||
if (catalogPrice < shippingPrice) {
|
||||
this.showTooltip$.next(true);
|
||||
}
|
||||
return catalogPrice <= shippingPrice ? catalogPrice : shippingPrice;
|
||||
return catalogPrice < shippingPrice;
|
||||
})
|
||||
);
|
||||
|
||||
constructor(private purchasingOptionsModalStore: PurchasingOptionsModalStore) {}
|
||||
readonly price$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
map(([availability, item]) => this._availabilityService.getPriceForAvailability('delivery', item.catalogAvailability, availability))
|
||||
);
|
||||
|
||||
constructor(private _purchasingOptionsModalStore: PurchasingOptionsModalStore, private _availabilityService: DomainAvailabilityService) {}
|
||||
|
||||
select() {
|
||||
this.purchasingOptionsModalStore.setOption('delivery');
|
||||
this._purchasingOptionsModalStore.setOption('delivery');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<h4>DIG Versand</h4>
|
||||
<p>Möchten Sie den Artikel geliefert bekommen?</p>
|
||||
<div class="price-wrapper">
|
||||
<span class="price">{{ price$ | async | currency: availability.price?.value?.currency:'code' }}</span>
|
||||
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
|
||||
<ng-container *ngIf="showTooltip$ | async">
|
||||
<button [uiOverlayTrigger]="tooltipContent" #tooltip="uiOverlayTrigger" class="info-tooltip-button" type="button">
|
||||
i
|
||||
@@ -18,9 +18,18 @@
|
||||
</div>
|
||||
<div class="grow"></div>
|
||||
<span class="delivery">Versandkostenfrei</span>
|
||||
<span class="date"
|
||||
>Versanddatum <strong>{{ availability?.estimatedShippingDate | date: 'shortDate' }}</strong></span
|
||||
>
|
||||
<span *ngIf="availability?.estimatedDelivery; else estimatedShippingDate" class="date">
|
||||
Zustellung zwischen <br />
|
||||
<strong
|
||||
>{{ (availability?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
|
||||
{{ (availability?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}</strong
|
||||
>
|
||||
</span>
|
||||
<ng-template #estimatedShippingDate>
|
||||
<span class="date">
|
||||
Versanddatum <strong>{{ estimatedShippingDate | date }}</strong>
|
||||
</span>
|
||||
</ng-template>
|
||||
|
||||
<div>
|
||||
<button [disabled]="availability.price?.value?.value < 0" type="button" class="select-option" (click)="select()">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
|
||||
@@ -10,26 +11,25 @@ import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DigDeliveryOptionComponent {
|
||||
readonly item$ = this.purchasingOptionsModalStore.selectItem;
|
||||
readonly item$ = this._purchasingOptionsModalStore.selectItem;
|
||||
|
||||
readonly availability$ = this.purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['dig-delivery']));
|
||||
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['dig-delivery']));
|
||||
|
||||
showTooltip$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
price$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
readonly showTooltip$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
map(([availability, item]) => {
|
||||
const shippingPrice = availability?.price?.value?.value;
|
||||
const catalogPrice = item?.catalogAvailability?.price?.value?.value;
|
||||
if (catalogPrice < shippingPrice) {
|
||||
this.showTooltip$.next(true);
|
||||
}
|
||||
return catalogPrice <= shippingPrice ? catalogPrice : shippingPrice;
|
||||
return catalogPrice < shippingPrice;
|
||||
})
|
||||
);
|
||||
|
||||
constructor(private purchasingOptionsModalStore: PurchasingOptionsModalStore) {}
|
||||
readonly price$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
map(([availability, item]) => this._availabilityService.getPriceForAvailability('dig-delivery', item.catalogAvailability, availability))
|
||||
);
|
||||
|
||||
constructor(private _purchasingOptionsModalStore: PurchasingOptionsModalStore, private _availabilityService: DomainAvailabilityService) {}
|
||||
|
||||
select() {
|
||||
this.purchasingOptionsModalStore.setOption('dig-delivery');
|
||||
this._purchasingOptionsModalStore.setOption('dig-delivery');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ p {
|
||||
}
|
||||
|
||||
.date {
|
||||
@apply text-cta-l;
|
||||
@apply text-cta-l whitespace-nowrap;
|
||||
}
|
||||
|
||||
.grow {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { PurchasingOptionsModalStore } from '../../purchasing-options-modal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-pick-up-dropdown',
|
||||
templateUrl: 'pick-up-dropdown.component.html',
|
||||
styleUrls: ['pick-up-dropdown.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PickUpDropdownComponent implements OnInit {
|
||||
branches$: Observable<BranchDTO[]>;
|
||||
selected$: Observable<string>;
|
||||
|
||||
searchFilter: string;
|
||||
preselectedBranch: BranchDTO;
|
||||
isOpen = false;
|
||||
|
||||
constructor(private purchasingOptionsModalStore: PurchasingOptionsModalStore) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.branches$ = this.purchasingOptionsModalStore.selectFilterResult;
|
||||
this.selected$ = this.branches$.pipe(
|
||||
switchMap((branches) =>
|
||||
this.purchasingOptionsModalStore.selectBranch.pipe(map((branch) => branches.find((b) => b.id === branch.id)?.name))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setBranch(branch: BranchDTO) {
|
||||
this.isOpen = false;
|
||||
this.purchasingOptionsModalStore.setBranch(branch);
|
||||
}
|
||||
|
||||
preselectStyling(branch: BranchDTO) {
|
||||
this.preselectedBranch = branch;
|
||||
}
|
||||
|
||||
filter(event: string) {
|
||||
this.purchasingOptionsModalStore.setFilteredBranches(event);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,12 @@
|
||||
<p>
|
||||
Möchten Sie den Artikel in einer unserer Filialen abholen?
|
||||
</p>
|
||||
<span class="price">{{ availability.price?.value?.value | currency: availability.price?.value?.currency:'code' }}</span>
|
||||
<page-pick-up-dropdown></page-pick-up-dropdown>
|
||||
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
|
||||
<ui-branch-dropdown
|
||||
[branches]="branches$ | async"
|
||||
[selected]="selected$ | async"
|
||||
(selectBranch)="selectBranch($event)"
|
||||
></ui-branch-dropdown>
|
||||
<span class="date"
|
||||
>Abholung ab <strong>{{ (availability$ | async)?.estimatedShippingDate | date: 'shortDate' }}</strong></span
|
||||
>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
|
||||
@@ -9,13 +12,24 @@ import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PickUpOptionComponent {
|
||||
readonly item$ = this.purchasingOptionsModalStore.selectItem;
|
||||
branches$: Observable<BranchDTO[]> = this._purchasingOptionsModalStore.selectAvailableBranches;
|
||||
selected$: Observable<string> = this._purchasingOptionsModalStore.selectBranch.pipe(map((branch) => branch.name));
|
||||
|
||||
readonly availability$ = this.purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['pick-up']));
|
||||
readonly item$ = this._purchasingOptionsModalStore.selectItem;
|
||||
|
||||
constructor(private purchasingOptionsModalStore: PurchasingOptionsModalStore) {}
|
||||
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['pick-up']));
|
||||
|
||||
readonly price$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
map(([availability, item]) => this._availabilityService.getPriceForAvailability('pick-up', item.catalogAvailability, availability))
|
||||
);
|
||||
|
||||
constructor(private _purchasingOptionsModalStore: PurchasingOptionsModalStore, private _availabilityService: DomainAvailabilityService) {}
|
||||
|
||||
select() {
|
||||
this.purchasingOptionsModalStore.setOption('pick-up');
|
||||
this._purchasingOptionsModalStore.setOption('pick-up');
|
||||
}
|
||||
|
||||
selectBranch(branch: BranchDTO) {
|
||||
this._purchasingOptionsModalStore.setBranch(branch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
<page-dig-delivery-option *ngSwitchCase="'dig-delivery'"></page-dig-delivery-option>
|
||||
<page-b2b-delivery-option *ngSwitchCase="'b2b-delivery'"></page-b2b-delivery-option>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="(availableOptions$ | async).length === 0">
|
||||
<p class="hint">Derzeit nicht bestellbar</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -41,9 +44,18 @@
|
||||
{{ price$ | async | currency: item?.catalogAvailability?.price?.value?.currency || 'EUR':'code' }}
|
||||
</div>
|
||||
<div class="date" *ngIf="option$ | async; let option">
|
||||
<ng-container *ngIf="option === 'pick-up'">Abholung ab</ng-container>
|
||||
<ng-container *ngIf="showDeliveryInfo$ | async">Versanddatum</ng-container>
|
||||
{{ (getAvailability(option) | async)?.estimatedShippingDate | date: 'shortDate' }}
|
||||
<ng-container *ngIf="option === 'pick-up'">
|
||||
Abholung ab {{ (getAvailability(option) | async)?.estimatedShippingDate | date: 'shortDate' }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showDeliveryInfo$ | async">
|
||||
<ng-container *ngIf="getAvailability(option) | async; let availability">
|
||||
<ng-container *ngIf="availability?.estimatedDelivery; else estimatedShippingDate">
|
||||
Zustellung zwischen {{ (availability?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
|
||||
{{ (availability?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate> Versanddatum {{ availability?.estimatedShippingDate | date: 'shortDate' }} </ng-template>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quantity">
|
||||
@@ -52,6 +64,7 @@
|
||||
#quantityControl
|
||||
[showSpinner]="purchasingOptionsModalStore.selectFetchingAvailability | async"
|
||||
[ngModel]="quantity$ | async"
|
||||
[range]="quantityRange$ | async"
|
||||
(ngModelChange)="changeQuantity($event)"
|
||||
>
|
||||
</ui-quantity-dropdown>
|
||||
|
||||
@@ -110,3 +110,7 @@ img.thumbnail {
|
||||
.quantity-error {
|
||||
@apply text-dark-goldenrod font-bold text-sm mt-2;
|
||||
}
|
||||
|
||||
.hint {
|
||||
@apply text-dark-goldenrod font-bold text-xl;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import { ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { AddToShoppingCartDTO, AvailabilityDTO, VATType } from '@swagger/checkout';
|
||||
import { UiModalRef } from '@ui/modal';
|
||||
import { debounceTime, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { shareReplay, debounceTime, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { PurchasingOptionsModalData } from './purchasing-options-modal.data';
|
||||
import { PurchasingOptions, PurchasingOptionsModalStore } from './purchasing-options-modal.store';
|
||||
import { PurchasingOptionsModalStore } from './purchasing-options-modal.store';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
|
||||
@@ -21,7 +21,7 @@ import { BreadcrumbService } from '@core/breadcrumb';
|
||||
export class PurchasingOptionsModalComponent {
|
||||
readonly item$ = this.purchasingOptionsModalStore.selectItem;
|
||||
|
||||
readonly availableOptions$ = this.purchasingOptionsModalStore.selectAvailableOptions;
|
||||
readonly availableOptions$ = this.purchasingOptionsModalStore.selectAvailableOptions.pipe(shareReplay());
|
||||
|
||||
readonly option$ = this.purchasingOptionsModalStore.selectOption;
|
||||
|
||||
@@ -169,6 +169,10 @@ export class PurchasingOptionsModalComponent {
|
||||
)
|
||||
);
|
||||
|
||||
quantityRange$ = combineLatest([this.option$, this.availability$]).pipe(
|
||||
map(([option, availability]) => (option === 'take-away' ? availability.inStock : 999))
|
||||
);
|
||||
|
||||
activeSpinner: string;
|
||||
|
||||
constructor(
|
||||
@@ -293,17 +297,11 @@ export class PurchasingOptionsModalComponent {
|
||||
break;
|
||||
}
|
||||
|
||||
const shoppingCart = await this.checkoutService.getShoppingCart({ processId }).pipe(first()).toPromise();
|
||||
|
||||
const existingItem = shoppingCart?.items?.find(
|
||||
({ data }) => data.product.ean === item.product.ean && data.features['orderType'] === this.getNameForOption(option)
|
||||
);
|
||||
|
||||
if (shoppingCartItem || existingItem) {
|
||||
if (shoppingCartItem) {
|
||||
await this.checkoutService
|
||||
.updateItemInShoppingCart({
|
||||
processId,
|
||||
shoppingCartItemId: shoppingCartItem?.id || existingItem?.id,
|
||||
shoppingCartItemId: shoppingCartItem?.id,
|
||||
update: {
|
||||
availability: newItem.availability,
|
||||
quantity: newItem.quantity,
|
||||
@@ -367,21 +365,4 @@ export class PurchasingOptionsModalComponent {
|
||||
}
|
||||
this.activeSpinner = undefined;
|
||||
}
|
||||
|
||||
getNameForOption(option: PurchasingOptions) {
|
||||
switch (option) {
|
||||
case 'take-away':
|
||||
return 'Rücklage';
|
||||
case 'pick-up':
|
||||
return 'Abholung';
|
||||
case 'delivery':
|
||||
return 'Versand';
|
||||
case 'dig-delivery':
|
||||
return 'DIG-Versand';
|
||||
case 'b2b-delivery':
|
||||
return 'B2B-Versand';
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AvailabilityDTO, BranchDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { AvailabilityDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { PurchasingOptions } from './purchasing-options-modal.store';
|
||||
|
||||
export interface PurchasingOptionsModalData {
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
} from './options';
|
||||
|
||||
import { PurchasingOptionsModalComponent } from './purchasing-options-modal.component';
|
||||
import { PickUpDropdownComponent } from './pick-up-option/pick-up-dropdown/pick-up-dropdown.component';
|
||||
import { PageCheckoutPipeModule } from '../../pipes/page-checkout-pipe.module';
|
||||
import { ProductImageModule } from 'apps/cdn/product-image/src/public-api';
|
||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
@@ -24,6 +23,7 @@ import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
import { PurchasingOptionsModalPriceInputModule } from './price-input/purchasing-options-modal-price-input.module';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiBranchDropdownModule } from '@ui/branch-dropdown';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -40,6 +40,7 @@ import { UiCommonModule } from '@ui/common';
|
||||
RouterModule,
|
||||
PurchasingOptionsModalPriceInputModule,
|
||||
UiTooltipModule,
|
||||
UiBranchDropdownModule,
|
||||
],
|
||||
exports: [PurchasingOptionsModalComponent],
|
||||
declarations: [
|
||||
@@ -47,7 +48,6 @@ import { UiCommonModule } from '@ui/common';
|
||||
B2BDeliveryOptionComponent,
|
||||
TakeAwayOptionComponent,
|
||||
PickUpOptionComponent,
|
||||
PickUpDropdownComponent,
|
||||
DeliveryOptionComponent,
|
||||
DigDeliveryOptionComponent,
|
||||
],
|
||||
|
||||
@@ -9,7 +9,6 @@ import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, ShoppingCartItemDTO, VA
|
||||
import { isBoolean, isNullOrUndefined, isString } from '@utils/common';
|
||||
import { NEVER, Observable } from 'rxjs';
|
||||
import { delay, filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { geoDistance } from '@utils/common';
|
||||
|
||||
export type PurchasingOptions = 'take-away' | 'pick-up' | 'delivery' | 'dig-delivery' | 'b2b-delivery' | 'download';
|
||||
|
||||
@@ -29,8 +28,6 @@ interface PurchasingOptionsModalState {
|
||||
canAdd: boolean;
|
||||
canAddError?: string;
|
||||
canUpgrade: boolean;
|
||||
// TODO: FilterBranch in der UI Component sortieren und filtern
|
||||
filterResult?: BranchDTO[];
|
||||
availabilities: { [key: string]: AvailabilityDTO };
|
||||
customPrice?: number;
|
||||
customVat?: VATType;
|
||||
@@ -97,8 +94,6 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
|
||||
|
||||
readonly selectCanAddError = this.select((s) => s.canAddError);
|
||||
|
||||
readonly selectFilterResult = this.select((s) => s.filterResult);
|
||||
|
||||
readonly selectFetchingAvailability = this.select((s) => s.fetchingAvailability);
|
||||
|
||||
readonly selectMaxQuantityError = this.select((s) => s.maxQuantityError);
|
||||
@@ -218,15 +213,12 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
|
||||
|
||||
readonly setBranch = this.updater((state, branch: BranchDTO) => {
|
||||
this.loadAvailability();
|
||||
const filterResult = state?.availableBranches;
|
||||
filterResult.sort((a: BranchDTO, b: BranchDTO) => this.branchSorterFn(a, b, branch));
|
||||
return {
|
||||
...state,
|
||||
branch,
|
||||
availability: undefined,
|
||||
canAdd: false,
|
||||
canAddError: undefined,
|
||||
filterResult,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -317,14 +309,10 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
|
||||
|
||||
readonly setAvailableBranches = this.updater((state, availableBranches: BranchDTO[]) => {
|
||||
const branch = state.branch || state.defaultBranch;
|
||||
const userBranch = availableBranches.find((b) => b.id === branch.id);
|
||||
const filterResult = availableBranches;
|
||||
filterResult.sort((a: BranchDTO, b: BranchDTO) => this.branchSorterFn(a, b, userBranch));
|
||||
return {
|
||||
...state,
|
||||
availableBranches,
|
||||
branch,
|
||||
filterResult,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -352,38 +340,6 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
|
||||
};
|
||||
});
|
||||
|
||||
readonly setFilteredBranches = this.updater((state, filterValue?: string) => {
|
||||
const branch = state.branch || state.defaultBranch;
|
||||
if (!!filterValue) {
|
||||
const filterResult = state.availableBranches.filter((b) => {
|
||||
const name = b.name.toLowerCase();
|
||||
const zipCode = b.address?.zipCode;
|
||||
const city = b.address?.city?.toLowerCase();
|
||||
if (!zipCode || !city) {
|
||||
return name.indexOf(filterValue.toLowerCase()) >= 0;
|
||||
} else {
|
||||
return (
|
||||
name.indexOf(filterValue.toLowerCase()) >= 0 ||
|
||||
zipCode.indexOf(filterValue) >= 0 ||
|
||||
city.indexOf(filterValue.toLowerCase()) >= 0
|
||||
);
|
||||
}
|
||||
});
|
||||
filterResult.sort((a: BranchDTO, b: BranchDTO) => this.branchSorterFn(a, b, branch));
|
||||
return {
|
||||
...state,
|
||||
filterResult,
|
||||
};
|
||||
} else {
|
||||
const filterResult = state.availableBranches;
|
||||
filterResult.sort((a: BranchDTO, b: BranchDTO) => this.branchSorterFn(a, b, branch));
|
||||
return {
|
||||
...state,
|
||||
filterResult,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
loadBranches = this.effect((branchId$: Observable<number>) =>
|
||||
branchId$.pipe(
|
||||
switchMap((branchId) =>
|
||||
@@ -476,19 +432,15 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
|
||||
withLatestFrom(this.selectOlaAvailability, this.selectProcessId, this.selectOrderType),
|
||||
switchMap(([_, availability, processId, orderType]) => {
|
||||
this.patchState({ checkingCanAdd: true });
|
||||
return this.checkoutService
|
||||
.canAddItem({
|
||||
processId,
|
||||
availability,
|
||||
orderType,
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(canAdd) => this.setCanAdd(canAdd),
|
||||
(error: Error) => this.setCanAdd(error?.message)
|
||||
),
|
||||
tap((_) => this.patchState({ checkingCanAdd: false }))
|
||||
);
|
||||
return this.checkoutService.canAddItems({ processId, payload: [{ availabilities: [availability] }], orderType }).pipe(
|
||||
tapResponse(
|
||||
(response: any) => {
|
||||
this.setCanAdd(response?.find((_) => true)?.status === 0 ? true : response?.find((_) => true)?.message);
|
||||
},
|
||||
(error: Error) => this.setCanAdd(error?.message)
|
||||
),
|
||||
tap((_) => this.patchState({ checkingCanAdd: false }))
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
@@ -515,13 +467,6 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
|
||||
)
|
||||
);
|
||||
|
||||
private branchSorterFn(a: BranchDTO, b: BranchDTO, userBranch: BranchDTO) {
|
||||
return (
|
||||
geoDistance(userBranch?.address?.geoLocation, a?.address?.geoLocation) -
|
||||
geoDistance(userBranch?.address?.geoLocation, b?.address?.geoLocation)
|
||||
);
|
||||
}
|
||||
|
||||
readonly loadDefaultBranch = this.effect(($) =>
|
||||
$.pipe(
|
||||
switchMap((_) =>
|
||||
|
||||
@@ -7,14 +7,7 @@
|
||||
<p>
|
||||
Möchten Sie den Artikel zurücklegen lassen oder sofort mitnehmen?
|
||||
</p>
|
||||
<span class="price" *ngIf="availability.price?.value?.value; else retailPrice">{{
|
||||
availability.price?.value?.value | currency: availability.price?.value?.currency:'code'
|
||||
}}</span>
|
||||
<ng-template #retailPrice>
|
||||
<span class="price" *ngIf="availability.retailPrice?.value?.value">{{
|
||||
availability.retailPrice?.value?.value | currency: availability.retailPrice?.value?.currency:'code'
|
||||
}}</span>
|
||||
</ng-template>
|
||||
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
|
||||
<div class="grow"></div>
|
||||
<div>
|
||||
<button
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
|
||||
@@ -9,13 +11,17 @@ import { PurchasingOptionsModalStore } from '../purchasing-options-modal.store';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TakeAwayOptionComponent {
|
||||
readonly item$ = this.purchasingOptionsModalStore.selectItem;
|
||||
readonly item$ = this._purchasingOptionsModalStore.selectItem;
|
||||
|
||||
readonly availability$ = this.purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['take-away']));
|
||||
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['take-away']));
|
||||
|
||||
constructor(private purchasingOptionsModalStore: PurchasingOptionsModalStore) {}
|
||||
readonly price$ = combineLatest([this.availability$, this.item$]).pipe(
|
||||
map(([availability, item]) => this._availabilityService.getPriceForAvailability('take-away', item.catalogAvailability, availability))
|
||||
);
|
||||
|
||||
constructor(private _purchasingOptionsModalStore: PurchasingOptionsModalStore, private _availabilityService: DomainAvailabilityService) {}
|
||||
|
||||
select() {
|
||||
this.purchasingOptionsModalStore.setOption('take-away');
|
||||
this._purchasingOptionsModalStore.setOption('take-away');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { PurchasingOptionsListModalModule } from './modals/purchasing-options-list-modal';
|
||||
|
||||
import { PurchasingOptionsModalModule } from './modals/purchasing-options-modal';
|
||||
|
||||
@NgModule({
|
||||
imports: [PurchasingOptionsModalModule],
|
||||
exports: [PurchasingOptionsModalModule],
|
||||
imports: [PurchasingOptionsModalModule, PurchasingOptionsListModalModule],
|
||||
exports: [PurchasingOptionsModalModule, PurchasingOptionsListModalModule],
|
||||
})
|
||||
export class PageCheckoutModalsModule {}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout',
|
||||
|
||||
@@ -67,7 +67,7 @@ export class TaskInfoComponent implements OnChanges {
|
||||
return `${this.datePipe.transform(dateFrom, 'dd.MM.yy')} bis ${this.datePipe.transform(dateTo, 'dd.MM.yy')}`;
|
||||
}
|
||||
|
||||
return `${this.datePipe.transform(dateFrom, 'dd.MM.yy HH:mm')} Uhr bis ${this.datePipe.transform(dateTo, 'dd.MM.yy HH:mm')} Uhr`;
|
||||
return `${this.datePipe.transform(dateFrom, 'dd.MM.yy')} bis ${this.datePipe.transform(dateTo, 'dd.MM.yy')}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container *ngIf="itemType$ | async; let itemType">
|
||||
<div class="indicator" *ngIf="isTask$ | async" [style.backgroundColor]="indicatorColor$ | async"></div>
|
||||
<div class="indicator" *ngIf="(isTask$ | async) && !(hasUpdate$ | async)" [style.backgroundColor]="indicatorColor$ | async"></div>
|
||||
<div class="icon" *ngIf="hasIcon$ | async">
|
||||
<ui-icon icon="info" *ngIf="isInfoOrPreInfo$ | async" size="16px"></ui-icon>
|
||||
<ui-icon icon="calendar" size="16px" *ngIf="isPreInfo$ | async"></ui-icon>
|
||||
|
||||
@@ -51,6 +51,8 @@ export class TaskListItemComponent implements OnChanges {
|
||||
map(([processingStatus, info]) => (info.successor && processingStatus.includes('Removed')) || info.predecessor)
|
||||
);
|
||||
|
||||
hasUpdate$ = this.item$.pipe(map((item) => !!item.successor));
|
||||
|
||||
@HostBinding('class')
|
||||
get completed() {
|
||||
return this.domainTaskCalendarService.getProcessingStatusList(this.item);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
[(page)]="page"
|
||||
(after-load-complete)="callBackFn($event)"
|
||||
[fit-to-page]="true"
|
||||
zoom-scale="page-height"
|
||||
></pdf-viewer>
|
||||
|
||||
<button (click)="print()" class="cta-print" type="button">
|
||||
|
||||
@@ -44,7 +44,8 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
|
||||
(s) =>
|
||||
this.dateAdapter.equals({ first: s.date, second: new Date(calendarIndicator.date), precision: 'day' }) &&
|
||||
s.color === calendarIndicator.color
|
||||
)
|
||||
) &&
|
||||
!item.successor // do not show color indicator for items with successor
|
||||
) {
|
||||
return [...agg, calendarIndicator];
|
||||
}
|
||||
|
||||
@@ -4,15 +4,20 @@
|
||||
<div class="process-name-container pt-3" (click)="selectProcess(process)">
|
||||
<span class="process-name">{{ process.name }}</span>
|
||||
</div>
|
||||
<ng-container>
|
||||
<div class="cart-container" [ngClass]="{ download: cartBackgroundForDownload }" (click)="openCart(process)">
|
||||
<ng-container *ngIf="{ length: cartCount$ | async }; let cartCount">
|
||||
<div
|
||||
[class.items-in-cart]="cartCount.length > 0"
|
||||
class="cart-container"
|
||||
[ngClass]="{ download: cartBackgroundForDownload }"
|
||||
(click)="openCart(process)"
|
||||
>
|
||||
<lib-icon
|
||||
mt="12px"
|
||||
ml="15px"
|
||||
width="17px"
|
||||
height="16px"
|
||||
name="Shopping_Cart"
|
||||
*ngIf="!cartBackgroundForDownload && process.id === (currentProcessId$ | async)"
|
||||
*ngIf="cartCount.length === 0 && !cartBackgroundForDownload && process.id === (currentProcessId$ | async)"
|
||||
class="process-cart-icon"
|
||||
></lib-icon>
|
||||
<lib-icon
|
||||
@@ -21,7 +26,7 @@
|
||||
width="17px"
|
||||
height="16px"
|
||||
name="Shopping_Cart_Inactive"
|
||||
*ngIf="!cartBackgroundForDownload && process.id !== (currentProcessId$ | async)"
|
||||
*ngIf="cartCount.length === 0 && !cartBackgroundForDownload && process.id !== (currentProcessId$ | async)"
|
||||
class="process-cart-icon"
|
||||
></lib-icon>
|
||||
<lib-icon
|
||||
@@ -30,11 +35,16 @@
|
||||
width="17px"
|
||||
height="16px"
|
||||
name="shopping_cart_white"
|
||||
*ngIf="cartBackgroundForDownload"
|
||||
*ngIf="cartCount.length > 0 || cartBackgroundForDownload"
|
||||
class="process-cart-icon"
|
||||
></lib-icon>
|
||||
<div [@cartnumber]="cartanimation" class="pt-3 process-cart-number-container">
|
||||
<span class="process-cart-number" [ngClass]="{ 'number-download': cartBackgroundForDownload }">{{ cartCount$ | async }}</span>
|
||||
<span
|
||||
[class.items-in-cart]="cartCount.length > 0"
|
||||
class="process-cart-number"
|
||||
[ngClass]="{ 'number-download': cartBackgroundForDownload }"
|
||||
>{{ cartCount.length }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -126,3 +126,8 @@
|
||||
.last {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.items-in-cart {
|
||||
@apply bg-active-customer !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@
|
||||
<div class="detail">
|
||||
<div class="label">ISBN/EAN</div>
|
||||
<div class="value">{{ orderItem.product?.ean }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Meldenummer</div>
|
||||
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
|
||||
<button class="cta-more" *ngIf="(more$ | async) === false" (click)="setMore(true)">
|
||||
Mehr <ui-icon size="15px" icon="arrow"></ui-icon>
|
||||
</button>
|
||||
@@ -83,10 +87,6 @@
|
||||
<div class="label">Lieferant</div>
|
||||
<div class="value">{{ orderItem.supplier }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Meldenummer</div>
|
||||
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">
|
||||
<ng-container
|
||||
@@ -160,40 +160,38 @@
|
||||
<div class="goods-in-out-order-details-item-comment">
|
||||
<label for="comment">Anmerkung</label>
|
||||
|
||||
<input #specialCommentInput type="text" name="comment" [formControl]="specialCommentControl" (keyup.enter)="saveSpecialComment()" />
|
||||
<textarea
|
||||
matInput
|
||||
cdkTextareaAutosize
|
||||
#autosize="cdkTextareaAutosize"
|
||||
cdkAutosizeMinRows="1"
|
||||
cdkAutosizeMaxRows="5"
|
||||
#specialCommentInput
|
||||
(keydown.delete)="triggerResize()"
|
||||
(keydown.backspace)="triggerResize()"
|
||||
type="text"
|
||||
name="comment"
|
||||
[formControl]="specialCommentControl"
|
||||
[class.inactive]="!specialCommentControl.dirty"
|
||||
></textarea>
|
||||
|
||||
<button
|
||||
type="reset"
|
||||
class="clear"
|
||||
*ngIf="specialCommentControl?.enabled && !!specialCommentControl.value?.length"
|
||||
(click)="specialCommentControl.setValue(''); saveSpecialComment()"
|
||||
>
|
||||
<ui-icon icon="close" size="12px"></ui-icon>
|
||||
</button>
|
||||
<button
|
||||
class="cta-save"
|
||||
type="submit"
|
||||
*ngIf="specialCommentControl?.enabled && specialCommentControl.dirty"
|
||||
(click)="saveSpecialComment()"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="cta-edit"
|
||||
type="button"
|
||||
(click)="specialCommentControl?.enable(); specialCommentInput?.focus()"
|
||||
*ngIf="!specialCommentControl?.value && specialCommentControl?.disabled"
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
<button
|
||||
class="cta-edit"
|
||||
type="button"
|
||||
(click)="specialCommentControl?.enable(); specialCommentInput?.focus()"
|
||||
*ngIf="!!specialCommentControl?.value && specialCommentControl?.disabled"
|
||||
>
|
||||
Ändern
|
||||
</button>
|
||||
<div class="comment-actions">
|
||||
<button
|
||||
type="reset"
|
||||
class="clear"
|
||||
*ngIf="!!specialCommentControl.value?.length"
|
||||
(click)="specialCommentControl.setValue(''); saveSpecialComment(); triggerResize()"
|
||||
>
|
||||
<ui-icon icon="close" size="12px"></ui-icon>
|
||||
</button>
|
||||
<button
|
||||
class="cta-save"
|
||||
type="submit"
|
||||
*ngIf="specialCommentControl?.enabled && specialCommentControl.dirty"
|
||||
(click)="saveSpecialComment()"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -59,14 +59,28 @@ button {
|
||||
}
|
||||
|
||||
.goods-in-out-order-details-item-comment {
|
||||
@apply flex flex-row items-center p-4 bg-white text-base font-bold;
|
||||
@apply flex flex-row items-start p-4 bg-white text-base font-bold;
|
||||
|
||||
textarea {
|
||||
@apply flex-grow bg-transparent border-none outline-none text-base mx-4;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
textarea.inactive {
|
||||
@apply text-warning font-bold;
|
||||
@apply flex-grow bg-transparent border-none outline-none text-base mx-4 text-warning font-bold;
|
||||
// ipad color fix
|
||||
-webkit-text-fill-color: rgb(190, 129, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply flex-grow bg-transparent border-none outline-none text-base mx-4;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
input.inactive {
|
||||
@apply text-warning font-bold;
|
||||
@apply flex-grow bg-transparent border-none outline-none text-base mx-4 text-warning font-bold;
|
||||
// ipad color fix
|
||||
-webkit-text-fill-color: rgb(190, 129, 0);
|
||||
opacity: 1;
|
||||
@@ -79,6 +93,10 @@ button {
|
||||
button.clear {
|
||||
@apply text-inactive-customer;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-more {
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ChangeDetectorRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { DomainOmsService, DomainReceiptService } from '@domain/oms';
|
||||
import { HistoryComponent } from '@modal/history';
|
||||
@@ -27,6 +38,8 @@ export interface SharedGoodsInOutOrderDetailsItemComponentState {
|
||||
})
|
||||
export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<SharedGoodsInOutOrderDetailsItemComponentState>
|
||||
implements OnInit, OnDestroy {
|
||||
@ViewChild('autosize') autosize: CdkTextareaAutosize;
|
||||
|
||||
@Input()
|
||||
get orderItem() {
|
||||
return this.get((s) => s.orderItem);
|
||||
@@ -38,7 +51,6 @@ export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<Sh
|
||||
|
||||
this.patchState({ orderItem, quantity: orderItem?.quantity, receipts: [], more: false });
|
||||
this.specialCommentControl.reset(orderItem?.specialComment);
|
||||
this.specialCommentControl.disable();
|
||||
|
||||
// Add New OrderItem to selected list if selected was set to true by its input
|
||||
if (this.get((s) => s.selected)) {
|
||||
@@ -173,17 +185,15 @@ export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<Sh
|
||||
);
|
||||
|
||||
async saveSpecialComment() {
|
||||
this.specialCommentControl.disable();
|
||||
const { orderId, orderItemId, orderItemSubsetId } = this.orderItem;
|
||||
|
||||
try {
|
||||
this.specialCommentControl.reset(this.specialCommentControl.value);
|
||||
const res = await this._omsService
|
||||
.patchComment({ orderId, orderItemId, orderItemSubsetId, specialComment: this.specialCommentControl.value ?? '' })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
this.specialCommentControl.reset(this.specialCommentControl.value);
|
||||
|
||||
this.orderItem = { ...this.orderItem, specialComment: this.specialCommentControl.value ?? '' };
|
||||
this.orderItemChange.next(this.orderItem);
|
||||
this._host.updateOrderItems([this.orderItem]);
|
||||
@@ -218,4 +228,8 @@ export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<Sh
|
||||
const orderItems = this.order?.items;
|
||||
return orderItems.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)?.data?.features?.orderType;
|
||||
}
|
||||
|
||||
triggerResize() {
|
||||
this.autosize.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { UiSliderModule } from '@ui/slider';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -35,6 +36,7 @@ import { UiTooltipModule } from '@ui/tooltip';
|
||||
UiSelectBulletModule,
|
||||
UiSpinnerModule,
|
||||
UiTooltipModule,
|
||||
TextFieldModule,
|
||||
],
|
||||
exports: [
|
||||
SharedGoodsInOutOrderDetailsComponent,
|
||||
|
||||
@@ -10,3 +10,7 @@ import { Injectable } from '@angular/core';
|
||||
export class AvConfiguration {
|
||||
rootUrl: string = 'https://isa-test.paragon-data.net/ava/v4';
|
||||
}
|
||||
|
||||
export interface AvConfigurationInterface {
|
||||
rootUrl?: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* tslint:disable */
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { AvConfiguration } from './av-configuration';
|
||||
import { AvConfiguration, AvConfigurationInterface } from './av-configuration';
|
||||
|
||||
import { AvailabilityService } from './services/availability.service';
|
||||
|
||||
@@ -21,4 +21,16 @@ import { AvailabilityService } from './services/availability.service';
|
||||
AvailabilityService
|
||||
],
|
||||
})
|
||||
export class AvModule { }
|
||||
export class AvModule {
|
||||
static forRoot(customParams: AvConfigurationInterface): ModuleWithProviders<AvModule> {
|
||||
return {
|
||||
ngModule: AvModule,
|
||||
providers: [
|
||||
{
|
||||
provide: AvConfiguration,
|
||||
useValue: {rootUrl: customParams.rootUrl}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,16 @@ export { ResponseArgsOfIEnumerableOfAvailabilityDTO } from './models/response-ar
|
||||
export { AvailabilityDTO } from './models/availability-dto';
|
||||
export { PriceDTO } from './models/price-dto';
|
||||
export { PriceValueDTO } from './models/price-value-dto';
|
||||
export { TouchedBase } from './models/touched-base';
|
||||
export { VATValueDTO } from './models/vatvalue-dto';
|
||||
export { VATType } from './models/vattype';
|
||||
export { AvailabilityType } from './models/availability-type';
|
||||
export { RangeDTO } from './models/range-dto';
|
||||
export { ResponseArgs } from './models/response-args';
|
||||
export { DialogOfString } from './models/dialog-of-string';
|
||||
export { DialogSettings } from './models/dialog-settings';
|
||||
export { DialogContentType } from './models/dialog-content-type';
|
||||
export { KeyValueDTOOfStringAndString } from './models/key-value-dtoof-string-and-string';
|
||||
export { IPublicUserInfo } from './models/ipublic-user-info';
|
||||
export { ProblemDetails } from './models/problem-details';
|
||||
export { AvailabilityRequestDTO } from './models/availability-request-dto';
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
/* tslint:disable */
|
||||
import { RangeDTO } from './range-dto';
|
||||
import { PriceDTO } from './price-dto';
|
||||
import { AvailabilityType } from './availability-type';
|
||||
export interface AvailabilityDTO {
|
||||
itemId?: number;
|
||||
supplierProductNumber?: string;
|
||||
requestReference?: string;
|
||||
altAt?: string;
|
||||
at?: string;
|
||||
ean?: string;
|
||||
shop?: number;
|
||||
price?: PriceDTO;
|
||||
supplier?: string;
|
||||
supplierId?: number;
|
||||
estimatedDelivery?: RangeDTO;
|
||||
isPrebooked?: boolean;
|
||||
itemId?: number;
|
||||
logistician?: string;
|
||||
logisticianId?: number;
|
||||
orderReference?: string;
|
||||
preferred?: number;
|
||||
price?: PriceDTO;
|
||||
qty?: number;
|
||||
requestMessage?: string;
|
||||
requestReference?: string;
|
||||
requestStatusCode?: string;
|
||||
requested?: string;
|
||||
shop?: number;
|
||||
ssc?: string;
|
||||
sscText?: string;
|
||||
qty?: number;
|
||||
isPrebooked?: boolean;
|
||||
at?: string;
|
||||
altAt?: string;
|
||||
status: AvailabilityType;
|
||||
preferred?: number;
|
||||
requested?: string;
|
||||
requestStatusCode?: string;
|
||||
requestMessage?: string;
|
||||
supplier?: string;
|
||||
supplierId?: number;
|
||||
supplierProductNumber?: string;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/* tslint:disable */
|
||||
import { PriceDTO } from './price-dto';
|
||||
export interface AvailabilityRequestDTO {
|
||||
itemId?: string;
|
||||
supplierProductNumber?: string;
|
||||
availabilityReference?: string;
|
||||
branchNumber?: string;
|
||||
ean?: string;
|
||||
qty: number;
|
||||
estimatedShipping?: string;
|
||||
itemId?: string;
|
||||
name?: string;
|
||||
orderCode?: string;
|
||||
supplier?: string;
|
||||
preBook?: boolean;
|
||||
price?: PriceDTO;
|
||||
ssc?: string;
|
||||
estimatedShipping?: string;
|
||||
qty: number;
|
||||
shopId?: number;
|
||||
branchNumber?: string;
|
||||
availabilityReference?: string;
|
||||
name?: string;
|
||||
ssc?: string;
|
||||
supplier?: string;
|
||||
supplierProductNumber?: string;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/* tslint:disable */
|
||||
export type AvailabilityType = 0 | 1 | 2 | 32 | 256 | 1024 | 2048 | 4096 | 8192 | 16384;
|
||||
export type AvailabilityType = 0 | 1 | 2 | 32 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384;
|
||||
@@ -0,0 +1,2 @@
|
||||
/* tslint:disable */
|
||||
export type DialogContentType = 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128;
|
||||
16
apps/swagger/availability/src/lib/models/dialog-of-string.ts
Normal file
16
apps/swagger/availability/src/lib/models/dialog-of-string.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/* tslint:disable */
|
||||
import { KeyValueDTOOfStringAndString } from './key-value-dtoof-string-and-string';
|
||||
import { DialogContentType } from './dialog-content-type';
|
||||
import { DialogSettings } from './dialog-settings';
|
||||
export interface DialogOfString {
|
||||
actions?: Array<KeyValueDTOOfStringAndString>;
|
||||
actionsRequired?: number;
|
||||
area?: string;
|
||||
content?: string;
|
||||
contentType: DialogContentType;
|
||||
description?: string;
|
||||
displayTimeout?: number;
|
||||
settings: DialogSettings;
|
||||
subtitle?: string;
|
||||
title?: string;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/* tslint:disable */
|
||||
export type DialogSettings = 0 | 1 | 2 | 4;
|
||||
@@ -2,6 +2,6 @@
|
||||
export interface IPublicUserInfo {
|
||||
alias?: string;
|
||||
displayName?: string;
|
||||
username?: string;
|
||||
isAuthenticated: boolean;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/* tslint:disable */
|
||||
export interface KeyValueDTOOfStringAndString {
|
||||
command?: string;
|
||||
description?: string;
|
||||
enabled?: boolean;
|
||||
key?: string;
|
||||
label?: string;
|
||||
selected?: boolean;
|
||||
sort?: number;
|
||||
value?: string;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/* tslint:disable */
|
||||
import { TouchedBase } from './touched-base';
|
||||
import { PriceValueDTO } from './price-value-dto';
|
||||
import { VATValueDTO } from './vatvalue-dto';
|
||||
export interface PriceDTO {
|
||||
export interface PriceDTO extends TouchedBase{
|
||||
value?: PriceValueDTO;
|
||||
vat?: VATValueDTO;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* tslint:disable */
|
||||
export interface PriceValueDTO {
|
||||
value?: number;
|
||||
import { TouchedBase } from './touched-base';
|
||||
export interface PriceValueDTO extends TouchedBase{
|
||||
currency?: string;
|
||||
currencySymbol?: string;
|
||||
value?: number;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* tslint:disable */
|
||||
export interface ProblemDetails {
|
||||
type?: string;
|
||||
title?: string;
|
||||
status?: number;
|
||||
detail?: string;
|
||||
extensions: {[key: string]: any};
|
||||
instance?: string;
|
||||
extensions?: {[key: string]: any};
|
||||
status?: number;
|
||||
title?: string;
|
||||
type?: string;
|
||||
[prop: string]: any;
|
||||
}
|
||||
|
||||
5
apps/swagger/availability/src/lib/models/range-dto.ts
Normal file
5
apps/swagger/availability/src/lib/models/range-dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/* tslint:disable */
|
||||
export interface RangeDTO {
|
||||
start?: string;
|
||||
stop?: string;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/* tslint:disable */
|
||||
import { ResponseArgs } from './response-args';
|
||||
import { AvailabilityDTO } from './availability-dto';
|
||||
export interface ResponseArgsOfIEnumerableOfAvailabilityDTO extends ResponseArgs {
|
||||
export interface ResponseArgsOfIEnumerableOfAvailabilityDTO extends ResponseArgs{
|
||||
result?: Array<AvailabilityDTO>;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/* tslint:disable */
|
||||
import { DialogOfString } from './dialog-of-string';
|
||||
import { IPublicUserInfo } from './ipublic-user-info';
|
||||
export interface ResponseArgs {
|
||||
dialog?: DialogOfString;
|
||||
error: boolean;
|
||||
invalidProperties?: {[key: string]: string};
|
||||
message?: string;
|
||||
requestId?: number;
|
||||
userInfo?: IPublicUserInfo;
|
||||
invalidProperties?: {[key: string]: string};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user