Merge branch 'release/2.3'

This commit is contained in:
Michael Auer
2023-07-11 12:20:14 +02:00
336 changed files with 7583 additions and 6869 deletions

View File

@@ -1635,5 +1635,8 @@
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@@ -34,6 +34,11 @@ export class DomainAvailabilityService {
private _branchService: StoreCheckoutBranchService
) {}
@memorize({ ttl: 10000 })
memorizedAvailabilityShippingAvailability(request: Array<AvailabilityRequestDTO>) {
return this._availabilityService.AvailabilityShippingAvailability(request).pipe(shareReplay(1));
}
@memorize()
getSuppliers(): Observable<SupplierDTO[]> {
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
@@ -160,7 +165,7 @@ export class DomainAvailabilityService {
),
map(([response, supplier, defaultBranch]) => {
const price = item?.price;
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch.id ?? defaultBranch.id, quantity, price });
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch?.id ?? defaultBranch?.id, quantity, price });
}),
shareReplay(1)
);
@@ -249,57 +254,53 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this._availabilityService
.AvailabilityShippingAvailability([
{
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
qty: quantity,
},
])
.pipe(
timeout(5000),
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
shareReplay(1)
);
return this.memorizedAvailabilityShippingAvailability([
{
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
qty: quantity,
},
]).pipe(
timeout(5000),
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
shareReplay(1)
);
}
@memorize({ ttl: 10000 })
getDigDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this._availabilityService
.AvailabilityShippingAvailability([
{
qty: quantity,
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
},
])
.pipe(
timeout(5000),
map((r) => {
const availabilities = r.result;
const preferred = availabilities?.find((f) => f.preferred === 1);
return this.memorizedAvailabilityShippingAvailability([
{
qty: quantity,
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
},
]).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,
supplier: { id: preferred?.supplierId },
isPrebooked: preferred?.isPrebooked,
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
estimatedDelivery: preferred?.estimatedDelivery,
price: preferred?.price,
logistician: { id: preferred?.logisticianId },
supplierProductNumber: preferred?.supplierProductNumber,
supplierInfo: preferred?.requestStatusCode,
lastRequest: preferred?.requested,
};
return availability;
}),
shareReplay(1)
);
const availability: AvailabilityDTO = {
availabilityType: preferred?.status,
ssc: preferred?.ssc,
sscText: preferred?.sscText,
supplier: { id: preferred?.supplierId },
isPrebooked: preferred?.isPrebooked,
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
estimatedDelivery: preferred?.estimatedDelivery,
price: preferred?.price,
logistician: { id: preferred?.logisticianId },
supplierProductNumber: preferred?.supplierProductNumber,
supplierInfo: preferred?.requestStatusCode,
lastRequest: preferred?.requested,
};
return availability;
}),
shareReplay(1)
);
}
@memorize({ ttl: 10000 })
@@ -333,37 +334,35 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDownloadAvailability({ item }: { item: ItemData }): Observable<AvailabilityDTO> {
return this._availabilityService
.AvailabilityShippingAvailability([
{
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
qty: 1,
},
])
.pipe(
map((r) => {
const availabilities = r.result;
const preferred = availabilities?.find((f) => f.preferred === 1);
return this.memorizedAvailabilityShippingAvailability([
{
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
qty: 1,
},
]).pipe(
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,
supplier: { id: preferred?.supplierId },
isPrebooked: preferred?.isPrebooked,
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
price: preferred?.price,
supplierProductNumber: preferred?.supplierProductNumber,
logistician: { id: preferred?.logisticianId },
supplierInfo: preferred?.requestStatusCode,
lastRequest: preferred?.requested,
};
return availability;
}),
shareReplay(1)
);
const availability: AvailabilityDTO = {
availabilityType: preferred?.status,
ssc: preferred?.ssc,
sscText: preferred?.sscText,
supplier: { id: preferred?.supplierId },
isPrebooked: preferred?.isPrebooked,
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
price: preferred?.price,
supplierProductNumber: preferred?.supplierProductNumber,
logistician: { id: preferred?.logisticianId },
supplierInfo: preferred?.requestStatusCode,
lastRequest: preferred?.requested,
};
return availability;
}),
shareReplay(1)
);
}
@memorize({ ttl: 10000 })
@@ -401,7 +400,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
);
@@ -409,7 +408,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
);
@@ -447,6 +446,9 @@ export class DomainAvailabilityService {
}
isAvailable({ availability }: { availability: AvailabilityDTO }) {
if (availability?.supplier?.id === 16 && availability?.inStock == 0) {
return false;
}
return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
}
@@ -538,6 +540,7 @@ export class DomainAvailabilityService {
return preferred.map((p) => {
return [
{
orderDeadline: p?.orderDeadline,
availabilityType: p?.status,
ssc: p?.ssc,
sscText: p?.sscText,
@@ -559,7 +562,6 @@ export class DomainAvailabilityService {
private _mapToShippingAvailability(availabilities: SwaggerAvailabilityDTO[]) {
const preferred = availabilities.filter((f) => f.preferred === 1);
return preferred.map((p) => {
return {
availabilityType: p?.status,
@@ -573,6 +575,7 @@ export class DomainAvailabilityService {
supplierInfo: p?.requestStatusCode,
lastRequest: p?.requested,
itemId: p.itemId,
priceMaintained: p.priceMaintained,
};
});
}

View File

@@ -26,6 +26,7 @@ import {
StoreCheckoutBuyerService,
StoreCheckoutPayerService,
StoreCheckoutBranchService,
ItemsResult,
} from '@swagger/checkout';
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
import { isNullOrUndefined, memorize } from '@utils/common';
@@ -198,7 +199,15 @@ export class DomainCheckoutService {
);
}
canAddItems({ processId, payload, orderType }: { processId: number; payload: ItemPayload[]; orderType: string }) {
canAddItems({
processId,
payload,
orderType,
}: {
processId: number;
payload: ItemPayload[];
orderType: string;
}): Observable<ItemsResult[]> {
return this.getShoppingCart({ processId }).pipe(
first(),
withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
@@ -217,7 +226,8 @@ export class DomainCheckoutService {
})
.pipe(
map((response) => {
return response.result;
// TODO: remove this when the API is fixed
return (response.result as unknown) as ItemsResult[];
})
);
})

View File

@@ -21,6 +21,7 @@ export class CollectOnDeliveryNoteActionHandler extends ActionHandler<OrderItems
const response = await this.orderService
.OrderCollectOnDeliveryNote({
data,
eagerLoading: 1,
})
.toPromise();
@@ -29,7 +30,7 @@ export class CollectOnDeliveryNoteActionHandler extends ActionHandler<OrderItems
return {
...context,
receipts: response.result,
receipts: response.result.map((r) => r.data),
};
}
}

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
import { OrderService } from '@swagger/oms';
import { OrderItemsContext } from './order-items.context';
@Injectable()
export class CollectWithSmallAmountinvoiceActionHandler extends ActionHandler<OrderItemsContext> {
constructor(private orderService: OrderService) {
super('COLLECT_WITH_SMALLAMOUNTINVOICE');
}
async handler(context: OrderItemsContext): Promise<OrderItemsContext> {
const data: Record<number, number> = {};
context.items.forEach((orderItemSubsetId) => {
data[orderItemSubsetId.orderItemSubsetId] =
context.itemQuantity?.get(orderItemSubsetId.orderItemSubsetId) ?? orderItemSubsetId.quantity;
});
const response = await this.orderService
.OrderCollectWithSmallAmountInvoice({
data,
eagerLoading: 1,
})
.toPromise();
// Für korrekte Navigation nach Aufruf, da ProcessingStatus Serverseitig auf abgeholt gesetzt wird
context.items?.forEach((i) => (i.processingStatus = 256));
return {
...context,
receipts: response.result.map((r) => r.data),
};
}
}

View File

@@ -1,4 +1,3 @@
// start:ng42.barrel
export * from './accepted.action-handler';
export * from './arrived.action-handler';
export * from './assembled.action-handler';
@@ -7,6 +6,10 @@ export * from './back-to-stock.action-handler';
export * from './canceled-by-buyer.action-handler';
export * from './canceled-by-retailer.action-handler';
export * from './canceled-by-supplier.action-handler';
export * from './change-order-item-status-base.action-handler';
export * from './collect-on-deliverynote.action-handler';
export * from './collect-with-smallamountinvoice.action-handler';
export * from './create-returnitem.action-handler';
export * from './create-shipping-note.action-handler';
export * from './delivered.action-handler';
export * from './determine-supplier.action-handler';
@@ -25,16 +28,14 @@ export * from './parked.action-handler';
export * from './placed.action-handler';
export * from './preperation-for-shipping.action-handler';
export * from './print-compartment-label.action-handler';
export * from './print-pricediffqrcodelabel.action-handler';
export * from './print-shipping-note.action-handler';
export * from './re-ordered.action-handler';
export * from './print-smallamountinvoice.action-handler';
export * from './re-order.action-handler';
export * from './re-ordered.action-handler';
export * from './redirected-internally.action-handler';
export * from './requested.action-handler';
export * from './reserved.action-handler';
export * from './returned-by-buyer.action-handler';
export * from './shipping-note.action-handler';
export * from './supplier-temporarily-out-of-stock.action-handler copy';
export * from './collect-on-deliverynote.action-handler';
export * from './create-returnitem.action-handler';
export * from './print-pricediffqrcodelabel.action-handler';
// end:ng42.barrel

View File

@@ -27,7 +27,8 @@ export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsCont
printerType: 'Label',
print: async (printer) => {
try {
for (const group of groupBy(data?.receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
const receipts = data?.receipts?.filter((r) => r?.receiptType & 1);
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
await this.domainPrinterService.printShippingNote({ printer, receipts: group?.items?.map((r) => r?.id) }).toPromise();
}
return {

View File

@@ -0,0 +1,56 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
import { OrderItemsContext } from './order-items.context';
import { OMSPrintService } from '@swagger/print';
import { UiModalService } from '@ui/modal';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { NativeContainerService } from 'native-container';
import { groupBy } from '@ui/common';
@Injectable()
export class PrintSmallamountinvoiceActionHandler extends ActionHandler<OrderItemsContext> {
constructor(
private uiModal: UiModalService,
private omsPrintService: OMSPrintService,
private nativeContainerService: NativeContainerService
) {
super('PRINT_SMALLAMOUNTINVOICE');
}
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
await this.uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: !this.nativeContainerService.isNative,
printerType: 'Label',
print: async (printer) => {
try {
const receipts = data?.receipts?.filter((r) => r?.receiptType & 128);
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
await this.omsPrintService
.OMSPrintKleinbetragsrechnung({
data: group?.items?.map((r) => r?.id),
printer,
})
.toPromise();
}
return {
error: false,
};
} catch (error) {
console.error(error);
return {
error: true,
message: error?.message || error,
};
}
},
} as PrintModalData,
})
.afterClosed$.toPromise();
return data;
}
}

View File

@@ -1,6 +1,5 @@
import { Injectable } from '@angular/core';
import {
AutocompleteTokenDTO,
BranchService,
ChangeStockStatusCodeValues,
HistoryDTO,
@@ -11,9 +10,7 @@ import {
OrderItemSubsetDTO,
OrderListItemDTO,
OrderService,
QueryTokenDTO,
ReceiptService,
ResponseArgsOfIEnumerableOfHistoryDTO,
StatusValues,
StockStatusCodeService,
ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO,
@@ -22,7 +19,7 @@ import {
} from '@swagger/oms';
import { memorize } from '@utils/common';
import { Observable } from 'rxjs';
import { map, mergeMap, shareReplay } from 'rxjs/operators';
import { map, shareReplay } from 'rxjs/operators';
@Injectable()
export class DomainOmsService {
@@ -191,6 +188,10 @@ export class DomainOmsService {
);
}
getOrderSource(orderId: number): Observable<string> {
return this.getOrder(orderId).pipe(map((order) => order?.features?.orderSource));
}
updateNotifications(orderId: number, changes: { selected: NotificationChannel; email: string; mobile: string }) {
const communicationDetails = {
email: changes.email,

View File

@@ -0,0 +1,11 @@
import { PackageArrivalStatusDTO } from '@swagger/wws';
export abstract class PackageInspectionEvent {
constructor(public readonly type: string) {}
}
export class PackageStatusChangedEvent extends PackageInspectionEvent {
constructor(public readonly packageId: string, public readonly status: PackageArrivalStatusDTO) {
super('PackageStatusChangedEvent');
}
}

View File

@@ -1,10 +1,8 @@
import { Injectable } from '@angular/core';
import {
ListResponseArgsOfPackageDTO,
ListResponseArgsOfPackageDTO2,
PackageArrivalStatusDTO,
PackageDetailResponseDTO,
PackageDTO,
PackageDTO2,
QuerySettingsDTO,
QueryTokenDTO,
@@ -13,13 +11,18 @@ import {
ResponseArgsOfQuerySettingsDTO,
WareneingangService,
} from '@swagger/wws';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Observable, Subject } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { PackageInspectionEvent, PackageStatusChangedEvent } from './events';
@Injectable({
providedIn: 'root',
})
export class DomainPackageInspectionService {
private _events = new Subject<PackageInspectionEvent>();
events = this._events.asObservable();
constructor(private _wareneingang: WareneingangService) {}
getQuerySettingsResponse(): Observable<ResponseArgsOfQuerySettingsDTO> {
@@ -47,23 +50,26 @@ export class DomainPackageInspectionService {
}
changePackageStatusResponse(pkg: PackageDTO2, modifier: string): Observable<ResponseArgsOfPackageArrivalStatusDTO> {
return this._wareneingang.WareneingangChangePackageStatus({
packageId: pkg.id,
payload: {
id: pkg.id,
annotation: pkg.annotation,
area: pkg.area,
arrivalChecked: pkg.arrivalChecked,
arrivalStatus: pkg.arrivalStatus,
deliveryNoteNumber: pkg.deliveryNoteNumber,
deliveryTarget: pkg.deliveryTarget,
estimatedDeliveryDate: pkg.estimatedDeliveryDate,
packageNumber: pkg.packageNumber,
supplier: pkg.supplier,
trackingNumber: pkg.trackingNumber,
},
modifier,
});
return this._wareneingang
.WareneingangChangePackageStatus({
packageId: pkg.id,
payload: {
id: pkg.id,
annotation: pkg.annotation,
area: pkg.area,
arrivalChecked: pkg.arrivalChecked,
arrivalStatus: pkg.arrivalStatus,
deliveryNoteNumber: pkg.deliveryNoteNumber,
deliveryTarget: pkg.deliveryTarget,
estimatedDeliveryDate: pkg.estimatedDeliveryDate,
packageNumber: pkg.packageNumber,
supplier: pkg.supplier,
trackingNumber: pkg.trackingNumber,
scanId: pkg.scanId,
},
modifier,
})
.pipe(tap((res) => this._events.next(new PackageStatusChangedEvent(pkg.id, res.result))));
}
changePackageStatus(pkg: PackageDTO2, modifier: string): Observable<PackageArrivalStatusDTO> {

View File

@@ -1,6 +1,6 @@
/*
* Public API Surface of package-inspection
*/
export * from './lib/events';
export * from './lib/package-inspection.service';
export * from './lib/package-inspection.module';

View File

@@ -447,7 +447,7 @@ export class DomainRemissionService {
* Create a new receipt for the given return/remission
* @param returnId Return ID
* @param receiptNumber Receipt number
* @returns ReturnDTO - ShippingDocument
* @returns ReceiptDTO
*/
async createReceipt(returnDTO: ReturnDTO, receiptNumber?: string): Promise<ReceiptDTO> {
const stock = await this._getStock();
@@ -471,14 +471,41 @@ export class DomainRemissionService {
return receipt;
}
async completeReceipt(returnId: number, receiptId: number, packageCode: string): Promise<ReceiptDTO> {
/**
* Create a new Package and assign it to a receipt
* @param returnId Return ID
* @param receiptId Receipt ID
* @param packageNumber Packagenumber
* @returns ReceiptDTO
*/
async createReceiptAndAssignPackage({
returnId,
receiptId,
packageNumber,
}: {
returnId: number;
receiptId: number;
packageNumber: string;
}): Promise<ReceiptDTO> {
const response = await this._returnService
.ReturnCreateAndAssignPackage({
returnId,
receiptId,
data: {
packageNumber,
},
})
.toPromise();
const receipt: ReceiptDTO = response.result;
return receipt;
}
async completeReceipt(returnId: number, receiptId: number): Promise<ReceiptDTO> {
const res = await this._returnService
.ReturnFinalizeReceipt({
returnId,
receiptId,
data: {
packageCode,
},
data: {},
})
.toPromise();

View File

@@ -107,6 +107,25 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
aliases: [
{ alias: 'd-account', name: 'account' },
{ alias: 'd-no-account', name: 'package-variant-closed' },
{ name: 'isa-audio', alias: 'AU' },
{ name: 'isa-audio', alias: 'CAS' },
{ name: 'isa-audio', alias: 'DL' },
{ name: 'isa-audio', alias: 'KAS' },
{ name: 'isa-hard-cover', alias: 'BUCH' },
{ name: 'isa-hard-cover', alias: 'GEB' },
{ name: 'isa-hard-cover', alias: 'HC' },
{ name: 'isa-hard-cover', alias: 'KT' },
{ name: 'isa-ebook', alias: 'EB' },
{ name: 'isa-non-book', alias: 'GLO' },
{ name: 'isa-non-book', alias: 'HDL' },
{ name: 'isa-non-book', alias: 'NB' },
{ name: 'isa-non-book', alias: 'SPL' },
{ name: 'isa-calendar', alias: 'KA' },
{ name: 'isa-scroll', alias: 'MA' },
{ name: 'isa-software', alias: 'SW' },
{ name: 'isa-soft-cover', alias: 'TB' },
{ name: 'isa-video', alias: 'VI' },
{ name: 'isa-news-paper', alias: 'ZS' },
],
}),
],

View File

@@ -41,7 +41,7 @@
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
"rootUrl": "https://isa-test.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"

View File

@@ -40,7 +40,7 @@
"rootUrl": "https://filialinformationsystem-integration.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-integration.paragon-data.net/inv/v1"
"rootUrl": "https://isa-integration.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-integration.paragon-data.net/wws/v1"

View File

@@ -42,7 +42,7 @@
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
"rootUrl": "https://isa-test.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"

View File

@@ -41,7 +41,7 @@
"rootUrl": "https://filialinformationsystem.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa.paragon-systems.de/inv/v1"
"rootUrl": "https://isa.paragon-systems.de/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa.paragon-systems.de/wws/v1"

View File

@@ -41,7 +41,7 @@
"rootUrl": "https://filialinformationsystem-staging.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v1"
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-staging.paragon-systems.de/wws/v1"

View File

@@ -42,7 +42,7 @@
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
"rootUrl": "https://isa-test.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"

View File

@@ -54,19 +54,11 @@ export class PriceUpdateItemComponent {
inStock$ = combineLatest([this.defaultBranch$, this._item$]).pipe(
debounceTime(100),
filter(([defaultBranch, item]) => !!defaultBranch && !!item),
switchMap(
([defaultBranch, item]) =>
this._stockService.getInStock$({
itemId: Number(item?.product?.catalogProductNumber),
branchId: defaultBranch?.id,
})
// TODO: Bugfixing INSTOCK
// .pipe(
// map((instock) => {
// this.item.product.ean === '9783551775559' ? console.log({ item: this.item, instock }) : '';
// return instock;
// })
// )
switchMap(([defaultBranch, item]) =>
this._stockService.getInStock$({
itemId: Number(item?.product?.catalogProductNumber),
branchId: defaultBranch?.id,
})
),
shareReplay(1)
);

View File

@@ -94,7 +94,24 @@
<div class="fetching xsmall" *ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"></div>
<ng-template #showAvailabilityPickUpIcon>
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
<ui-icon
[uiOverlayTrigger]="orderDeadlineTooltip"
*ngIf="store.isPickUpAvailabilityAvailable$ | async"
icon="box_out"
size="18px"
></ui-icon>
<ui-tooltip
[warning]="true"
yPosition="above"
xPosition="after"
[yOffset]="-11"
[xOffset]="8"
#orderDeadlineTooltip
[closeable]="true"
>
<b>{{ (store.pickUpAvailability$ | async)?.orderDeadline | orderDeadline }}</b>
</ui-tooltip>
</ng-template>
<div class="fetching xsmall" *ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"></div>

View File

@@ -1,14 +1,12 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
import { ItemDTO as PrinterItemDTO } from '@swagger/print';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { AvailabilityDTO, BranchDTO } from '@swagger/checkout';
import { BranchDTO } from '@swagger/checkout';
import { UiModalService } from '@ui/modal';
import { ModalReviewsComponent } from '@modal/reviews';
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal';
import { PurchasingOptions } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal/purchasing-options-modal.store';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import { debounceTime, filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
import { ArticleDetailsStore } from './article-details.store';
@@ -20,7 +18,9 @@ import { BreadcrumbService } from '@core/breadcrumb';
import { ItemDTO } from '@swagger/cat';
import { DateAdapter } from '@ui/common';
import { DatePipe } from '@angular/common';
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCheckoutService } from '@domain/checkout';
@Component({
selector: 'page-article-details',
@@ -66,6 +66,10 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
this.store.isDeliveryB2BAvailabilityAvailable$,
]).pipe(map(([digDelivery, b2bDelivery]) => b2bDelivery && !digDelivery));
customerFeatures$ = this.applicationService.activatedProcessId$.pipe(
switchMap((processId) => this._domainCheckoutService.getCustomerFeatures({ processId }))
);
showSubscriptionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'PFO')));
showPromotionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'Promotion')));
@@ -125,7 +129,10 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
private _dateAdapter: DateAdapter,
private _datePipe: DatePipe,
public elementRef: ElementRef,
private _availability: DomainAvailabilityService
private _purchaseOptionsModalService: PurchaseOptionsModalService,
private _availability: DomainAvailabilityService,
private _router: Router,
private _domainCheckoutService: DomainCheckoutService
) {}
ngOnInit() {
@@ -262,59 +269,69 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
}
async showPurchasingModal(selectedBranch?: BranchDTO) {
let availableOptions: PurchasingOptions[] = [];
const availabilities: { [key: string]: AvailabilityDTO } = {};
const item = await this.store.item$.pipe(first()).toPromise();
const takeNow = await this.store.isTakeAwayAvailabilityAvailable$.pipe(first()).toPromise();
if (takeNow) {
availableOptions.push('take-away');
availabilities['take-away'] = await this.store.takeAwayAvailability$.pipe(first()).toPromise();
}
const download = await this.store.isDownloadAvailabilityAvailable$.pipe(first()).toPromise();
if (download) {
availableOptions.push('download');
availabilities['download'] = await this.store.downloadAvailability$.pipe(first()).toPromise();
}
const pickup = await this.store.isPickUpAvailabilityAvailable$.pipe(first()).toPromise();
if (pickup) {
availableOptions.push('pick-up');
availabilities['pick-up'] = await this.store.pickUpAvailability$.pipe(first()).toPromise();
}
const digDelivery = await this.store.isDeliveryDigAvailabilityAvailable$.pipe(first()).toPromise();
if (digDelivery) {
availableOptions.push('dig-delivery');
availabilities['dig-delivery'] = await this.store.deliveryDigAvailability$.pipe(first()).toPromise();
}
const b2b = await this.store.isDeliveryB2BAvailabilityAvailable$.pipe(first()).toPromise();
if (b2b) {
availableOptions.push('b2b-delivery');
availabilities['b2b-delivery'] = await this.store.deliveryB2BAvailability$.pipe(first()).toPromise();
}
if (availableOptions.includes('dig-delivery') && availableOptions.includes('b2b-delivery')) {
availableOptions.push('delivery');
availabilities['delivery'] = await this.store.deliveryAvailability$.pipe(first()).toPromise();
availableOptions = availableOptions.filter((option) => !(option === 'dig-delivery' || option === 'b2b-delivery'));
}
const branch = selectedBranch || (await this.store.branch$.pipe(first()).toPromise());
this.uiModal.open({
content: PurchasingOptionsModalComponent,
data: {
availableOptions,
option: selectedBranch ? 'take-away' : undefined,
item: await this.store.item$.pipe(first()).toPromise(),
branchId: branch?.id,
this._purchaseOptionsModalService
.open({
type: 'add',
processId: this.applicationService.activatedProcessId,
availabilities,
} as PurchasingOptionsModalData,
});
items: [item],
pickupBranch: selectedBranch,
inStoreBranch: selectedBranch,
})
.afterClosed$.subscribe(async (result) => {
if (result?.data === 'continue') {
const customer = await this._domainCheckoutService
.getBuyer({ processId: this.applicationService.activatedProcessId })
.pipe(first())
.toPromise();
if (customer) {
this.navigateToShoppingCart();
} else {
this.navigateToCustomerSearch();
}
} else if (result?.data === 'continue-shopping') {
this.navigateToResultList();
}
});
}
navigateToShoppingCart() {
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/cart/review`]);
}
async navigateToCustomerSearch() {
try {
const response = await this.customerFeatures$
.pipe(
first(),
switchMap((customerFeatures) => {
return this._domainCheckoutService.canSetCustomer({ processId: this.applicationService.activatedProcessId, customerFeatures });
})
)
.toPromise();
this._router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search'], {
queryParams: { filter_customertype: response.filter.customertype },
});
} catch (error) {
this._router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search']);
}
}
async navigateToResultList() {
let crumbs = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog'])
.pipe(first())
.toPromise();
crumbs = crumbs.filter((crumb) => !crumb.tags?.includes('details'));
const crumb = crumbs[crumbs.length - 1];
if (crumb) {
this._router.navigate([crumb.path], { queryParams: crumb.params });
} else {
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/product`]);
}
}
scrollTop() {

View File

@@ -10,6 +10,7 @@ import { ArticleRecommendationsComponent } from './recommendations/article-recom
import { PipesModule } from '../shared/pipes/pipes.module';
import { UiTooltipModule } from '@ui/tooltip';
import { UiCommonModule } from '@ui/common';
import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
@NgModule({
imports: [
@@ -22,6 +23,7 @@ import { UiCommonModule } from '@ui/common';
UiCommonModule,
UiTooltipModule,
PipesModule,
OrderDeadlinePipeModule,
],
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],

View File

@@ -129,7 +129,11 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
//#region Abholung
readonly fetchingPickUpAvailability$ = this.select((s) => s.fetchingPickUpAvailability);
readonly pickUpAvailability$: Observable<AvailabilityDTO> = combineLatest([this.itemData$, this.branch$, this.isDownload$]).pipe(
readonly pickUpAvailability$: Observable<AvailabilityDTO & { orderDeadline?: string }> = combineLatest([
this.itemData$,
this.branch$,
this.isDownload$,
]).pipe(
tap(() => this.patchState({ fetchingPickUpAvailability: true, fetchingPickUpAvailabilityError: undefined })),
switchMap(([item, branch, isDownload]) =>
!!item && !!branch && !isDownload

View File

@@ -1,10 +1,13 @@
<ng-container *ngIf="filter$ | async; let filter">
<div class="catalog-search-filter-content">
<button class="btn-close" type="button" (click)="close.emit()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
<div class="w-full flex flex-row justify-end items-center">
<button (click)="clearFilter(filter)" class="text-[#0556B4] mr-[0.8125rem]">Alle Filter entfernen</button>
<button class="text-cool-grey p-4 outline-none border-none bg-transparent" type="button" (click)="close.emit()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
</div>
<div class="catalog-search-filter-content-main">
<div class="catalog-search-filter-content-main -mt-8">
<h1 class="text-3xl font-bold text-center py-4">Filter</h1>
<ui-filter
[filter]="filter"

View File

@@ -6,10 +6,6 @@
@apply relative mx-auto p-4;
}
.btn-close {
@apply absolute text-cool-grey top-3 p-4 right-4 outline-none border-none bg-transparent;
}
.catalog-search-filter-content-main {
h1.title {
@apply text-center;

View File

@@ -58,4 +58,8 @@ export class ArticleSearchFilterComponent implements OnInit {
const queryParams = { main_qs: value?.getQueryParams()?.main_qs || '' };
this.articleSearch.setDefaultFilter(queryParams);
}
clearFilter(value: UiFilter) {
value.unselectAll();
}
}

View File

@@ -7,7 +7,7 @@ import { ItemDTO } from '@swagger/cat';
import { DateAdapter } from '@ui/common';
import { isEqual } from 'lodash';
import { combineLatest } from 'rxjs';
import { debounceTime, switchMap, map, tap, shareReplay } from 'rxjs/operators';
import { debounceTime, switchMap, map, shareReplay, filter } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
export interface SearchResultItemComponentState {
@@ -110,9 +110,11 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
inStock$ = combineLatest([this.item$, this.selectedBranchId$, this.defaultBranch$]).pipe(
debounceTime(100),
filter(([item, branch, defaultBranch]) => !!item && !!defaultBranch),
switchMap(([item, branch, defaultBranch]) =>
this._stockService.getInStock$({ itemId: item.id, branchId: branch?.id ?? defaultBranch?.id })
)
),
shareReplay(1)
);
constructor(

View File

@@ -241,6 +241,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
// 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);
}

View File

@@ -1,5 +1,11 @@
<shared-breadcrumb class="my-4" [key]="activatedProcessId$ | async" [tags]="['catalog']">
<shared-branch-selector [branchType]="1" [value]="selectedBranch$ | async" (valueChange)="patchProcessData($event)">
<shared-branch-selector
[filterCurrentBranch]="!!auth.hasRole('Store')"
[orderBy]="auth.hasRole('Store') ? 'distance' : 'name'"
[branchType]="1"
[value]="selectedBranch$ | async"
(valueChange)="patchProcessData($event)"
>
</shared-branch-selector>
</shared-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -1,5 +1,6 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
import { ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { EnvironmentService } from '@core/environment';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
@@ -30,11 +31,13 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
constructor(
public application: ApplicationService,
private _uiModal: UiModalService,
public auth: AuthService,
private _environmentService: EnvironmentService,
private _renderer: Renderer2
) {}
ngOnInit() {
// this.auth.getClaims();
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));

View File

@@ -6,8 +6,6 @@ import { DomainCheckoutService } from '@domain/checkout';
import { AvailabilityDTO, DestinationDTO, NotificationChannel, ShoppingCartItemDTO, ShoppingCartDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
import { PrintModalData, PrintModalComponent } from '@modal/printer';
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from '../modals/purchasing-options-modal';
import { PurchasingOptions } from '../modals/purchasing-options-modal/purchasing-options-modal.store';
import { AuthService } from '@core/auth';
import { first, map, shareReplay, switchMap, take, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
@@ -15,13 +13,11 @@ 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 { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/components/notification-channel-control';
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';
import { CheckoutDummyData } from '../checkout-dummy/checkout-dummy-data';
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
export interface CheckoutReviewComponentState {
shoppingCart: ShoppingCartDTO;
@@ -242,7 +238,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
private domainCatalogService: DomainCatalogService,
private breadcrumb: BreadcrumbService,
private domainPrinterService: DomainPrinterService,
private _fb: UntypedFormBuilder
private _fb: UntypedFormBuilder,
private _purchaseOptionsModalService: PurchaseOptionsModalService
) {
super({
shoppingCart: undefined,
@@ -274,7 +271,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
shoppingCart,
shoppingCartItems,
});
this.checkQuantityErrors(shoppingCartItems);
// this.checkQuantityErrors(shoppingCartItems);
},
(err) => {},
() => {}
@@ -285,15 +282,15 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
)
);
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);
}
});
}
// checkQuantityErrors(shoppingCartItems: ShoppingCartItemDTO[]) {
// shoppingCartItems.forEach((item) => {
// if (item.features?.orderType === 'Abholung') {
// this.setQuantityError(item, item.availability, item.quantity > item.availability?.inStock);
// } else {
// this.setQuantityError(item, item.availability, false);
// }
// });
// }
async updateBreadcrumb() {
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
@@ -434,171 +431,10 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
}
async changeItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
this.loadingOnItemChangeById$.next(shoppingCartItem.id);
const quantity = shoppingCartItem.quantity;
const branchNo = this.auth.getClaimByKey('branch_no');
const branchId = shoppingCartItem?.destination?.data?.targetBranch?.id;
const customerFeatures = await this.customerFeatures$.pipe(first()).toPromise();
let branch = await this.domainCheckoutService
.getBranches()
.pipe(map((branches) => branches.find((branch) => (branchId ? branch.id === branchId : branch.branchNumber === branchNo))))
.toPromise();
if (!branch) {
branch = await this.applicationService.getSelectedBranch$().pipe(take(1)).toPromise();
}
let catalogItem: ResponseArgsOfItemDTO;
if (Number.isInteger(shoppingCartItem?.product?.catalogProductNumber)) {
catalogItem = await this.domainCatalogService
.getDetailsById({ id: Number(shoppingCartItem.product.catalogProductNumber) })
.toPromise();
} else if (shoppingCartItem?.product?.ean) {
catalogItem = await this.domainCatalogService.getDetailsByEan({ ean: shoppingCartItem.product.ean }).toPromise();
}
let takeAwayAvailability: AvailabilityDTO;
if (!!catalogItem?.result?.product) {
takeAwayAvailability = await this.availabilityService
.getTakeAwayAvailability({
item: {
itemId: catalogItem.result.id,
ean: catalogItem.result.product.ean,
price: catalogItem.result.catalogAvailability?.price,
},
quantity,
})
.toPromise();
}
const pickupAvailability = await this.availabilityService
.getPickUpAvailability({
item: {
itemId: Number(shoppingCartItem.product.catalogProductNumber),
ean: shoppingCartItem.product.ean,
price: shoppingCartItem.availability.price,
},
branch,
quantity,
})
.toPromise();
const digAvailability = await this.availabilityService
.getDigDeliveryAvailability({
item: {
itemId: Number(shoppingCartItem.product.catalogProductNumber),
ean: shoppingCartItem.product.ean,
price: shoppingCartItem.availability.price,
},
quantity,
})
.toPromise();
const b2bAvailability = await this.availabilityService
.getB2bDeliveryAvailability({
item: {
itemId: Number(shoppingCartItem.product.catalogProductNumber),
ean: shoppingCartItem.product.ean,
price: shoppingCartItem.availability.price,
},
quantity,
})
.toPromise();
const downloadAvailability = await this.availabilityService
.getDownloadAvailability({
item: {
itemId: Number(shoppingCartItem.product.catalogProductNumber),
ean: shoppingCartItem.product.ean,
price: shoppingCartItem.availability.price,
},
})
.toPromise();
let availableOptions: PurchasingOptions[] = [];
const availabilities: { [key: string]: AvailabilityDTO } = {};
if (takeAwayAvailability && this.availabilityService.isAvailable({ availability: takeAwayAvailability })) {
availableOptions.push('take-away');
availabilities['take-away'] = takeAwayAvailability;
}
if (downloadAvailability && this.availabilityService.isAvailable({ availability: downloadAvailability })) {
availableOptions.push('download');
availabilities['download'] = downloadAvailability;
}
if (pickupAvailability && this.availabilityService.isAvailable({ availability: pickupAvailability[0] })) {
if (pickupAvailability[1].availableFor) {
if ((pickupAvailability[1].availableFor & 2) === 2) {
availableOptions.push('pick-up');
availabilities['pick-up'] = pickupAvailability[0];
}
} else {
availableOptions.push('pick-up');
availabilities['pick-up'] = pickupAvailability[0];
}
if (!customerFeatures?.webshop && this.availabilityService.isAvailable({ availability: b2bAvailability })) {
availableOptions.push('b2b-delivery');
availabilities['b2b-delivery'] = b2bAvailability;
}
}
if (digAvailability && this.availabilityService.isAvailable({ availability: digAvailability }) && !customerFeatures?.b2b) {
availableOptions.push('dig-delivery');
availabilities['dig-delivery'] = digAvailability;
}
if (availableOptions.includes('dig-delivery') && availableOptions.includes('b2b-delivery')) {
let shippingAvailability = await this.availabilityService
.getDeliveryAvailability({
item: {
itemId: Number(shoppingCartItem.product.catalogProductNumber),
ean: shoppingCartItem.product.ean,
price: shoppingCartItem.availability.price,
},
quantity,
})
.toPromise();
if (shippingAvailability && this.availabilityService.isAvailable({ availability: shippingAvailability })) {
availableOptions.push('delivery');
availabilities['delivery'] = shippingAvailability;
availableOptions = availableOptions.filter((option) => !(option === 'dig-delivery' || option === 'b2b-delivery'));
}
}
this.loadingOnItemChangeById$.next(undefined);
this.cdr.markForCheck();
const itemId = Number(shoppingCartItem.product.catalogProductNumber);
const modal = this.uiModal.open({
content: PurchasingOptionsModalComponent,
data: {
availableOptions,
item: {
id: itemId,
itemId: itemId,
product: shoppingCartItem.product,
price: shoppingCartItem.availability.price,
catalogAvailability: {
status: shoppingCartItem.availability.availabilityType,
price: shoppingCartItem.availability.price,
},
},
shoppingCartItem,
branchId: branch?.id,
processId: this.applicationService.activatedProcessId,
availabilities,
} as PurchasingOptionsModalData,
});
modal.afterClosed$.pipe(takeUntil(this._orderCompleted)).subscribe(() => {
this.setQuantityError(shoppingCartItem, undefined, false);
this._purchaseOptionsModalService.open({
processId: this.applicationService.activatedProcessId,
items: [shoppingCartItem],
type: 'update',
});
}
@@ -649,7 +485,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
})
.toPromise();
this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
// this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
break;
case 'Abholung':
availability = await this.availabilityService
@@ -727,7 +563,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
},
})
.toPromise();
this.setQuantityError(shoppingCartItem, availability, false);
// this.setQuantityError(shoppingCartItem, availability, false);
} else if (availability) {
// Wenn das Ergebnis der Availability Abfrage keinen Preis zurückliefert (z.B. HFI Geschenkkarte), wird der Preis aus der
// Availability vor der Abfrage verwendet
@@ -758,16 +594,16 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
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 });
}
}
// 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 });
// }
// }
// Bei unbekannten Kunden und DIG Bestellung findet ein Vergleich der Preise statt
compareDeliveryAndCatalogPrice(availability: AvailabilityDTO, orderType: string, shoppingCartItemPrice: number) {
@@ -807,6 +643,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
})
)
.toPromise();
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search'], {
queryParams: { filter_customertype: response.filter.customertype },
});
@@ -816,16 +653,10 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
}
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,
this._purchaseOptionsModalService.open({
processId: this.applicationService.activatedProcessId,
items: shoppingCartItems,
type: 'update',
});
}

