Files
ISA-Frontend/apps/isa-app/src/page/checkout/checkout-summary/checkout-summary.component.ts
2025-06-16 11:54:47 +02:00

498 lines
15 KiB
TypeScript

import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Injector,
OnDestroy,
OnInit,
inject,
} from '@angular/core';
import { DomainCheckoutService } from '@domain/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { first, map, shareReplay, switchMap } from 'rxjs/operators';
import { CrmCustomerService } from '@domain/crm';
import { ActivatedRoute, Router } from '@angular/router';
import { DomainOmsService } from '@domain/oms';
import { DomainCatalogService } from '@domain/catalog';
import {
DisplayOrderDTO,
DisplayOrderItemDTO,
DisplayOrderItemSubsetDTO,
} from '@generated/swagger/oms-api';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
import { BehaviorSubject, combineLatest, NEVER, Subject } from 'rxjs';
import { DateAdapter } from '@ui/common';
import {
CheckoutNavigationService,
PickUpShelfOutNavigationService,
ProductCatalogNavigationService,
} from '@shared/services/navigation';
import { EnvironmentService } from '@core/environment';
import { SendOrderConfirmationModalService } from '@modal/send-order-confirmation';
import { ToasterService } from '@shared/shell';
@Component({
selector: 'page-checkout-summary',
templateUrl: 'checkout-summary.component.html',
styleUrls: ['checkout-summary.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class CheckoutSummaryComponent implements OnInit, OnDestroy {
private _injector = inject(Injector);
get sendOrderConfirmationModalService() {
return this._injector.get(SendOrderConfirmationModalService);
}
private _toaster = inject(ToasterService);
private _onDestroy$ = new Subject<void>();
processId = Date.now();
selectedDate = this.dateAdapter.today();
minDateDatepicker = this.dateAdapter.addCalendarDays(
this.dateAdapter.today(),
-1,
);
updatingPreferredPickUpDate$ = new BehaviorSubject<Record<string, string>>(
{},
);
displayOrders$ = combineLatest([
this.domainCheckoutService.getOrders(),
this._route.params,
]).pipe(
map(([orders, params]) => {
let filteredOrders: DisplayOrderDTO[] = [];
if (params?.orderIds) {
const orderIds: string[] = params.orderIds.split(',');
filteredOrders = orders.filter((order) =>
orderIds.find((id) => Number(id) === order.id),
);
} else {
return filteredOrders;
}
// Ticket #4228 Für die korrekte Gruppierung der Items bei gleichem Bestellziel (Aufsplitten von Abholung und Rücklage)
const ordersWithMultipleFeatures = filteredOrders.filter((order) =>
order.items.find(
(item) => item.features.orderType !== order.features.orderType,
),
);
if (ordersWithMultipleFeatures) {
for (let orderWithMultipleFeatures of ordersWithMultipleFeatures) {
if (orderWithMultipleFeatures?.items?.length > 1) {
const itemsWithOrderFeature =
orderWithMultipleFeatures.items.filter(
(item) =>
item.features.orderType ===
orderWithMultipleFeatures.features.orderType,
);
const itemsWithDifferentOrderFeature =
orderWithMultipleFeatures.items.filter(
(item) =>
item.features.orderType !==
orderWithMultipleFeatures.features.orderType,
);
filteredOrders = [
...filteredOrders.filter(
(order) => order.id !== orderWithMultipleFeatures.id,
),
];
if (itemsWithOrderFeature?.length > 0) {
filteredOrders = [
...filteredOrders,
{ ...orderWithMultipleFeatures, items: itemsWithOrderFeature },
];
}
if (itemsWithDifferentOrderFeature?.length > 0) {
filteredOrders = [
...filteredOrders,
{
...orderWithMultipleFeatures,
items: itemsWithDifferentOrderFeature,
},
];
}
}
}
}
return filteredOrders?.map((order) => {
return {
...order,
items: [...order.items]?.sort((a, b) =>
a.product?.name.localeCompare(b.product?.name),
),
};
});
}),
shareReplay(),
);
hasAbholung$ = this.displayOrders$.pipe(
map(
(displayOrders) =>
displayOrders.filter(
(order) => order.features?.orderType === 'Abholung',
)?.length > 0,
),
);
totalItemCount$ = this.displayOrders$.pipe(
map((displayOrders) =>
displayOrders.reduce(
(total, displayOrder) =>
total +
displayOrder?.items?.reduce(
(subTotal, order) => subTotal + order?.quantity,
0,
),
0,
),
),
);
totalReadingPoints$ = this.displayOrders$.pipe(
switchMap((displayOrders) => {
const items = displayOrders
.reduce<DisplayOrderItemDTO[]>(
(items, order) => [...items, ...order.items],
[],
)
.map((i) => {
if (i?.product?.catalogProductNumber) {
return {
id: Number(i.product?.catalogProductNumber),
quantity: i.quantity,
price: i.price?.value?.value,
};
}
})
.filter((item) => item !== undefined);
if (items.length !== 0) {
return this.domainCatalogService
.getPromotionPoints({ items })
.pipe(
map((response) =>
Object.values(response.result).reduce(
(sum, points) => sum + points,
0,
),
),
);
} else {
return NEVER;
}
}),
);
totalPrice$ = this.displayOrders$.pipe(
map((displayOrders) =>
displayOrders.reduce(
(total, displayOrder) =>
total +
displayOrder?.items?.reduce(
(subTotal, order) =>
subTotal + order?.price?.value?.value * order.quantity,
0,
),
0,
),
),
);
isPrinting$ = new BehaviorSubject(false);
totalPriceCurrency$ = this.displayOrders$.pipe(
map((displayOrders) => displayOrders[0]?.items[0]?.price?.value?.currency),
);
containsDeliveryOrder$ = this.displayOrders$.pipe(
map(
(displayOrders) =>
displayOrders.filter(
(o) =>
['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(
o.features?.orderType,
) > -1,
)?.length > 0,
),
);
customer$ = this.displayOrders$.pipe(
switchMap((o) =>
this.customerService.getCustomers(o[0].buyerNumber, { take: 5 }),
),
map((customers) => customers.result[0]),
shareReplay(),
);
isB2BCustomer$ = this.customer$.pipe(
map((customer) => customer?.features?.find((f) => f.key === 'b2b') != null),
);
takeNowOrders$ = this.displayOrders$.pipe(
map((displayOrders) =>
displayOrders.filter(
(o) =>
o.items.find((oi) => oi.features?.orderType === 'Rücklage') != null,
),
),
);
get isDesktop$() {
return this._environmentService.matchDesktopLarge$;
}
get isTablet() {
return this._environmentService.matchTablet();
}
expanded: boolean[] = [];
constructor(
private domainCheckoutService: DomainCheckoutService,
private customerService: CrmCustomerService,
private domainCatalogService: DomainCatalogService,
private router: Router,
private _route: ActivatedRoute,
private omsService: DomainOmsService,
private uiModal: UiModalService,
private breadcrumb: BreadcrumbService,
public applicationService: ApplicationService,
private domainPrinterService: DomainPrinterService,
private dateAdapter: DateAdapter,
private _navigation: CheckoutNavigationService,
private _productNavigationService: ProductCatalogNavigationService,
private _shelfOutNavigationService: PickUpShelfOutNavigationService,
private _environmentService: EnvironmentService,
private _cdr: ChangeDetectorRef,
) {
this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, [
'checkout',
])
.pipe(first())
.subscribe(async (crumbs) => {
for await (const crumb of crumbs) {
this.breadcrumb.removeBreadcrumb(crumb.id);
}
this.breadcrumb.addBreadcrumbIfNotExists({
key: this.applicationService.activatedProcessId,
name: 'Bestellbestätigung',
path: this._navigation.getCheckoutSummaryPath({
processId: this.applicationService.activatedProcessId,
orderIds: this._route.snapshot.params.orderIds,
}).path,
tags: ['checkout', 'cart'],
section: 'customer',
});
});
}
async ngOnInit() {
const displayOrders = await this.displayOrders$.pipe(first()).toPromise();
if (displayOrders?.length === 1) {
this.expanded = [true];
} else {
this.expanded = [];
}
this._cdr.markForCheck();
}
async ngOnDestroy() {
const checkoutProcess = await this.applicationService
.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout')
.pipe(first())
.toPromise();
// Wenn es keine Bestellabschluss Prozesse mehr gibt, werden alle Orders aus dem Store removed um die Performance zu verbessern
if (!checkoutProcess) {
this.domainCheckoutService.removeAllOrders();
}
this._onDestroy$.next();
this._onDestroy$.complete();
}
getProductSearchDetailsPath(ean: string) {
return this._productNavigationService.getArticleDetailsPathByEan({
processId: this.processId,
ean,
}).path;
}
getProductSearchDetailsQueryParams(item: DisplayOrderItemDTO) {
return {
main_qs: item?.product?.ean,
filter_format:
item?.features?.orderType === 'Download' ? 'eb;dl' : undefined,
};
}
async updatePreferredPickUpDate(item: DisplayOrderItemDTO, date: Date) {
const data: Record<string, string> = {};
try {
const items = item ? [item] : await this.getAllOrderItems();
const subsetItems = items
.filter((item) =>
['Rücklage', 'Abholung'].includes(item.features.orderType),
)
// .flatMap((item) => item.subsetItems);
.reduce<DisplayOrderItemSubsetDTO[]>(
(acc, item) => [...acc, ...item.subsetItems],
[],
);
subsetItems.forEach((item) => (data[`${item.id}`] = date?.toISOString()));
try {
this.updatingPreferredPickUpDate$.next(data);
await this.omsService.setPreferredPickUpDate({ data }).toPromise();
} catch (error) {
this.uiModal.open({
content: UiErrorModalComponent,
title: 'Fehler beim setzen des Wunschdatums',
data: error,
});
} finally {
this.updatingPreferredPickUpDate$.next({});
}
items.forEach((item) => {
this.updateDisplayOrderItem({
...item,
subsetItems: subsetItems.map((subsetItem) => {
return {
...subsetItem,
preferredPickUpDate: date?.toISOString(),
};
}),
});
});
} catch (error) {
console.error(error);
}
}
async getAllOrderItems() {
const orders = await this.displayOrders$.pipe(first()).toPromise();
return orders.reduce<DisplayOrderItemDTO[]>(
(agg, order) => [...agg, ...order.items],
[],
);
}
async updateDisplayOrderItem(item: DisplayOrderItemDTO) {
this.domainCheckoutService.updateOrderItem(item);
}
async navigateToShelfOut() {
let takeNowOrders = await this.takeNowOrders$.pipe(first()).toPromise();
if (takeNowOrders.length != 1) return;
try {
await this.router.navigate(
this._shelfOutNavigationService.listRoute({ processId: Date.now() })
.path,
{
queryParams: {
main_qs: takeNowOrders[0].orderNumber,
filter_supplier_id: '16',
},
},
);
} catch (e) {
console.error(e);
}
}
async sendOrderConfirmation() {
const orders = await this.displayOrders$.pipe(first()).toPromise();
await this.sendOrderConfirmationModalService.open(orders);
}
async printOrderConfirmation() {
this.isPrinting$.next(true);
const orders = await this.displayOrders$.pipe(first()).toPromise();
const selectedPrinter = await this.domainPrinterService
.getAvailableLabelPrinters()
.pipe(
first(),
map((printers) => {
if (Array.isArray(printers))
return printers.find((printer) => printer.selected === true);
}),
)
.toPromise();
console.log(selectedPrinter);
if (!selectedPrinter || this.isTablet) {
await this.uiModal
.open({
content: PrintModalComponent,
data: {
printerType: 'Label',
printImmediately: !this.isTablet,
print: async (printer) => {
try {
const result = await this.domainPrinterService
.printOrder({ orderIds: orders.map((o) => o.id), printer })
.toPromise();
this._toaster.open({
type: 'success',
message: 'Bestellbestätigung wurde gedruckt',
});
return result;
} catch (error) {
this._toaster.open({
type: 'danger',
message: 'Fehler beim Drucken der Bestellbestätigung',
});
} finally {
this.isPrinting$.next(false);
}
},
} as PrintModalData,
config: {
panelClass: [],
showScrollbarY: false,
},
})
.afterClosed$.toPromise();
this.isPrinting$.next(false);
} else {
try {
const result = await this.domainPrinterService
.printOrder({
orderIds: orders.map((o) => o.id),
printer: selectedPrinter.key,
})
.toPromise();
this._toaster.open({
type: 'success',
message: 'Bestellbestätigung wurde gedruckt',
});
return result;
} catch (error) {
this._toaster.open({
type: 'danger',
message: 'Fehler beim Drucken der Bestellbestätigung',
});
} finally {
this.isPrinting$.next(false);
}
}
}
}