View File

@@ -1,4 +1,3 @@
// start:ng42.barrel
export * from './page-checkout.module';
export * from './page-checkout-modals.module';
// end:ng42.barrel

View File

@@ -1,13 +0,0 @@
<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>

View File

@@ -1,23 +0,0 @@
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;
}
}
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './delivery-option-list.component';
// end:ng42.barrel

View File

@@ -1,7 +0,0 @@
// 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

View File

@@ -1,44 +0,0 @@
: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;
}
p {
@apply my-4;
}
::ng-deep page-purchasing-options-list-modal ui-branch-dropdown .wrapper {
@apply mx-auto;
width: 80%;
}
::ng-deep page-pick-up-option-list .option-chip:disabled,
::ng-deep page-take-away-option-list .option-chip:disabled {
@apply bg-disabled-branch border-disabled-branch text-white;
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './pick-up-option-list.component';
// end:ng42.barrel

View File

@@ -1,18 +0,0 @@
<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"
(selectBranch)="selectBranch($event)"
></ui-branch-dropdown>

View File

@@ -1,48 +0,0 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { BranchDTO } from '@swagger/checkout';
import { combineLatest } from 'rxjs';
import { first, map } 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$.pipe(
map((branch) => {
// Determins if branch is targetBranch
if (branch?.branchType === 1) {
return branch.name;
}
})
);
selectedOption$ = this._store.selectedFilterOption$;
optionChipDisabled$ = combineLatest([this._store.fetchingAvailabilities$, this.selectedBranch$]).pipe(
map(([fetching, selectedBranch]) => {
return fetching || !selectedBranch;
})
);
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 }));
}
}

View File

@@ -1,115 +0,0 @@
<div class="item-thumbnail">
<img loading="lazy" *ngIf="item?.product?.ean | productImage; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.name" />
</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" *ngIf="item?.product?.format && item?.product?.formatDetail">
<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>

View File

@@ -1,180 +0,0 @@
: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 border-solid border-2 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;
}
}

View File

@@ -1,250 +0,0 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { DomainAvailabilityService } from '@domain/availability';
import { AvailabilityDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { combineLatest, Observable } 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) => {
if (takeAwayAvailabilities) {
const availability = takeAwayAvailabilities[this.item.product?.catalogProductNumber];
if (typeof availability === 'boolean') {
return undefined;
}
return availability;
}
return undefined;
}),
shareReplay()
);
pickUpAvailabilities$: Observable<AvailabilityDTO> = this._store.pickUpAvailabilities$.pipe(
map((pickUpAvailabilities) => {
if (pickUpAvailabilities) {
const availability = pickUpAvailabilities[this.item.product?.catalogProductNumber];
if (typeof availability === 'boolean') {
return undefined;
}
return availability;
}
return undefined;
}),
shareReplay()
);
deliveryAvailabilities$ = this._store.deliveryAvailabilities$.pipe(
map((shippingAvailabilities) => (!!shippingAvailabilities ? shippingAvailabilities[this.item.product?.catalogProductNumber] : [])),
shareReplay()
);
deliveryDigAvailabilities$: Observable<AvailabilityDTO> = this._store.deliveryDigAvailabilities$.pipe(
map((shippingAvailabilities) => {
if (shippingAvailabilities) {
const availability = shippingAvailabilities[this.item.product?.catalogProductNumber];
if (typeof availability === 'boolean') {
return undefined;
}
return availability;
}
return undefined;
}),
shareReplay()
);
deliveryB2bAvailabilities$ = this._store.deliveryB2bAvailabilities$.pipe(
map((shippingAvailabilities) => {
if (shippingAvailabilities) {
const availability = shippingAvailabilities[this.item.product?.catalogProductNumber];
if (typeof availability === 'boolean') {
return undefined;
}
return availability;
}
return undefined;
}),
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$ = combineLatest([this.fetchingAvailabilities$, this._store.selectedFilterOption$]).pipe(
filter(([fetching]) => !fetching),
withLatestFrom(
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':
if (deliveryDig || delivery) {
availability = deliveryDig || delivery;
} else {
availability = deliveryB2b;
option = 'b2b-delivery';
availability.p;
}
break;
default:
return this.item.availability?.price ?? this.item.unitPrice;
}
return this._availabilityService.getPriceForAvailability(option, this.item.availability, availability) ?? this.item.unitPrice;
})
);
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 }] });
}
}
}

View File

@@ -1,49 +0,0 @@
<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>

View File

@@ -1,49 +0,0 @@
: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;
}
}
}

View File

@@ -1,266 +0,0 @@
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();
let option = this._store.selectedFilterOption;
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];
option = 'b2b-delivery';
}
break;
}
const price = this._availability.getPriceForAvailability(option, 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: price ? price : item.unitPrice,
},
promotion: item?.promotion?.points ? { points: item.promotion.points } : undefined,
};
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) {
console.error(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);
}
}
}

View File

@@ -1,7 +0,0 @@
import { ShoppingCartItemDTO } from '@swagger/checkout';
export interface PurchasingOptionsListModalData {
processId: number;
shoppingCartItems?: ShoppingCartItemDTO[];
customerFeatures: { [key: string]: string };
}

View File

@@ -1,41 +0,0 @@
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 {}

View File

@@ -1,598 +0,0 @@
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';
import { ApplicationService } from '@core/application';
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,
private _application: ApplicationService
) {
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(
map((av) => {
if (av?.length > 0) {
if (av[1].availableFor) {
if ((av[1].availableFor & 2) === 2) {
return av[0];
} else {
return undefined;
}
} else {
return av[0];
}
}
}),
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: {} });
}
)
);
})
)
);
getCurrentBranch() {
return combineLatest([this._application.getSelectedBranch$(), this._availabilityService.getDefaultBranch()]).pipe(
map(([selectedBranch, defaultBranch]) => selectedBranch || defaultBranch)
);
}
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.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: [] });
}
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './take-away-option-list.component';
// end:ng42.barrel

View File

@@ -1,18 +0,0 @@
<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"
(selectBranch)="selectBranch($event)"
></ui-branch-dropdown>

View File

@@ -1,48 +0,0 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { BranchDTO } from '@swagger/checkout';
import { combineLatest } from 'rxjs';
import { first, map } 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$.pipe(
map((branch) => {
// Determins if branch is targetBranch
if (branch?.branchType === 1) {
return branch.name;
}
})
);
selectedOption$ = this._store.selectedFilterOption$;
optionChipDisabled$ = combineLatest([this._store.fetchingAvailabilities$, this.selectedBranch$]).pipe(
map(([fetching, selectedBranch]) => {
return fetching || !selectedBranch;
})
);
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 }));
}
}

View File

@@ -1,23 +0,0 @@
<ng-container *ngIf="item$ | async; let item">
<ng-container *ngIf="availability$ | async; let availability">
<div class="option-icon">
<ui-icon size="80px" icon="truck_b2b"></ui-icon>
</div>
<h4>B2B Versand</h4>
<p>
Als B2B Kunde können wir Ihnen den Artikel auch liefern.
</p>
<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"
>Versanddatum <strong>{{ availability?.estimatedShippingDate | date: 'shortDate' }}</strong></span
>
<div>
<button [disabled]="availability.price?.value?.value < 0" type="button" class="select-option" (click)="select()">
Auswählen
</button>
</div>
</ng-container>
</ng-container>

View File

@@ -1,9 +0,0 @@
.option-icon {
margin-top: -12px;
width: 70px;
}
h4 {
@apply font-bold;
margin-top: -2px;
}

View File

@@ -1,27 +0,0 @@
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';
@Component({
selector: 'page-b2b-delivery-option',
templateUrl: 'b2b-delivery-option.component.html',
styleUrls: ['../option.scss', 'b2b-delivery-option.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class B2BDeliveryOptionComponent {
readonly item$ = this._purchasingOptionsModalStore.selectItem;
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['b2b-delivery']));
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');
}
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './b2b-delivery-option.component';
// end:ng42.barrel

View File

@@ -1,41 +0,0 @@
<ng-container *ngIf="item$ | async; let item">
<ng-container *ngIf="availability$ | async; let availability">
<div class="option-icon">
<ui-icon size="50px" icon="truck"></ui-icon>
</div>
<h4>Versand</h4>
<p>
Möchten Sie den Artikel geliefert bekommen?
</p>
<div class="price-wrapper">
<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
</button>
<ui-tooltip #tooltipContent yPosition="above" xPosition="after" [yOffset]="-16">
Günstigerer Preis aus Hugendubel Katalog wird übernommen
</ui-tooltip>
</ng-container>
</div>
<div class="grow"></div>
<span class="delivery">Versandkostenfrei</span>
<span *ngIf="availability?.estimatedDelivery; else estimatedShippingDateTmpl" 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 #estimatedShippingDateTmpl>
<span class="date">
Versanddatum <strong>{{ availability?.estimatedShippingDate | date }}</strong>
</span>
</ng-template>
<div>
<button [disabled]="availability.price?.value?.value < 0" type="button" class="select-option" (click)="select()">
Auswählen
</button>
</div>
</ng-container>
</ng-container>

View File

@@ -1,15 +0,0 @@
.price-wrapper {
@apply mt-2;
}
.info-tooltip-button {
@apply border-font-customer border-solid border-2 bg-white rounded-full text-base font-bold;
border-style: outset;
width: 31px;
height: 31px;
margin-left: 10px;
}
h4 {
@apply font-bold;
}

View File

@@ -1,35 +0,0 @@
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';
@Component({
selector: 'page-delivery-option',
templateUrl: 'delivery-option.component.html',
styleUrls: ['../option.scss', 'delivery-option.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DeliveryOptionComponent {
readonly item$ = this._purchasingOptionsModalStore.selectItem;
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['delivery']));
readonly showTooltip$ = combineLatest([this.availability$, this.item$]).pipe(
map(([availability, item]) => {
const shippingPrice = availability?.price?.value?.value;
const catalogPrice = item?.catalogAvailability?.price?.value?.value;
return catalogPrice < shippingPrice;
})
);
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');
}
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './delivery-option.component';
// end:ng42.barrel

View File

@@ -1,40 +0,0 @@
<ng-container *ngIf="item$ | async; let item">
<ng-container *ngIf="availability$ | async; let availability">
<div class="option-icon">
<ui-icon size="50px" icon="truck"></ui-icon>
</div>
<h4>DIG Versand</h4>
<p>Möchten Sie den Artikel geliefert bekommen?</p>
<div class="price-wrapper">
<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
</button>
<ui-tooltip #tooltipContent yPosition="above" xPosition="after" [yOffset]="-16">
Günstigerer Preis aus Hugendubel Katalog wird übernommen
</ui-tooltip>
</ng-container>
</div>
<div class="grow"></div>
<span class="delivery">Versandkostenfrei</span>
<span *ngIf="availability?.estimatedDelivery; else estimatedShippingDateTmpl" 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 #estimatedShippingDateTmpl>
<span class="date">
Versanddatum <strong>{{ availability?.estimatedShippingDate | date }}</strong>
</span>
</ng-template>
<div>
<button [disabled]="availability.price?.value?.value < 0" type="button" class="select-option" (click)="select()">
Auswählen
</button>
</div>
</ng-container>
</ng-container>

View File

@@ -1,15 +0,0 @@
.price-wrapper {
@apply mt-2;
}
.info-tooltip-button {
@apply border-font-customer bg-white rounded-full text-base font-bold;
border-style: outset;
width: 31px;
height: 31px;
margin-left: 10px;
}
h4 {
@apply font-bold;
}

View File

@@ -1,35 +0,0 @@
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';
@Component({
selector: 'page-dig-delivery-option',
templateUrl: 'dig-delivery-option.component.html',
styleUrls: ['../option.scss', 'dig-delivery-option.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DigDeliveryOptionComponent {
readonly item$ = this._purchasingOptionsModalStore.selectItem;
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['dig-delivery']));
readonly showTooltip$ = combineLatest([this.availability$, this.item$]).pipe(
map(([availability, item]) => {
const shippingPrice = availability?.price?.value?.value;
const catalogPrice = item?.catalogAvailability?.price?.value?.value;
return catalogPrice < shippingPrice;
})
);
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');
}
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './dig-delivery-option.component';
// end:ng42.barrel

View File

@@ -1,6 +0,0 @@
// start:ng42.barrel
export * from './options';
export * from './purchasing-options-modal.component';
export * from './purchasing-options-modal.data';
export * from './purchasing-options-modal.module';
// end:ng42.barrel

View File

@@ -1,41 +0,0 @@
:host {
@apply flex flex-col box-border text-center;
width: 202px;
}
.option-icon {
@apply text-ucla-blue mx-auto;
width: 40px;
}
h4 {
@apply text-2xl mt-4 mb-0;
}
p {
@apply my-2;
}
.price {
@apply font-bold my-2;
}
.delivery {
@apply text-regular mb-px-5;
}
.date {
@apply text-cta-l whitespace-nowrap;
}
.grow {
@apply flex-grow;
}
.select-option {
@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;
}
.select-option:disabled {
@apply bg-disabled-branch border-disabled-branch text-white;
}

View File

@@ -1,7 +0,0 @@
// start:ng42.barrel
export * from './b2b-delivery-option';
export * from './delivery-option';
export * from './dig-delivery-option';
export * from './pick-up-option';
export * from './take-away-option';
// end:ng42.barrel

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './pick-up-option.component';
// end:ng42.barrel

View File

@@ -1,32 +0,0 @@
<ng-container *ngIf="item$ | async; let item">
<ng-container *ngIf="availability$ | async; let availability">
<div class="option-icon">
<ui-icon size="50px" icon="box_out"></ui-icon>
</div>
<h4>Abholung</h4>
<p>
Möchten Sie den Artikel in einer unserer Filialen abholen?
</p>
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
<ui-branch-dropdown
class="min-h-[38px]"
[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
>
<div class="grow"></div>
<div>
<button
[disabled]="availability.price?.value?.value < 0 || !(selected$ | async)"
type="button"
class="select-option"
(click)="select()"
>
Auswählen
</button>
</div>
</ng-container>
</ng-container>

View File

@@ -1,3 +0,0 @@
h4 {
@apply font-bold;
}

View File

@@ -1,42 +0,0 @@
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';
@Component({
selector: 'page-pick-up-option',
templateUrl: 'pick-up-option.component.html',
styleUrls: ['../option.scss', 'pick-up-option.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PickUpOptionComponent {
branches$: Observable<BranchDTO[]> = this._purchasingOptionsModalStore.selectAvailableBranches;
selected$: Observable<string> = this._purchasingOptionsModalStore.selectBranch.pipe(
map((branch) => {
// Determins if branch is targetBranch
if (branch?.branchType === 1) {
return branch.name;
}
})
);
readonly item$ = this._purchasingOptionsModalStore.selectItem;
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');
}
selectBranch(branch: BranchDTO) {
this._purchasingOptionsModalStore.setBranch(branch);
}
}

View File

@@ -1,11 +0,0 @@
<form *ngIf="control" [formGroup]="control">
<ui-form-control label="MwSt" variant="default" *ngIf="!hideVat">
<ui-select formControlName="vat">
<ui-select-option *ngFor="let vat of vats$ | async" [label]="vat.name + '%'" [value]="vat.vatType"> </ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control class="price" label="Preis" variant="default">
<input uiInput formControlName="price" [max]="maxValue" maxLength="6" />
</ui-form-control>
</form>

View File

@@ -1,11 +0,0 @@
form {
@apply grid grid-flow-col items-center justify-end gap-4 mb-2;
}
ui-form-control {
@apply w-32;
}
::ng-deep page-purchasing-options-modal-price-input ui-form-control .input-wrapper input {
@apply w-20;
}

View File

@@ -1,55 +0,0 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { DomainOmsService } from '@domain/oms';
import { VATType } from '@swagger/checkout';
import { VATDTO } from '@swagger/oms';
import { Observable, Subscription } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
@Component({
selector: 'page-purchasing-options-modal-price-input',
templateUrl: 'purchasing-options-modal-price-input.component.html',
styleUrls: ['purchasing-options-modal-price-input.component.scss'],
providers: [UntypedFormBuilder],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PurchasingOptionsModalPriceInputComponent implements OnInit {
control: UntypedFormGroup;
vats$: Observable<VATDTO[]> = this._omsService.getVATs().pipe(shareReplay());
@Output()
priceChanged = new EventEmitter<number>();
@Output()
vatChanged = new EventEmitter<VATType>();
private _subscriptions = new Subscription();
@Input()
hideVat = false;
@Input()
maxValue = 99999;
constructor(private _omsService: DomainOmsService, private _fb: UntypedFormBuilder) {}
ngOnInit() {
this.initForm();
}
initForm() {
const fb = this._fb;
this.control = fb.group({
price: fb.control(undefined, [Validators.required, Validators.pattern(/^\d+([\,]\d{1,2})?$/), Validators.max(this.maxValue)]),
vat: fb.control(0, [Validators.required]),
});
this._subscriptions.add(
this.control.get('price').valueChanges.subscribe((price) => this.priceChanged.emit(Number(String(price).replace(',', '.'))))
);
this._subscriptions.add(this.control.get('vat').valueChanges.subscribe(this.vatChanged));
this.control.markAllAsTouched();
}
}

View File

@@ -1,16 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { UiSelectModule } from '@ui/select';
import { PurchasingOptionsModalPriceInputComponent } from './purchasing-options-modal-price-input.component';
@NgModule({
imports: [CommonModule, UiFormControlModule, UiInputModule, UiSelectModule, FormsModule, ReactiveFormsModule],
exports: [PurchasingOptionsModalPriceInputComponent],
declarations: [PurchasingOptionsModalPriceInputComponent],
providers: [],
})
export class PurchasingOptionsModalPriceInputModule {}

View File

@@ -1,140 +0,0 @@
<ng-container *ngIf="(hasOption$ | async) === false">
<h3 class="modal-title">Wie möchten Sie den Artikel erhalten?</h3>
<div class="options-wrapper">
<ng-container *ngFor="let option of availableOptions$ | async" [ngSwitch]="option">
<page-take-away-option *ngSwitchCase="'take-away'"></page-take-away-option>
<page-pick-up-option *ngSwitchCase="'pick-up'"></page-pick-up-option>
<page-delivery-option *ngSwitchCase="'delivery'"></page-delivery-option>
<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>
<ng-container *ngIf="hasOption$ | async">
<h3 class="modal-title">Artikel dem Warenkorb hinzufügen</h3>
<div class="option-product-summary" *ngIf="item$ | async; let item">
<div class="header-row">
<h5 class="option-name" *ngIf="option$ | async; let option">
<ng-container *ngIf="option | purchaseOptionIcon; let icon">
<ui-icon [size]="icon === 'truck_b2b' ? '40px' : '23px'" [icon]="icon"></ui-icon>
</ng-container>
{{ option | purchaseOptionName }}
</h5>
<span *ngIf="(option$ | async) !== 'download'">
in der Filiale:
<span class="option-branch">{{ (branch$ | async)?.name }}</span>
</span>
</div>
<hr />
<div class="product-row">
<img class="thumbnail" [src]="(item?.imageId !== undefined ? item?.imageId : item?.product?.ean) | productImage: 80:100:true" />
<div class="details">
<h6 class="title">{{ item?.product?.contributors }} - {{ item?.product?.name }}</h6>
<strong class="can-add-error" *ngIf="canAddError$ | async; let canAddError">{{ canAddError }}</strong>
<div class="grow"></div>
<div class="format" *ngIf="item?.product?.format && item?.product?.formatDetail">
<img
*ngIf="item?.product?.format !== '--'"
src="assets/images/Icon_{{ item?.product?.format }}.svg"
[alt]="item?.product?.formatDetail"
/>
{{ item?.product?.formatDetail }}
</div>
<div class="price">
{{ 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 {{ (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">
<div class="row">
<ui-quantity-dropdown
#quantityControl
[showSpinner]="purchasingOptionsModalStore.selectFetchingAvailability | async"
[ngModel]="quantity$ | async"
[range]="quantityRange$ | async"
(ngModelChange)="changeQuantity($event)"
>
</ui-quantity-dropdown>
<button *ngIf="!quantityControl?.customInput" (click)="backToSetOptions()" class="cta-modify">Ändern</button>
</div>
<div class="quantity-error" *ngIf="quantityError$ | async; let message">{{ message }}</div>
</div>
</div>
<div class="custom-price" *ngIf="showCustomPrice$ | async">
<page-purchasing-options-modal-price-input
[hideVat]="isGiftCard(item.type)"
[maxValue]="isGiftCard(item.type) ? 200 : 99999"
(priceChanged)="changeCustomPrice($event)"
(vatChanged)="changeCustomVat($event)"
>
</page-purchasing-options-modal-price-input>
</div>
<hr />
<div class="summary-row" *ngIf="quantity$ | async; let quantity">
<div class="reading-points">
{{ quantity }} Artikel
<ng-container *ngIf="promoPoints$ | async; let promoPoints"> | {{ promoPoints }} Lesepunkte </ng-container>
</div>
<div class="subtotal">
Zwischensumme
{{ (price$ | async) * quantity | currency: item?.catalogAvailability?.price?.value?.currency || 'EUR':'code' }}
<div class="shipping-cost" *ngIf="showDeliveryInfo$ | async">
ohne Versandkosten
</div>
</div>
</div>
</div>
<div class="actions" *ngIf="option$ | async; let option">
<button
*ngIf="canContinueShopping$ | async"
class="cta-continue-shopping"
[disabled]="(fetching$ | async) || (canContinueShopping$ | async) === false || (customPriceInvalid$ | async) === true"
(click)="continue('continue-shopping')"
>
Weiter einkaufen
</button>
<button *ngIf="canUpgrade$ | async" class="cta-upgrade-customer" (click)="continue('add-customer-data')">
Kundendaten erfassen
</button>
<button
*ngIf="showTakeAwayButton$ | async"
[disabled]="(fetching$ | async) || (canAdd$ | async) === false || (customPriceInvalid$ | async) === true"
class="cta-continue"
(click)="continue()"
>
Reservieren
</button>
<button
*ngIf="showDefaultContinueButton$ | async"
class="cta-continue"
(click)="continue()"
[disabled]="(fetching$ | async) || (canAdd$ | async) === false || (customPriceInvalid$ | async) === true"
>
Fortfahren
</button>
<button *ngIf="showContinueWithoutAdding$ | async" class="cta-continue" (click)="continue()">
Ohne Artikel Fortfahren
</button>
</div>
</ng-container>

View File

@@ -1,124 +0,0 @@
:host {
@apply block box-border;
}
.modal-title {
@apply text-center mt-2 text-xl font-bold;
}
.cta-modify {
@apply self-end bg-transparent text-brand font-bold text-lg outline-none border-none ml-4;
}
.options-wrapper {
@apply flex flex-row justify-evenly items-stretch mt-2;
}
.option-product-summary {
@apply flex flex-col box-border;
hr {
@apply my-4;
}
}
.option-name {
@apply flex flex-row items-center font-bold text-card-sub mb-2 mt-1;
ui-icon {
@apply mr-2 text-ucla-blue;
}
}
.option-branch {
@apply font-bold;
}
.product-row {
@apply flex flex-row items-center;
}
.summary-row {
@apply flex flex-row justify-between font-bold;
}
.reading-points {
@apply text-ucla-blue;
}
.subtotal {
@apply text-lg;
}
.shipping-cost {
@apply text-sm text-right font-normal;
}
img.thumbnail {
height: 100px;
}
.grow {
@apply flex-grow;
}
.details {
@apply ml-4 flex flex-col font-bold self-stretch flex-grow;
.title {
@apply text-base m-0;
}
}
.format {
@apply flex flex-row items-center whitespace-nowrap;
img {
@apply mr-2;
}
}
.quantity {
@apply self-end flex flex-col justify-end;
.row {
@apply flex flex-row justify-end;
}
}
.actions {
@apply flex flex-row justify-end items-center mt-8;
}
.cta-continue-shopping {
@apply text-brand border-2 border-solid border-brand bg-white font-bold text-lg px-4 py-2 rounded-full;
::ng-deep.spin {
@apply text-brand;
}
&:disabled {
@apply text-inactive-branch border-inactive-branch cursor-not-allowed;
}
}
.cta-continue,
.cta-upgrade-customer {
@apply text-white bg-brand font-bold text-lg px-4 py-2 rounded-full border-none ml-4 no-underline;
&:disabled {
@apply bg-inactive-branch cursor-not-allowed;
}
}
.can-add-error {
@apply text-xl text-dark-goldenrod mt-2;
}
.quantity-error {
@apply text-dark-goldenrod font-bold text-sm mt-2;
}
.hint {
@apply text-dark-goldenrod font-bold text-xl;
}

View File

@@ -1,396 +0,0 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { AddToShoppingCartDTO, AvailabilityDTO, ItemType, VATType } from '@swagger/checkout';
import { UiModalRef } from '@ui/modal';
import { shareReplay, debounceTime, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { combineLatest, Observable } from 'rxjs';
import { PurchasingOptionsModalData } from './purchasing-options-modal.data';
import { PurchasingOptionsModalStore } from './purchasing-options-modal.store';
import { DomainCatalogService } from '@domain/catalog';
import { BreadcrumbService } from '@core/breadcrumb';
import {
encodeFormData,
mapCustomerDtoToCustomerCreateFormData,
} from 'apps/page/customer/src/lib/create-customer/customer-create-form-data';
import { isNumber } from '@utils/common';
@Component({
selector: 'page-purchasing-options-modal',
templateUrl: 'purchasing-options-modal.component.html',
styleUrls: ['purchasing-options-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [PurchasingOptionsModalStore],
})
export class PurchasingOptionsModalComponent {
readonly item$ = this.purchasingOptionsModalStore.selectItem;
readonly availableOptions$ = this.purchasingOptionsModalStore.selectAvailableOptions.pipe(shareReplay());
readonly option$ = this.purchasingOptionsModalStore.selectOption;
readonly hasOption$ = this.purchasingOptionsModalStore.selectHasOption;
readonly quantity$ = this.purchasingOptionsModalStore.selectQuantity;
readonly canAdd$ = this.purchasingOptionsModalStore.selectCanAdd;
readonly canAddError$ = this.purchasingOptionsModalStore.selectCanAddError;
readonly canUpgrade$ = this.purchasingOptionsModalStore.selectCanUpgrade;
readonly availability$ = this.purchasingOptionsModalStore.selectAvailability;
readonly branch$ = this.purchasingOptionsModalStore.selectBranch;
readonly canContinueShopping$ = this.purchasingOptionsModalStore.selectCanContinueShopping;
readonly canContinueShoppingIsLoading$ = this.purchasingOptionsModalStore.selectCanContinueShoppingIsLoading;
readonly quantityError$ = this.purchasingOptionsModalStore.selectQuantityError;
readonly showCustomPrice$ = this.purchasingOptionsModalStore.selectAvailabilities.pipe(
withLatestFrom(this.option$),
map(([availabilities, option]) => !availabilities[option]?.price?.value?.value)
);
readonly customPriceInvalid$ = combineLatest([
this.item$,
this.showCustomPrice$,
this.purchasingOptionsModalStore.selectCustomPrice,
this.purchasingOptionsModalStore.selectCustomVat,
]).pipe(
map(([item, showCustomPrice, customPrice, customVat]) => {
if (!showCustomPrice) {
return false;
}
if ((item.type as any) === 66560) {
return !isNumber(customPrice) || customPrice < 1 || customPrice > 200;
}
return !customPrice || !customVat;
})
);
readonly showTakeAwayButton$ = combineLatest([
this.option$,
this.purchasingOptionsModalStore.selectFetchingAvailability,
this.purchasingOptionsModalStore.selectCheckingCanAdd,
this.canAdd$,
this.canUpgrade$,
]).pipe(
map(([option, fetchingAvailability, checkingCanAdd, canAdd, canUpgrade]) => {
if (option !== 'take-away') {
return false;
}
if (!fetchingAvailability && !checkingCanAdd && !canAdd) {
return false;
}
return !canUpgrade;
})
);
readonly showDefaultContinueButton$ = combineLatest([
this.option$,
this.purchasingOptionsModalStore.selectFetchingAvailability,
this.purchasingOptionsModalStore.selectCheckingCanAdd,
this.canAdd$,
this.canUpgrade$,
]).pipe(
map(([option, fetchingAvailability, checkingCanAdd, canAdd, canUpgrade]) => {
if (option === 'take-away') {
return false;
}
if (!fetchingAvailability && !checkingCanAdd && !canAdd) {
return false;
}
return !canUpgrade;
})
);
readonly showContinueWithoutAdding$ = combineLatest([this.showTakeAwayButton$, this.showDefaultContinueButton$, this.canUpgrade$]).pipe(
map(([showTakeAway, showDefault, canUpgrade]) => !canUpgrade && !(showTakeAway || showDefault))
);
readonly showDeliveryInfo$ = this.option$.pipe(map((option) => ['delivery', 'b2b-delivery', 'dig-delivery'].indexOf(option) > -1));
readonly fetching$ = combineLatest([
this.purchasingOptionsModalStore.selectFetchingAvailability,
this.purchasingOptionsModalStore.selectCheckingCanAdd,
]).pipe(map(([fetching, checking]) => fetching || checking));
customerFeatures$ = this.application.activatedProcessId$.pipe(
switchMap((processId) => this.checkoutService.getCustomerFeatures({ processId }))
);
readonly customer$ = this.application.activatedProcessId$.pipe(switchMap((processId) => this.checkoutService.getCustomer({ processId })));
price$ = combineLatest([
this.purchasingOptionsModalStore.selectAvailabilities,
this.option$,
this.purchasingOptionsModalStore.selectCustomPrice,
]).pipe(
map(([availabilities, option, customPrice]) => {
if (option && !!availabilities[option]) {
if (availabilities[option]?.price?.value?.value) {
return availabilities[option]?.price?.value?.value;
}
return availabilities[option]?.price?.value?.value ?? customPrice;
} else {
const key = Object.keys(availabilities).find((key) => !!availabilities[key]?.price?.value?.value);
return availabilities[key]?.price?.value?.value ?? customPrice;
}
})
);
vat$ = combineLatest([
this.purchasingOptionsModalStore.selectAvailabilities,
this.option$,
this.purchasingOptionsModalStore.selectCustomVat,
]).pipe(
map(([availabilities, option, customVat]) => {
if (option && !!availabilities[option]) {
if (availabilities[option]?.price?.vat?.vatType) {
return availabilities[option]?.price?.vat?.vatType;
}
return availabilities[option]?.price?.vat?.vatType ?? customVat;
} else {
const key = Object.keys(availabilities).find((key) => !!availabilities[key]?.price?.vat?.vatType);
return availabilities[key]?.price?.vat?.vatType ?? customVat;
}
})
);
readonly promoPoints$ = combineLatest([this.item$, this.quantity$, this.price$]).pipe(
debounceTime(250),
switchMap(([item, quantity, price]) =>
this.domainCatalogService
.getPromotionPoints({
items: [
{
id: item.id,
quantity: quantity,
price: price,
},
],
})
.pipe(map((res) => res.result[item.id]))
)
);
quantityRange$ = combineLatest([this.option$, this.availability$]).pipe(
map(([option, availability]) => (option === 'take-away' && availability?.inStock ? availability.inStock : 999))
);
activeSpinner: string;
constructor(
public modalRef: UiModalRef<any, PurchasingOptionsModalData>,
public purchasingOptionsModalStore: PurchasingOptionsModalStore,
private application: ApplicationService,
private router: Router,
private checkoutService: DomainCheckoutService,
private domainCatalogService: DomainCatalogService,
private breadcrumb: BreadcrumbService
) {
this.purchasingOptionsModalStore.setShoppingCartItem(this.modalRef.data.shoppingCartItem);
this.purchasingOptionsModalStore.setItem(this.modalRef.data.item);
this.purchasingOptionsModalStore.setProcessId(this.modalRef.data.processId || this.application.activatedProcessId);
this.purchasingOptionsModalStore.setAvailabilities(this.modalRef.data.availabilities || {});
this.purchasingOptionsModalStore.setQuantity(this.modalRef?.data?.shoppingCartItem?.quantity || 1);
this.purchasingOptionsModalStore.setOption(this.modalRef.data.option);
this.purchasingOptionsModalStore.setAvailableOptions(this.modalRef.data.availableOptions);
if (
this.modalRef.data.availableOptions?.some((option) => option === 'pick-up' || option === 'take-away') ||
['take-away', 'pick-up'].includes(this.modalRef.data.option)
) {
this.purchasingOptionsModalStore.loadBranches(this.modalRef?.data?.branchId);
}
}
changeCustomVat(vat: VATType) {
this.purchasingOptionsModalStore.setCustomVat(vat);
}
changeCustomPrice(price: number) {
this.purchasingOptionsModalStore.setCustomPrice(price);
}
backToSetOptions() {
this.purchasingOptionsModalStore.setOption(undefined);
}
getAvailability(option: string): Observable<AvailabilityDTO> {
return this.purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava[option]));
}
async changeQuantity(quantity: number = 1) {
this.purchasingOptionsModalStore.setQuantity(quantity);
if (quantity === 0) {
this.modalRef.close();
}
}
async continue(navigate: 'continue' | 'continue-shopping' | 'add-customer-data' = 'continue') {
this.activeSpinner = navigate ? 'continue-shopping' : 'continue';
try {
const processId = await this.purchasingOptionsModalStore.selectProcessId.pipe(first()).toPromise();
const buyer = await this.checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
const item = await this.item$.pipe(first()).toPromise();
const quantity = await this.quantity$.pipe(first()).toPromise();
const availability = await this.availability$.pipe(first()).toPromise();
const option = await this.option$.pipe(first()).toPromise();
const branch = await this.branch$.pipe(first()).toPromise();
const shoppingCartItem = await this.purchasingOptionsModalStore.selectShoppingCartItem.pipe(first()).toPromise();
const canAdd = await this.canAdd$.pipe(first()).toPromise();
const customPrice = await this.purchasingOptionsModalStore.selectCustomPrice.pipe(first()).toPromise();
const customVat = (await this.purchasingOptionsModalStore.selectCustomVat.pipe(first()).toPromise()) ?? 0;
const customer = await this.checkoutService.getCustomer({ processId }).pipe(first()).toPromise();
if (canAdd || navigate === 'add-customer-data') {
const newItem: AddToShoppingCartDTO = {
quantity,
availability,
product: {
catalogProductNumber: '',
...item.product,
},
promotion: { points: item.promoPoints },
itemType: item.type,
};
newItem.product.catalogProductNumber = String(item.id);
if (!!customPrice && !!customVat) {
newItem.availability.price = {
value: {
value: customPrice,
currency: 'EUR',
},
vat: {
vatType: customVat,
},
};
} else {
const price = await this.price$.pipe(first()).toPromise();
const vat = await this.vat$.pipe(first()).toPromise();
newItem.availability.price = {
value: {
value: price,
currency: 'EUR',
},
vat: {
vatType: vat,
},
};
}
switch (option) {
case 'take-away':
case 'pick-up':
newItem.destination = {
data: { target: 1, targetBranch: { id: branch.id } },
};
break;
case 'delivery':
case 'dig-delivery':
case 'b2b-delivery':
newItem.destination = {
data: { target: 2, logistician: availability.logistician },
};
break;
case 'download':
newItem.destination = {
data: { target: 16, logistician: availability.logistician },
};
break;
}
if (shoppingCartItem) {
await this.checkoutService
.updateItemInShoppingCart({
processId,
shoppingCartItemId: shoppingCartItem?.id,
update: {
availability: newItem.availability,
quantity: newItem.quantity,
destination: newItem.destination,
},
})
.toPromise();
} else {
await this.checkoutService
.addItemToShoppingCart({
processId,
items: [newItem],
})
.toPromise();
}
}
this.modalRef.close();
if (shoppingCartItem) {
return;
}
if (navigate === 'continue-shopping') {
const crumbs = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'filter', 'results'])
.pipe(first())
.toPromise();
if (!!crumbs && crumbs.length > 0) {
const queryParams = crumbs[0].params;
this.router.navigate(['/kunde', this.application.activatedProcessId, 'product', 'search', 'results'], { queryParams });
} else {
// Route back to search if no result page was loaded (f.e. When searching Article and landing directly on details page)
this.router.navigate(['/kunde', this.application.activatedProcessId, 'product', 'search']);
}
} else if (navigate === 'continue') {
// Set filter for navigation to customer search if customer is not set
let filter: { [key: string]: string };
if (!buyer) {
filter = await this.customerFeatures$
.pipe(
first(),
switchMap((customerFeatures) => {
return this.checkoutService.canSetCustomer({ processId, customerFeatures });
}),
map((res) => res.filter)
)
.toPromise();
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', 'search'], {
queryParams: { filter_customertype: filter.customertype },
});
} else {
this.router.navigate(['/kunde', this.application.activatedProcessId, 'cart', 'review']);
}
} else if (navigate === 'add-customer-data') {
if (customer?.attributes.some((attr) => attr.data.key === 'p4mUser')) {
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', 'create', 'webshop-p4m'], {
queryParams: { formData: encodeFormData(mapCustomerDtoToCustomerCreateFormData(customer)) },
});
} else {
this.router.navigate(['/kunde', this.application.activatedProcessId, 'customer', 'create', 'webshop'], {
queryParams: { formData: encodeFormData(mapCustomerDtoToCustomerCreateFormData(customer)) },
});
}
}
} catch (error) {
console.log('PurchasingOptionsModalComponent.continue', error);
}
this.activeSpinner = undefined;
}
isGiftCard(itemType: ItemType): boolean {
return (itemType as any) === 66560;
}
}

View File

@@ -1,13 +0,0 @@
import { ItemDTO } from '@swagger/cat';
import { AvailabilityDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { PurchasingOptions } from './purchasing-options-modal.store';
export interface PurchasingOptionsModalData {
item: ItemDTO;
availableOptions: PurchasingOptions[];
processId?: number;
option?: PurchasingOptions;
shoppingCartItem?: ShoppingCartItemDTO;
availabilities?: { [key: string]: AvailabilityDTO };
branchId?: number;
}

View File

@@ -1,55 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { OverlayModule } from '@angular/cdk/overlay';
import { UiIconModule } from '@ui/icon';
import {
B2BDeliveryOptionComponent,
TakeAwayOptionComponent,
PickUpOptionComponent,
DeliveryOptionComponent,
DigDeliveryOptionComponent,
} from './options';
import { PurchasingOptionsModalComponent } from './purchasing-options-modal.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';
import { KeyNavigationModule } from '../../shared/key-navigation/key-navigation.module';
import { RouterModule } from '@angular/router';
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: [
CommonModule,
UiCommonModule,
FormsModule,
UiIconModule,
OverlayModule,
PageCheckoutPipeModule,
ProductImageModule,
UiQuantityDropdownModule,
UiSpinnerModule,
KeyNavigationModule,
RouterModule,
PurchasingOptionsModalPriceInputModule,
UiTooltipModule,
UiBranchDropdownModule,
],
exports: [PurchasingOptionsModalComponent],
declarations: [
PurchasingOptionsModalComponent,
B2BDeliveryOptionComponent,
TakeAwayOptionComponent,
PickUpOptionComponent,
DeliveryOptionComponent,
DigDeliveryOptionComponent,
],
})
export class PurchasingOptionsModalModule {}

View File

@@ -1,498 +0,0 @@
import { Injectable } from '@angular/core';
import { ApplicationService } from '@core/application';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCheckoutService } from '@domain/checkout';
import { CrmCustomerService } from '@domain/crm';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ItemDTO } from '@swagger/cat';
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, ShoppingCartItemDTO, VATType } from '@swagger/checkout';
import { isBoolean, isNullOrUndefined, isString } from '@utils/common';
import { NEVER, Observable } from 'rxjs';
import { delay, filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
export type PurchasingOptions = 'take-away' | 'pick-up' | 'delivery' | 'dig-delivery' | 'b2b-delivery' | 'download';
interface PurchasingOptionsModalState {
item?: ItemDTO;
shoppingCartItem?: ShoppingCartItemDTO;
option?: PurchasingOptions;
defaultBranch?: BranchDTO;
branch?: BranchDTO;
processId?: number;
fetchingAvailability: boolean;
availableOptions: PurchasingOptions[];
availableBranches: BranchDTO[];
quantity: number;
maxQuantityError: boolean;
checkingCanAdd: boolean;
canAdd: boolean;
canAddError?: string;
canUpgrade: boolean;
availabilities: { [key: string]: AvailabilityDTO };
customPrice?: number;
customVat?: VATType;
}
@Injectable()
export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOptionsModalState> {
readonly selectItem = this.select((s) => s.item);
readonly selectShoppingCartItem = this.select((s) => s.shoppingCartItem);
readonly selectOption = this.select((s) => s.option);
readonly selectOrderType = this.select(this.selectOption, (option) => {
switch (option) {
case 'take-away':
return 'Rücklage';
case 'pick-up':
return 'Abholung';
case 'delivery':
return 'Versand';
case 'b2b-delivery':
return 'B2B-Versand';
case 'dig-delivery':
return 'DIG-Versand';
case 'download':
return 'Download';
}
});
readonly selectHasOption = this.select((s) => !!s.option);
readonly selectBranch = this.select((s) => {
return s.branch || s.defaultBranch;
});
readonly selectQuantity = this.select((s) => s.quantity);
readonly selectCustomPrice = this.select((s) => s.customPrice);
readonly selectCustomVat = this.select((s) => s.customVat);
readonly selectAvailabilities = this.select((s) => s.availabilities);
readonly selectAvailability = this.select((s) => s.availabilities[s.option]);
readonly selectAvailabilityIsValid = this.select(this.selectAvailability, (availability) =>
this.availabilityService.isAvailable({ availability })
);
readonly selectAvailableOptions = this.select((s) => s.availableOptions);
readonly selectAvailableBranches = this.select((s) => s.availableBranches);
readonly selectProcessId = this.select((s) => s.processId);
readonly selectCheckingCanAdd = this.select((s) => s.checkingCanAdd);
readonly selectCanAdd = this.select(
this.selectAvailability,
this.select((s) => s.canAdd),
(availability, canAdd) => canAdd && this.availabilityService.isAvailable({ availability })
);
readonly selectCanAddError = this.select((s) => s.canAddError);
readonly selectFetchingAvailability = this.select((s) => s.fetchingAvailability);
readonly selectMaxQuantityError = this.select((s) => s.maxQuantityError);
readonly selectQuantityError = this.select(
this.selectQuantity,
this.selectOption,
this.selectAvailability,
this.selectFetchingAvailability,
this.selectMaxQuantityError,
(quantity, option, availability, fetching, maxQuantityError) => {
if (!fetching) {
if (maxQuantityError) {
return `Achtung, Maximal 999 Exemplare bestellbar.`;
}
if (availability?.inStock < quantity) {
if (option === 'pick-up') {
return `${availability?.inStock} Exemplare sofort lieferbar.`;
}
if (option === 'take-away') {
return `${availability?.inStock} Exemplare sofort lieferbar.`;
}
}
if (!this.availabilityService.isAvailable({ availability })) {
return availability?.sscText;
}
}
return undefined;
}
);
readonly selectOlaAvailability = this.select(
(s): OLAAvailabilityDTO =>
this.availabilityService.mapToOlaAvailability({
availability: s.availabilities[s.option],
item: s.item,
quantity: s.quantity,
})
);
readonly selectCanUpgrade = this.select((s) => s.canUpgrade);
readonly selectCanContinueShopping = this.select(
this.selectFetchingAvailability,
this.selectCanAdd,
this.selectOption,
this.selectQuantityError,
this.selectQuantity,
this.selectCanUpgrade,
(fetching, canAdd, option, quantityError, quantity, canUpgrade) => {
let hasError = !!quantityError;
if (option === 'pick-up' && quantity <= 999) {
hasError = false;
}
return !fetching && (canAdd || canUpgrade);
}
).pipe(delay(1));
readonly selectCanContinueShoppingIsLoading = this.select(this.selectFetchingAvailability, this.selectCanAdd, (fetching, canAdd) => {
return fetching;
});
constructor(
private checkoutService: DomainCheckoutService,
private availabilityService: DomainAvailabilityService,
private customerService: CrmCustomerService,
private applicationService: ApplicationService
) {
super({
availableBranches: [],
availableOptions: [],
quantity: 1,
canAdd: false,
canUpgrade: false,
availabilities: {},
checkingCanAdd: false,
fetchingAvailability: false,
maxQuantityError: false,
});
this.loadDefaultBranch();
}
readonly setItem = this.updater((state, item: ItemDTO) => {
this.loadAvailability();
return {
...state,
item,
availability: undefined,
canAdd: false,
canAddError: undefined,
};
});
readonly setShoppingCartItem = this.updater((state, shoppingCartItem: ShoppingCartItemDTO) => {
this.loadAvailability();
return {
...state,
shoppingCartItem,
};
});
readonly setOption = this.updater((state, option: PurchasingOptions) => {
this.loadAvailability();
return {
...state,
option,
availability: undefined,
canAdd: false,
canAddError: undefined,
};
});
readonly setBranch = this.updater((state, branch: BranchDTO) => {
this.loadAvailability();
return {
...state,
branch,
availability: undefined,
canAdd: false,
canAddError: undefined,
};
});
readonly setBranchId = this.updater((state, branchId: number) => {
const branch = state.availableBranches.find((branch) => branch.id === branchId);
this.loadAvailability();
return {
...state,
branchId,
branch,
};
});
readonly setAvailability = this.updater(
(state, { availability, option, item }: { availability: AvailabilityDTO; option: PurchasingOptions; item: ItemDTO }) => {
this.checkCanAdd();
let updatedAvailability = availability;
if ((option && option === 'delivery') || option === 'dig-delivery') {
const catalogPrice = item?.catalogAvailability?.price?.value?.value;
const availabilityPrice = availability?.price?.value?.value;
const updatedPrice = catalogPrice <= availabilityPrice ? catalogPrice : availabilityPrice;
updatedAvailability = {
...availability,
price: {
...availability.price,
value: {
...availability.price.value,
value: updatedPrice,
},
},
};
}
return {
...state,
availabilities: {
...state.availabilities,
[option]: updatedAvailability,
},
};
}
);
readonly setQuantity = this.updater((state, quantity: number = 1) => {
let qty = quantity;
if (quantity > 999) {
qty = 999;
this.patchState({ maxQuantityError: true });
} else {
this.patchState({ maxQuantityError: false });
}
this.loadAvailability();
return {
...state,
quantity: qty,
canAdd: false,
canAddError: undefined,
};
});
readonly setCustomPrice = this.updater((state, customPrice: number) => {
return {
...state,
customPrice,
};
});
readonly setCustomVat = this.updater((state, customVat: VATType) => {
return {
...state,
customVat,
};
});
readonly setAvailableOptions = this.updater((state, availableOptions: PurchasingOptions[]) => {
let option = state.option;
if (availableOptions?.length === 1 && availableOptions[0] === 'download') {
option = availableOptions[0];
}
return {
...state,
availableOptions,
option,
};
});
readonly setAvailableBranches = this.updater((state, availableBranches: BranchDTO[]) => {
const branch = state.branch || state.defaultBranch;
return {
...state,
availableBranches,
branch,
};
});
readonly setProcessId = this.updater((state, processId: number) => ({
...state,
processId,
}));
readonly setCanAdd = this.updater((state, canAddItem: true | string) => {
let canAdd = isBoolean(canAddItem) ? Boolean(canAddItem) : false;
if (!canAdd) {
this.checkCanUpgrade();
}
return { ...state, canAdd, canAddError: isString(canAddItem) ? String(canAddItem) : undefined };
});
readonly setAvailabilities = this.updater((state, availabilities: { [key: string]: AvailabilityDTO }) => {
this.checkCanAdd();
return {
...state,
availabilities: {
...state.availabilities,
...availabilities,
},
};
});
loadBranches = this.effect((branchId$: Observable<number>) =>
branchId$.pipe(
switchMap((branchId) =>
this.checkoutService.getBranches().pipe(
tapResponse(
(branches: BranchDTO[]) => {
this.setAvailableBranches(branches);
this.setBranchId(branchId);
},
() => this.setAvailableBranches([])
)
)
)
)
);
loadAvailability = this.effect(($) =>
$.pipe(
delay(10),
withLatestFrom(this.selectItem, this.selectQuantity, this.selectOption, this.selectBranch),
switchMap(([_, item, quantity, option, branch]) => {
let availability$: Observable<AvailabilityDTO> = NEVER;
if (!isNullOrUndefined(item) && quantity > 0 && isString(option)) {
this.patchState({ fetchingAvailability: true });
switch (option) {
case 'take-away':
availability$ = this.availabilityService.getTakeAwayAvailabilityByBranch({
itemId: item.id,
price: item.catalogAvailability.price,
quantity,
branch,
});
break;
case 'pick-up':
if (!isNullOrUndefined(branch)) {
availability$ = this.availabilityService
.getPickUpAvailability({
item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
quantity,
branch,
})
.pipe(
map((av) => {
if (av?.length > 0) {
if (av[1].availableFor) {
if ((av[1].availableFor & 2) === 2) {
return av[0];
} else {
undefined;
}
} else {
return av[0];
}
}
})
);
}
break;
case 'delivery':
availability$ = this.availabilityService.getDeliveryAvailability({
item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
quantity,
});
break;
case 'dig-delivery':
availability$ = this.availabilityService.getDigDeliveryAvailability({
item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
quantity,
});
break;
case 'b2b-delivery':
if (!isNullOrUndefined(branch)) {
availability$ = this.availabilityService.getB2bDeliveryAvailability({
item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
quantity,
});
}
break;
case 'download':
availability$ = this.availabilityService.getDownloadAvailability({
item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
});
break;
}
}
return availability$.pipe(
tapResponse(
(availability) => {
this.setAvailability({ option, availability, item });
this.patchState({ fetchingAvailability: false });
},
() => {
this.setAvailability(null);
this.patchState({ fetchingAvailability: false });
}
)
);
})
)
);
checkCanAdd = this.effect(($) =>
$.pipe(
delay(10),
withLatestFrom(this.selectOlaAvailability, this.selectProcessId, this.selectOrderType),
switchMap(([_, availability, processId, orderType]) => {
this.patchState({ checkingCanAdd: true });
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 }))
);
})
)
);
checkCanUpgrade = this.effect(($) =>
$.pipe(
withLatestFrom(this.applicationService.activatedProcessId$),
switchMap(([_, processId]) => this.checkoutService.getBuyer({ processId })),
map((buyer) => buyer?.source),
filter((customerId) => !isNaN(customerId)),
switchMap((customerId) =>
this.customerService.canUpgrade(customerId).pipe(
tapResponse(
(response) => {
let canUpgrade = response.options?.values?.some((u) => u.value === 'webshop');
this.patchState({ canUpgrade });
},
(error) => {
this.patchState({ canUpgrade: false });
}
)
)
)
)
);
readonly loadDefaultBranch = this.effect(($) =>
$.pipe(
switchMap((_) =>
this.availabilityService.getDefaultBranch().pipe(
tapResponse(
(defaultBranch) => this.patchState({ defaultBranch }),
(err) => {}
)
)
)
)
);
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './take-away-option.component';
// end:ng42.barrel

View File

@@ -1,18 +0,0 @@
<ng-container *ngIf="item$ | async; let item">
<ng-container *ngIf="availability$ | async; let availability">
<div class="option-icon">
<ui-icon size="50px" icon="shopping_bag"></ui-icon>
</div>
<h4>Rücklage / Filialentnahme</h4>
<p>
Möchten Sie den Artikel zurücklegen lassen oder sofort mitnehmen?
</p>
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
<div class="grow"></div>
<div>
<button [disabled]="availability.price?.value?.value < 0" type="button" class="select-option" (click)="select()">
Auswählen
</button>
</div>
</ng-container>
</ng-container>

View File

@@ -1,27 +0,0 @@
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';
@Component({
selector: 'page-take-away-option',
templateUrl: 'take-away-option.component.html',
styleUrls: ['../option.scss', 'take-away-option.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TakeAwayOptionComponent {
readonly item$ = this._purchasingOptionsModalStore.selectItem;
readonly availability$ = this._purchasingOptionsModalStore.selectAvailabilities.pipe(map((ava) => ava['take-away']));
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');
}
}

View File

@@ -1,10 +0,0 @@
import { NgModule } from '@angular/core';
import { PurchasingOptionsListModalModule } from './modals/purchasing-options-list-modal';
import { PurchasingOptionsModalModule } from './modals/purchasing-options-modal';
@NgModule({
imports: [PurchasingOptionsModalModule, PurchasingOptionsListModalModule],
exports: [PurchasingOptionsModalModule, PurchasingOptionsListModalModule],
})
export class PageCheckoutModalsModule {}

View File

@@ -4,14 +4,12 @@ import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { CheckoutDummyModule } from './checkout-dummy/checkout-dummy.module';
import { CheckoutReviewModule } from './checkout-review/checkout-review.module';
import { CheckoutSummaryModule } from './checkout-summary/checkout-summary.module';
import { PageCheckoutModalsModule } from './page-checkout-modals.module';
import { PageCheckoutRoutingModule } from './page-checkout-routing.module';
import { PageCheckoutComponent } from './page-checkout.component';
@NgModule({
imports: [
CommonModule,
PageCheckoutModalsModule,
CheckoutSummaryModule,
PageCheckoutRoutingModule,
CheckoutReviewModule,
@@ -19,6 +17,6 @@ import { PageCheckoutComponent } from './page-checkout.component';
ShellBreadcrumbModule,
],
declarations: [PageCheckoutComponent],
exports: [PageCheckoutModalsModule],
exports: [],
})
export class PageCheckoutModule {}

View File

@@ -1,9 +1,12 @@
<div class="goods-out-search-filter-content">
<button class="btn-close" type="button" (click)="close.emit()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
<div class="w-full flex flex-row justify-end items-center">
<button (click)="clearFilter(filter)" class="text-[#0556B4] mr-[0.8125rem]">Alle Filter entfernen</button>
<button class="text-cool-grey p-4 outline-none border-none bg-transparent" type="button" (click)="close.emit()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
</div>
<div class="goods-out-search-filter-content-main">
<div class="goods-out-search-filter-content-main -mt-8">
<h1 class="title">Filter</h1>
<ui-filter
[filter]="filter"

View File

@@ -6,10 +6,6 @@
@apply relative mx-auto;
}
.btn-close {
@apply absolute text-inactive-customer top-3 p-4 right-4 outline-none border-none bg-transparent;
}
.goods-out-search-filter-content-main {
@apply px-4;
h1.title {

View File

@@ -121,6 +121,10 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
this.updateBreadcrumb();
}
clearFilter(value: UiFilter) {
value.unselectAll();
}
getDetailsPath(item: OrderItemListItemDTO) {
return item?.compartmentCode
? `/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`

View File

@@ -25,18 +25,31 @@ export class OrderBranchIdInputComponent extends AbstractUiFilterInputDirective
ngOnInit() {
this.control.setValue({ id: Number(this.value) });
const uiInputChangesSub = this.uiInput?.changes?.subscribe((changes) => {
const controlValue = this.control?.value?.id;
const changesValue = Number(changes?.target?.value);
if (controlValue !== changesValue) {
this.control.setValue(changesValue && !isNaN(changesValue) ? { id: changesValue } : undefined);
}
});
const onInputChangeSub = this.onUiInputChange$.subscribe((input) => {
if (this.control.value !== input.value) {
this.control.setValue(input.value ? { id: Number(input.value) } : undefined);
const controlValue = this.control?.value?.id;
const inputValue = Number(input?.value);
if (controlValue !== inputValue) {
this.control.setValue(inputValue && !isNaN(inputValue) ? { id: inputValue } : undefined);
}
});
const onControlValueChangeSub = this.control.valueChanges.subscribe((value) => {
if (this.value !== value) {
this.setValue(value ? String(value?.id) : undefined);
if (!value) {
this.setValue(undefined);
} else if (this.value !== String(value?.id)) {
this.setValue(String(value?.id));
}
});
this._subscriptions.add(uiInputChangesSub);
this._subscriptions.add(onInputChangeSub);
this._subscriptions.add(onControlValueChangeSub);
}

View File

@@ -1,6 +1,7 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { BreadcrumbService } from '@core/breadcrumb';
import { BranchDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
@@ -24,7 +25,8 @@ export class CustomerOrderComponent implements OnInit {
public application: ApplicationService,
private _activatedRoute: ActivatedRoute,
private _uiModal: UiModalService,
private _breadcrumb: BreadcrumbService
private _breadcrumb: BreadcrumbService,
public auth: AuthService
) {}
ngOnInit(): void {

View File

@@ -39,6 +39,8 @@ import {
ReOrderedActionHandler,
CollectOnDeliveryNoteActionHandler,
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
} from '@domain/oms';
import { CoreCommandModule } from '@core/command';
import { CustomerOrderRoutingModule } from './customer-order-routing.module';
@@ -89,6 +91,8 @@ import { BreadcrumbModule } from '@shared/components/breadcrumb';
SupplierTemporarilyOutOfStockActionHandler,
CollectOnDeliveryNoteActionHandler,
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
]),
],
exports: [CustomerOrderComponent],

View File

@@ -1,29 +1,30 @@
<div class="customer-search-filter-content">
<button class="btn-close" type="button" (click)="close.emit()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
<div *ngIf="filter$ | async; let filter" class="customer-search-filter-content">
<div class="w-full flex flex-row justify-end items-center">
<button (click)="clearFilter(filter)" class="text-[#0556B4] mr-[0.8125rem]">Alle Filter entfernen</button>
<button class="text-cool-grey p-4 outline-none border-none bg-transparent" type="button" (click)="close.emit()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
</div>
<h1 class="text-3xl font-bold text-center py-4">Filter</h1>
<h1 class="text-3xl font-bold text-center py-4 -mt-8">Filter</h1>
<ng-container *ngIf="filter$ | async; let filter">
<ui-filter
[filter]="filter"
[loading]="store.fetching$ | async"
(search)="applyFilter(filter)"
[hint]="store.message$ | async"
resizeInputOptionsToElement="page-customer-search-filter .sticky-cta-wrapper"
[scanner]="true"
></ui-filter>
<ui-filter
[filter]="filter"
[loading]="store.fetching$ | async"
(search)="applyFilter(filter)"
[hint]="store.message$ | async"
resizeInputOptionsToElement="page-customer-search-filter .sticky-cta-wrapper"
[scanner]="true"
></ui-filter>
<div class="sticky-cta-wrapper">
<button class="cta-reset-filter" (click)="resetFilter()" [disabled]="store.fetching$ | async">
Filter zurücksetzen
</button>
<button class="apply-filter" (click)="applyFilter(filter)" [disabled]="store.fetching$ | async">
<ui-spinner [show]="store.fetching$ | async">
Filter anwenden
</ui-spinner>
</button>
</div>
</ng-container>
<div class="sticky-cta-wrapper">
<button class="cta-reset-filter" (click)="resetFilter()" [disabled]="store.fetching$ | async">
Filter zurücksetzen
</button>
<button class="apply-filter" (click)="applyFilter(filter)" [disabled]="store.fetching$ | async">
<ui-spinner [show]="store.fetching$ | async">
Filter anwenden
</ui-spinner>
</button>
</div>
</div>

View File

@@ -6,10 +6,6 @@
@apply relative mx-auto p-4;
}
.btn-close {
@apply absolute text-cool-grey top-3 p-4 right-4 outline-none border-none bg-transparent;
}
.sticky-cta-wrapper {
@apply fixed text-center inset-x-0 bottom-0;
bottom: 30px;

View File

@@ -45,6 +45,10 @@ export class CustomerSearchFilterComponent implements OnInit, OnDestroy {
});
}
clearFilter(value: UiFilter) {
value.unselectAll();
}
resetFilter() {
this.store.setDefaultFilter();
}

View File

@@ -1,11 +1,14 @@
<ui-checkbox
*ngIf="customerType !== 'b2b'"
[ngModel]="p4mUser"
(ngModelChange)="setValue({ p4mUser: !p4mUser })"
[disabled]="p4mReadonly || readonly"
>
Kundenkarte
</ui-checkbox>
<ng-container *ifRole="'Store'">
<ui-checkbox
*ngIf="customerType !== 'b2b'"
[ngModel]="p4mUser"
(ngModelChange)="setValue({ p4mUser: !p4mUser })"
[disabled]="p4mReadonly || readonly"
>
Kundenkarte
</ui-checkbox>
</ng-container>
<ng-container *ngFor="let option of filteredOptions$ | async">
<ui-checkbox
*ngIf="option?.enabled !== false"

View File

@@ -4,9 +4,10 @@ import { CommonModule } from '@angular/common';
import { CustomerTypeSelectorComponent } from './customer-type-selector.component';
import { FormsModule } from '@angular/forms';
import { UiCheckboxModule } from '@ui/checkbox';
import { AuthModule } from '@core/auth';
@NgModule({
imports: [CommonModule, FormsModule, UiCheckboxModule],
imports: [CommonModule, FormsModule, UiCheckboxModule, AuthModule],
exports: [CustomerTypeSelectorComponent],
declarations: [CustomerTypeSelectorComponent],
})

Some files were not shown because too many files have changed in this diff Show More