mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Compare commits
1 Commits
feature/rd
...
fix/4082-S
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aa9ffec5d |
@@ -11,9 +11,13 @@ export class DevScanAdapter implements ScanAdapter {
|
||||
constructor(private _modal: UiModalService, private _environmentService: EnvironmentService) {}
|
||||
|
||||
async init(): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(isDevMode());
|
||||
});
|
||||
if (this._environmentService.isTablet()) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(isDevMode());
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
|
||||
@@ -32,32 +32,22 @@ export class ScanAdapterService {
|
||||
return Object.values(this._readyAdapters).some((ready) => ready);
|
||||
}
|
||||
|
||||
scan(ops: { use?: string; include?: string[]; exclude?: string[] } = {}): Observable<string> {
|
||||
scan(ops: { use?: string; include?: string[]; exclude?: string[] } = { exclude: ['Dev'] }): Observable<string> {
|
||||
let adapter: ScanAdapter;
|
||||
|
||||
if (ops.use == undefined) {
|
||||
const adapterOrder = ['Native', 'Scandit', 'Dev'];
|
||||
|
||||
for (const name of adapterOrder) {
|
||||
adapter = this.getAdapter(name);
|
||||
|
||||
if (adapter) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// get the first adapter that is ready to use
|
||||
// adapter = this.scanAdapters
|
||||
// .filter((adapter) => {
|
||||
// if (ops.include?.length) {
|
||||
// return ops.include.includes(adapter.name);
|
||||
// } else if (ops.exclude?.length) {
|
||||
// return !ops.exclude.includes(adapter.name);
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
// })
|
||||
// .find((adapter) => this._readyAdapters[adapter.name]);
|
||||
adapter = this.scanAdapters
|
||||
.filter((adapter) => {
|
||||
if (ops.include?.length) {
|
||||
return ops.include.includes(adapter.name);
|
||||
} else if (ops.exclude?.length) {
|
||||
return !ops.exclude.includes(adapter.name);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.find((adapter) => this._readyAdapters[adapter.name]);
|
||||
} else {
|
||||
adapter = this.getAdapter(ops.use);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { NgModule } from '@angular/core';
|
||||
import { ProductImagePipe } from './product-image.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [ProductImagePipe],
|
||||
declarations: [ProductImagePipe],
|
||||
imports: [],
|
||||
exports: [ProductImagePipe],
|
||||
})
|
||||
export class ProductImageModule {}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { ProductImageService } from './product-image.service';
|
||||
|
||||
@Pipe({
|
||||
name: 'productImage',
|
||||
standalone: true,
|
||||
pure: true,
|
||||
})
|
||||
export class ProductImagePipe implements PipeTransform {
|
||||
constructor(private imageService: ProductImageService) {}
|
||||
|
||||
@@ -138,6 +138,6 @@ export class BreadcrumbService {
|
||||
getLatestBreadcrumbForSection(section: 'customer' | 'branch', predicate: (crumb: Breadcrumb) => boolean = (_) => true) {
|
||||
return this.store
|
||||
.select(selectors.selectBreadcrumbsBySection, { section })
|
||||
.pipe(map((crumbs) => crumbs.sort((a, b) => b.timestamp - a.timestamp).find((f) => predicate(f))));
|
||||
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => predicate(f))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { CommandService } from './command.service';
|
||||
|
||||
export abstract class ActionHandler<T = any> {
|
||||
constructor(readonly action: string) {}
|
||||
abstract handler(data: T, service?: CommandService): Promise<T>;
|
||||
abstract handler(data: T): Promise<T>;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { ModuleWithProviders, NgModule, Provider, Type } from '@angular/core';
|
||||
import { ModuleWithProviders, NgModule, Type } from '@angular/core';
|
||||
import { ActionHandler } from './action-handler.interface';
|
||||
import { CommandService } from './command.service';
|
||||
import { FEATURE_ACTION_HANDLERS, ROOT_ACTION_HANDLERS } from './tokens';
|
||||
|
||||
export function provideActionHandlers(actionHandlers: Type<ActionHandler>[]): Provider[] {
|
||||
return [CommandService, actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true }))];
|
||||
}
|
||||
|
||||
@NgModule({})
|
||||
export class CoreCommandModule {
|
||||
static forRoot(actionHandlers: Type<ActionHandler>[]): ModuleWithProviders<CoreCommandModule> {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class CommandService {
|
||||
throw new Error('Action Handler does not exist');
|
||||
}
|
||||
|
||||
data = await handler.handler(data, this);
|
||||
data = await handler.handler(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
|
||||
import { Platform } from '@angular/cdk/platform';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
|
||||
const MATCH_TABLET = '(max-width: 1024px)';
|
||||
|
||||
@@ -24,19 +23,19 @@ export class EnvironmentService {
|
||||
return this._breakpointObserver.isMatched(MATCH_TABLET);
|
||||
}
|
||||
|
||||
matchTablet$ = this._breakpointObserver.observe(MATCH_TABLET).pipe(shareReplay());
|
||||
matchTablet$ = this._breakpointObserver.observe(MATCH_TABLET);
|
||||
|
||||
matchDesktopSmall(): boolean {
|
||||
return this._breakpointObserver.isMatched(MATCH_DESKTOP_SMALL);
|
||||
}
|
||||
|
||||
matchDesktopSmall$ = this._breakpointObserver.observe(MATCH_DESKTOP_SMALL).pipe(shareReplay());
|
||||
matchDesktopSmall$ = this._breakpointObserver.observe(MATCH_DESKTOP_SMALL);
|
||||
|
||||
matchDesktop(): boolean {
|
||||
return this._breakpointObserver.isMatched(MATCH_DESKTOP);
|
||||
}
|
||||
|
||||
matchDesktop$ = this._breakpointObserver.observe(MATCH_DESKTOP).pipe(shareReplay());
|
||||
matchDesktop$ = this._breakpointObserver.observe(MATCH_DESKTOP);
|
||||
|
||||
/**
|
||||
* @deprecated Use `matchDesktopSmall` or 'matchDesktop' instead.
|
||||
|
||||
@@ -145,7 +145,6 @@ export class DomainAvailabilityService {
|
||||
);
|
||||
}
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
getTakeAwayAvailability({
|
||||
item,
|
||||
quantity,
|
||||
@@ -155,7 +154,6 @@ export class DomainAvailabilityService {
|
||||
quantity: number;
|
||||
branch?: BranchDTO;
|
||||
}): Observable<AvailabilityDTO> {
|
||||
console.log('getTakeAwayAvailability', item, quantity, branch);
|
||||
const request = !!branch ? this.getStockByBranch(branch.id) : this.getDefaultStock();
|
||||
return request.pipe(
|
||||
switchMap((s) =>
|
||||
@@ -542,7 +540,6 @@ export class DomainAvailabilityService {
|
||||
return preferred.map((p) => {
|
||||
return [
|
||||
{
|
||||
orderDeadline: p?.orderDeadline,
|
||||
availabilityType: p?.status,
|
||||
ssc: p?.ssc,
|
||||
sscText: p?.sscText,
|
||||
@@ -564,6 +561,7 @@ export class DomainAvailabilityService {
|
||||
|
||||
private _mapToShippingAvailability(availabilities: SwaggerAvailabilityDTO[]) {
|
||||
const preferred = availabilities.filter((f) => f.preferred === 1);
|
||||
|
||||
return preferred.map((p) => {
|
||||
return {
|
||||
availabilityType: p?.status,
|
||||
@@ -577,7 +575,6 @@ export class DomainAvailabilityService {
|
||||
supplierInfo: p?.requestStatusCode,
|
||||
lastRequest: p?.requested,
|
||||
itemId: p.itemId,
|
||||
priceMaintained: p.priceMaintained,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,13 +28,7 @@ import {
|
||||
StoreCheckoutBranchService,
|
||||
ItemsResult,
|
||||
} from '@swagger/checkout';
|
||||
import {
|
||||
DisplayOrderDTO,
|
||||
DisplayOrderItemDTO,
|
||||
OrderCheckoutService,
|
||||
ReorderValues,
|
||||
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
|
||||
} from '@swagger/oms';
|
||||
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
|
||||
import { isNullOrUndefined, memorize } from '@utils/common';
|
||||
import { combineLatest, Observable, of, concat, isObservable, throwError } from 'rxjs';
|
||||
import { bufferCount, catchError, filter, first, map, mergeMap, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
@@ -378,9 +372,8 @@ export class DomainCheckoutService {
|
||||
_setBuyer({ processId, buyer }: { processId: number; buyer: BuyerDTO }): Observable<CheckoutDTO> {
|
||||
return this.getCheckout({ processId }).pipe(
|
||||
first(),
|
||||
mergeMap((checkout) => {
|
||||
console.log('checkout', checkout, processId);
|
||||
return this._buyerService
|
||||
mergeMap((checkout) =>
|
||||
this._buyerService
|
||||
.StoreCheckoutBuyerSetBuyerPOST({
|
||||
checkoutId: checkout?.id,
|
||||
buyerDTO: buyer,
|
||||
@@ -388,8 +381,8 @@ export class DomainCheckoutService {
|
||||
.pipe(
|
||||
map((response) => response.result),
|
||||
tap((checkout) => this.store.dispatch(DomainCheckoutActions.setCheckout({ processId, checkout })))
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -717,47 +710,6 @@ export class DomainCheckoutService {
|
||||
.pipe(mergeMap((_) => completeOrder$.pipe(tap(console.log.bind(window, 'completeOrder$')))));
|
||||
}
|
||||
|
||||
completeKulturpassOrder({
|
||||
processId,
|
||||
orderItemSubsetId,
|
||||
}: {
|
||||
processId: number;
|
||||
orderItemSubsetId: number;
|
||||
}): Observable<ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString> {
|
||||
const refreshShoppingCart$ = this.getShoppingCart({ processId, latest: true }).pipe(first());
|
||||
const refreshCheckout$ = this.getCheckout({ processId, refresh: true }).pipe(first());
|
||||
|
||||
const setBuyer$ = this.getBuyer({ processId }).pipe(
|
||||
first(),
|
||||
mergeMap((buyer) => this._setBuyer({ processId, buyer }))
|
||||
);
|
||||
|
||||
const setPayer$ = this.getPayer({ processId }).pipe(
|
||||
first(),
|
||||
mergeMap((payer) => this._setPayer({ processId, payer }))
|
||||
);
|
||||
|
||||
const checkAvailabilities$ = this.checkAvailabilities({ processId });
|
||||
|
||||
const updateAvailabilities$ = this.updateAvailabilities({ processId });
|
||||
|
||||
return refreshShoppingCart$.pipe(
|
||||
mergeMap((_) => refreshCheckout$),
|
||||
mergeMap((_) => checkAvailabilities$),
|
||||
mergeMap((_) => updateAvailabilities$),
|
||||
mergeMap((_) => setBuyer$),
|
||||
mergeMap((_) => setPayer$),
|
||||
mergeMap((checkout) =>
|
||||
this.orderCheckoutService.OrderCheckoutCreateKulturPassOrder({
|
||||
payload: {
|
||||
checkoutId: checkout.id,
|
||||
orderItemSubsetId: String(orderItemSubsetId),
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
updateDestination({
|
||||
processId,
|
||||
destinationId,
|
||||
@@ -978,5 +930,6 @@ export class DomainCheckoutService {
|
||||
private updateProcessCount(processId: number, count: number) {
|
||||
this.applicationService.patchProcessData(processId, { count });
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@@ -18,15 +18,14 @@ import {
|
||||
NotificationChannel,
|
||||
PayerDTO,
|
||||
PayerService,
|
||||
QueryTokenDTO,
|
||||
ResponseArgsOfIEnumerableOfBonusCardInfoDTO,
|
||||
ShippingAddressDTO,
|
||||
ShippingAddressService,
|
||||
} from '@swagger/crm';
|
||||
import { isArray, memorize } from '@utils/common';
|
||||
import { isArray } from '@utils/common';
|
||||
import { PagedResult, Result } from 'apps/domain/defs/src/public-api';
|
||||
import { Observable, of, ReplaySubject } from 'rxjs';
|
||||
import { catchError, map, mergeMap, retry, shareReplay } from 'rxjs/operators';
|
||||
import { catchError, map, mergeMap, retry } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CrmCustomerService {
|
||||
@@ -39,14 +38,6 @@ export class CrmCustomerService {
|
||||
private loyaltyCardService: LoyaltyCardService
|
||||
) {}
|
||||
|
||||
@memorize()
|
||||
filterSettings() {
|
||||
return this.customerService.CustomerCustomerQuerySettings().pipe(
|
||||
map((res) => res.result),
|
||||
shareReplay(1)
|
||||
);
|
||||
}
|
||||
|
||||
complete(queryString: string, filter?: { [key: string]: string }): Observable<Result<AutocompleteDTO[]>> {
|
||||
return this.customerService.CustomerCustomerAutocomplete({
|
||||
input: queryString,
|
||||
@@ -75,15 +66,6 @@ export class CrmCustomerService {
|
||||
});
|
||||
}
|
||||
|
||||
getCustomersWithQueryToken(queryToken: QueryTokenDTO) {
|
||||
if (queryToken.skip === undefined) queryToken.skip = 0;
|
||||
if (queryToken.take === undefined) queryToken.take = 20;
|
||||
if (queryToken.input === undefined) queryToken.input = { qs: '' };
|
||||
if (queryToken.filter === undefined) queryToken.filter = {};
|
||||
|
||||
return this.customerService.CustomerListCustomers(queryToken);
|
||||
}
|
||||
|
||||
getCustomersByCustomerCardNumber(queryString: string): Observable<PagedResult<CustomerInfoDTO>> {
|
||||
return this.customerService.CustomerGetCustomerByBonuscard(!!queryString ? queryString : undefined);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export class CollectOnDeliveryNoteActionHandler extends ActionHandler<OrderItems
|
||||
const response = await this.orderService
|
||||
.OrderCollectOnDeliveryNote({
|
||||
data,
|
||||
eagerLoading: 1,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
@@ -30,7 +29,7 @@ export class CollectOnDeliveryNoteActionHandler extends ActionHandler<OrderItems
|
||||
|
||||
return {
|
||||
...context,
|
||||
receipts: response.result.map((r) => r.data),
|
||||
receipts: response.result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './accepted.action-handler';
|
||||
export * from './arrived.action-handler';
|
||||
export * from './assembled.action-handler';
|
||||
@@ -6,10 +7,6 @@ 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';
|
||||
@@ -28,15 +25,16 @@ 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 './print-smallamountinvoice.action-handler';
|
||||
export * from './re-order.action-handler';
|
||||
export * from './re-ordered.action-handler';
|
||||
export * from './re-order.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 './shop-with-kulturpass.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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrderItemListItemDTO, ReceiptDTO, OrderDTO } from '@swagger/oms';
|
||||
import { OrderItemListItemDTO, ReceiptDTO } from '@swagger/oms';
|
||||
|
||||
export interface OrderItemsContext {
|
||||
items: OrderItemListItemDTO[];
|
||||
@@ -12,6 +12,4 @@ export interface OrderItemsContext {
|
||||
receipts?: ReceiptDTO[];
|
||||
|
||||
shippingDelayComment?: string;
|
||||
|
||||
order?: OrderDTO;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsCont
|
||||
printerType: 'Label',
|
||||
print: async (printer) => {
|
||||
try {
|
||||
const receipts = data?.receipts?.filter((r) => r?.receiptType & 1);
|
||||
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
|
||||
for (const group of groupBy(data?.receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
|
||||
await this.domainPrinterService.printShippingNote({ printer, receipts: group?.items?.map((r) => r?.id) }).toPromise();
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { OrderItemsContext } from './order-items.context';
|
||||
import { ActionHandler, CommandService } from '@core/command';
|
||||
import { KulturpassOrderModalService } from '@shared/modals/kulturpass-order-modal';
|
||||
import { DisplayOrderItemSubsetDTO, OrderItemListItemDTO, ReceiptDTO } from '@swagger/oms';
|
||||
import { DomainReceiptService } from '../receipt.service';
|
||||
import { DomainGoodsService } from '../goods.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ShopWithKulturpassActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
constructor(
|
||||
private _modal: KulturpassOrderModalService,
|
||||
private _receiptService: DomainReceiptService,
|
||||
private _goodsService: DomainGoodsService
|
||||
) {
|
||||
super('SHOP_WITH_KULTURPASS');
|
||||
}
|
||||
|
||||
async handler(data: OrderItemsContext, service: CommandService): Promise<OrderItemsContext> {
|
||||
const items: OrderItemListItemDTO[] = [];
|
||||
const receipts: ReceiptDTO[] = [];
|
||||
|
||||
let command: string;
|
||||
for (const item of data.items) {
|
||||
const result = await this._modal.open({ orderItemListItem: item, order: data.order }).afterClosed$.toPromise();
|
||||
|
||||
if (result.data == null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const displayOrder = result.data[0];
|
||||
command = result.data[1];
|
||||
|
||||
if (displayOrder) {
|
||||
const subsetItems = displayOrder.items.reduce((acc, item) => [...acc, ...item.subsetItems], [] as DisplayOrderItemSubsetDTO[]);
|
||||
|
||||
const orderItems = await this.getItems(displayOrder.orderNumber);
|
||||
|
||||
items.push(...orderItems);
|
||||
|
||||
const subsetItemIds = subsetItems.map((item) => item.id);
|
||||
|
||||
const r = await this.getReceipts(subsetItemIds);
|
||||
|
||||
receipts.push(...r);
|
||||
}
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
return {
|
||||
...data,
|
||||
items,
|
||||
receipts,
|
||||
};
|
||||
} else {
|
||||
return service.handleCommand(command, {
|
||||
...data,
|
||||
items,
|
||||
receipts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getReceipts(ids: number[]) {
|
||||
return this._receiptService
|
||||
.getReceipts({
|
||||
receiptType: 128,
|
||||
eagerLoading: 1,
|
||||
ids,
|
||||
})
|
||||
.pipe(map((res) => res.result.map((data) => data.item3.data).filter((data) => !!data)))
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
getItems(orderNumber: string) {
|
||||
return this._goodsService
|
||||
.getWarenausgabeItemByOrderNumber(orderNumber, false)
|
||||
.pipe(map((res) => res.result))
|
||||
.toPromise();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AutocompleteTokenDTO, OrderService, QueryTokenDTO } from '@swagger/oms';
|
||||
import { memorize } from '@utils/common';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainCustomerOrderService {
|
||||
@@ -25,11 +23,7 @@ export class DomainCustomerOrderService {
|
||||
});
|
||||
}
|
||||
|
||||
@memorize()
|
||||
settings() {
|
||||
return this._orderService.OrderKundenbestellungenSettings().pipe(
|
||||
map((res) => res?.result),
|
||||
shareReplay()
|
||||
);
|
||||
return this._orderService.OrderKundenbestellungenSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ListResponseArgsOfPackageDTO,
|
||||
ListResponseArgsOfPackageDTO2,
|
||||
PackageArrivalStatusDTO,
|
||||
PackageDetailResponseDTO,
|
||||
PackageDTO,
|
||||
PackageDTO2,
|
||||
QuerySettingsDTO,
|
||||
QueryTokenDTO,
|
||||
@@ -11,18 +13,13 @@ import {
|
||||
ResponseArgsOfQuerySettingsDTO,
|
||||
WareneingangService,
|
||||
} from '@swagger/wws';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { PackageInspectionEvent, PackageStatusChangedEvent } from './events';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DomainPackageInspectionService {
|
||||
private _events = new Subject<PackageInspectionEvent>();
|
||||
|
||||
events = this._events.asObservable();
|
||||
|
||||
constructor(private _wareneingang: WareneingangService) {}
|
||||
|
||||
getQuerySettingsResponse(): Observable<ResponseArgsOfQuerySettingsDTO> {
|
||||
@@ -50,26 +47,23 @@ 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,
|
||||
scanId: pkg.scanId,
|
||||
},
|
||||
modifier,
|
||||
})
|
||||
.pipe(tap((res) => this._events.next(new PackageStatusChangedEvent(pkg.id, res.result))));
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
changePackageStatus(pkg: PackageDTO2, modifier: string): Observable<PackageArrivalStatusDTO> {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
CanActivateCartGuard,
|
||||
CanActivateCartWithProcessIdGuard,
|
||||
CanActivateCustomerGuard,
|
||||
CanActivateCustomerOrdersGuard,
|
||||
CanActivateCustomerOrdersWithProcessIdGuard,
|
||||
CanActivateCustomerWithProcessIdGuard,
|
||||
CanActivateGoodsInGuard,
|
||||
CanActivateGoodsOutGuard,
|
||||
@@ -61,22 +59,22 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'order',
|
||||
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersGuard],
|
||||
canActivate: [CanActivateGoodsOutGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/order',
|
||||
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
|
||||
canActivate: [CanActivateGoodsOutWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'customer',
|
||||
loadChildren: () => import('@page/customer-rd').then((m) => m.CustomerModule),
|
||||
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),
|
||||
canActivate: [CanActivateCustomerGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/customer',
|
||||
loadChildren: () => import('@page/customer-rd').then((m) => m.CustomerModule),
|
||||
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),
|
||||
canActivate: [CanActivateCustomerWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
|
||||
@@ -32,11 +32,11 @@ import { IsaErrorHandler } from './providers/isa.error-handler';
|
||||
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
|
||||
import { RootStateService } from './store/root-state.service';
|
||||
import * as Commands from './commands';
|
||||
import { UiIconModule, UI_ICON_CFG } from '@ui/icon';
|
||||
import { PreviewComponent } from './preview';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { ShellModule } from '@shared/shell';
|
||||
import { MainComponent } from './main.component';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
@@ -106,7 +106,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
ScanAdapterModule.forRoot(),
|
||||
ScanditScanAdapterModule.forRoot(),
|
||||
PlatformModule,
|
||||
IconModule.forRoot(),
|
||||
UiIconModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -135,6 +135,11 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
useClass: IsaErrorHandler,
|
||||
},
|
||||
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
||||
{
|
||||
provide: UI_ICON_CFG,
|
||||
useFactory: (config: Config) => config.get('@ui/icon'),
|
||||
deps: [Config],
|
||||
},
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { CheckoutNavigationService } from '@shared/services';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateCartGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService, private _checkoutNavigationService: CheckoutNavigationService) {}
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
@@ -22,7 +21,7 @@ export class CanActivateCartGuard implements CanActivate {
|
||||
name: `Vorgang ${processes.length + 1}`,
|
||||
});
|
||||
}
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: lastActivatedProcessId });
|
||||
await this._router.navigate(['/kunde', lastActivatedProcessId, 'cart']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateCustomerOrdersWithProcessIdGuard implements CanActivate {
|
||||
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const process = await this._applicationService
|
||||
.getProcessById$(+route.params.processId)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
if (!process) {
|
||||
await this._applicationService.createProcess({
|
||||
id: +route.params.processId,
|
||||
type: 'customer-order',
|
||||
section: 'customer',
|
||||
name: `Kundenbestellungen`,
|
||||
});
|
||||
}
|
||||
|
||||
await this.removeBreadcrumbWithSameProcessId(route);
|
||||
this._applicationService.activateProcess(+route.params.processId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
|
||||
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
|
||||
const crumbs = await this._breadcrumbService
|
||||
.getBreadcrumbByKey$(+route.params.processId)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
// Entferne alle Crumbs die nichts mit den Kundenbestellungen zu tun haben
|
||||
if (crumbs.length > 1) {
|
||||
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'customer-order') === undefined);
|
||||
for (const crumb of crumbsToRemove) {
|
||||
await this._breadcrumbService.removeBreadcrumb(crumb.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { CustomerOrdersNavigationService } from '@shared/services';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateCustomerOrdersGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _navigationService: CustomerOrdersNavigationService
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
|
||||
let lastActivatedProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
const lastActivatedCartCheckoutProcessId = (
|
||||
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
|
||||
)?.id;
|
||||
|
||||
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
|
||||
|
||||
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
|
||||
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
|
||||
await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lastActivatedProcessId) {
|
||||
await this.fromGoodsOutProcess(processes, route);
|
||||
return false;
|
||||
} else {
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId: lastActivatedProcessId });
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bei offenen Kundenbestellungen und Klick auf Kundenbestellungen
|
||||
async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
|
||||
const newProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: newProcessId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId: newProcessId });
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Kundenbestellungen
|
||||
async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
|
||||
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
|
||||
this._checkoutService.removeProcess({ processId });
|
||||
|
||||
// Ändere type cart-checkout zu customer-order
|
||||
this._applicationService.patchProcess(processId, {
|
||||
id: processId,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
data: {},
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId });
|
||||
}
|
||||
|
||||
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
|
||||
url.push(...route.url.map((segment) => segment.path));
|
||||
if (route.firstChild) {
|
||||
return this.getUrlFromSnapshot(route.firstChild, url);
|
||||
}
|
||||
return url.filter((segment) => !!segment);
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
|
||||
}
|
||||
|
||||
findMissingNumber(processNumbers: number[]) {
|
||||
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
|
||||
if (!processNumbers.find((number) => number === missingNumber)) {
|
||||
return missingNumber;
|
||||
}
|
||||
}
|
||||
return Math.max(...processNumbers) + 1;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
@@ -10,7 +10,8 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _navigationService: ProductCatalogNavigationService
|
||||
private readonly _navigationService: ProductCatalogNavigationService,
|
||||
private readonly _router: Router
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
@@ -42,7 +43,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
await this.fromCartProcess(processes);
|
||||
return false;
|
||||
} else {
|
||||
await this._navigationService.navigateToProductSearch({ processId: lastActivatedProcessId });
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -82,7 +83,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._navigationService.navigateToProductSearch({ processId });
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Footer Artikelsuche
|
||||
@@ -100,7 +101,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
});
|
||||
|
||||
// Navigation
|
||||
await this._navigationService.navigateToProductSearch({ processId });
|
||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
|
||||
}
|
||||
|
||||
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
|
||||
|
||||
@@ -5,8 +5,6 @@ export * from './can-activate-customer.guard';
|
||||
export * from './can-activate-goods-in.guard';
|
||||
export * from './can-activate-goods-out-with-process-id.guard';
|
||||
export * from './can-activate-goods-out.guard';
|
||||
export * from './can-activate-customer-orders.guard';
|
||||
export * from './can-activate-customer-orders-with-process-id.guard';
|
||||
export * from './can-activate-product-with-process-id.guard';
|
||||
export * from './can-activate-product.guard';
|
||||
export * from './can-activate-remission.guard';
|
||||
|
||||
@@ -52,7 +52,11 @@ export class IsAuthenticatedGuard implements CanActivate {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = await this._scanService.scan()?.toPromise();
|
||||
const result = await this._scanService
|
||||
.scan({
|
||||
exclude: ['Dev'],
|
||||
})
|
||||
?.toPromise();
|
||||
|
||||
if (typeof result === 'string') {
|
||||
try {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -68,7 +68,6 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
"scandit": "AQZyKCc+BEkNL00Y3h3FjawGLF+INUj7cVb0My91hl8ffiW873T8FTV1k4TIZJx5RwcJlYxhgsxHVcnM4AJgSwJhbAfxJmP/3XGijLlLp3XUIRjQwFtf7UlZAFZ7Vrt1/WSf7kxxrFQ2SE2AQwLqPg9DL+hHEfd4xT/15n8p2q7qUlCKLsV6jF12Pd7koFNSWNL3ZIkRtd1ma99/321dnwAJHFGXqWg5nprJ7sYtqUqNQ8Er9SlvKbhnw3AipHzKpz0O3oNfUsr6NlZivRBhMhCZLo5WpXo1m9uIU8zLEWMNDJ+wGUctcGxE3eCptP2zLXUgxxjB+0EXOUtT/GWUc/Ip61CMiyUf7Paz026E2eYil2yWgfkTP5CUgDMNGZFuAA1T5PhB9FRW51CjAIvwOKVMCvfixJiVoUsXHnWH2ZnXqtbDR/uEZBE7OKoBlaPL4G3Lvgdqym5EjROAztUXb6wOmVDiGzzqgizyZnIcxFBSKJAownGj9Vh4/Y/Ag1xzGzNtjz3ngSRfMfIIq/q2Q51uiLiv7mBVliPvPWMUTfTjnqnK/OSBlR2ID+COJqnUKpQMedPyOT3IMznmM6gQCmyYO5KE0MkfhFh6+pdNi6oJM2iZsxK1Z1V+GRSOIwrJEoajjDJkh439XjXk8NExFvplrLjK/oL/dsHIZiG6U5GVWW92kGkuXkJCeUz1CET3paxbGqwrd53r5d6gFABbC12CtcP2JeH4YYCpHYyPQacf0prj9Hdq3wDztShC9tH+4UQS/GbaDHKcS1ANIyPuTxHmBFtPuCJ9Uagy5QBEc8eAz2nfsbfaUxYzco6u/zhNsFbqp6zgQIxs5OcqDQ=="
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -69,7 +69,6 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZZzfQ+eLFl3Dzf1QSBag1lDibIoOPh4W33erRIRe3SDUMkHDX8eczEjd2TnfRMWoE5lXOBGtESCWICN9EbrmI1S9Lu5APsvvEOD+K54ADwIVawx0HNZRAc8/+9Vf/izcEGOFQFGBQJyR6vzdzFv5HcjznhxI9E3LiF+uVQPtCqsVYzpkMWIrC5VCg2uwNrj9Bw6f8zYi/lZPrDMS5yVKVcajeK7sh9QAq17dR0opjIIuP5t5nDEJ7hnITwtTR5HaM6cX/KhKpTILOgKexvLYqrK6QJWpU85sDwqwn6T7av4V68qL3XrUo60dScop4QsvraQe1HkRsffl6DkAEoX0RNMS5qVWjGerW7lvA/DQd9hsAO3jWFDR9hVDyt2VvmzzFKnHYqTYxC5qG4bCEJ0RJjy6tEP5Q7vL5SxWygVadmjPv+TwDOCS7DxzxIjcO+BXQY7gW6qn0hx9fXzyvO3avrGWqyImMlgEApZq+36ANqtRcPD/stEe4i0N9dSPhYoHPcc/9/9jpts43FozlgfY4wY8Wt5ybB3X0caISMmB/klFIJKKN7num439z3+Xk7ENB/Xvb0XAtnOt/cuxQYsGQ7fb62GOO/7Va5fdE9ZfaIJsS5ToE6oIbV04pLUssJf9cUMsyPFVELYSJmyGPQQFRz0TTxxRvPapIWrfa2x5x3hYUpNTAdY3v0fN9l/1ZqNSBmIBLH/LoXaVJQ2DydGD1/QFZ2Z/S7zTYKg5/cSEpUgiYtbwutNZSjRH29ucSizC524k+Zst95T8G7LJaWCT8SQAcKXqCnjpiEGWzD++h0jXjn6BWjUnIHi0te+27vF/z6UQL00sWco5hUIqF66EiU="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,6 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZZzfQ+eLFl3Dzf1QSBag1lDibIoOPh4W33erRIRe3SDUMkHDX8eczEjd2TnfRMWoE5lXOBGtESCWICN9EbrmI1S9Lu5APsvvEOD+K54ADwIVawx0HNZRAc8/+9Vf/izcEGOFQFGBQJyR6vzdzFv5HcjznhxI9E3LiF+uVQPtCqsVYzpkMWIrC5VCg2uwNrj9Bw6f8zYi/lZPrDMS5yVKVcajeK7sh9QAq17dR0opjIIuP5t5nDEJ7hnITwtTR5HaM6cX/KhKpTILOgKexvLYqrK6QJWpU85sDwqwn6T7av4V68qL3XrUo60dScop4QsvraQe1HkRsffl6DkAEoX0RNMS5qVWjGerW7lvA/DQd9hsAO3jWFDR9hVDyt2VvmzzFKnHYqTYxC5qG4bCEJ0RJjy6tEP5Q7vL5SxWygVadmjPv+TwDOCS7DxzxIjcO+BXQY7gW6qn0hx9fXzyvO3avrGWqyImMlgEApZq+36ANqtRcPD/stEe4i0N9dSPhYoHPcc/9/9jpts43FozlgfY4wY8Wt5ybB3X0caISMmB/klFIJKKN7num439z3+Xk7ENB/Xvb0XAtnOt/cuxQYsGQ7fb62GOO/7Va5fdE9ZfaIJsS5ToE6oIbV04pLUssJf9cUMsyPFVELYSJmyGPQQFRz0TTxxRvPapIWrfa2x5x3hYUpNTAdY3v0fN9l/1ZqNSBmIBLH/LoXaVJQ2DydGD1/QFZ2Z/S7zTYKg5/cSEpUgiYtbwutNZSjRH29ucSizC524k+Zst95T8G7LJaWCT8SQAcKXqCnjpiEGWzD++h0jXjn6BWjUnIHi0te+27vF/z6UQL00sWco5hUIqF66EiU="
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,3 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
debug: false,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
debug: false,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -14,28 +14,26 @@ if (environment.production) {
|
||||
|
||||
const debugService = new DebugService();
|
||||
|
||||
if (environment.debug) {
|
||||
const consoleLog = console.log;
|
||||
const consoleLog = console.log;
|
||||
|
||||
console.log = (...args) => {
|
||||
debugService.add({ type: 'log', args });
|
||||
consoleLog(...args);
|
||||
};
|
||||
console.log = (...args) => {
|
||||
debugService.add({ type: 'log', args });
|
||||
consoleLog(...args);
|
||||
};
|
||||
|
||||
const consoleWarn = console.warn;
|
||||
const consoleWarn = console.warn;
|
||||
|
||||
console.warn = (...args) => {
|
||||
debugService.add({ type: 'warn', args });
|
||||
consoleWarn(...args);
|
||||
};
|
||||
console.warn = (...args) => {
|
||||
debugService.add({ type: 'warn', args });
|
||||
consoleWarn(...args);
|
||||
};
|
||||
|
||||
const consoleError = console.error;
|
||||
const consoleError = console.error;
|
||||
|
||||
console.error = (...args) => {
|
||||
debugService.add({ type: 'error', args });
|
||||
consoleError(...args);
|
||||
};
|
||||
}
|
||||
console.error = (...args) => {
|
||||
debugService.add({ type: 'error', args });
|
||||
consoleError(...args);
|
||||
};
|
||||
|
||||
platformBrowserDynamic([{ provide: DebugService, useValue: debugService }])
|
||||
.bootstrapModule(AppModule)
|
||||
|
||||
@@ -67,18 +67,3 @@
|
||||
@apply block bg-gray-300 h-6;
|
||||
animation: load 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.input-control {
|
||||
@apply rounded border border-solid border-[#AEB7C1] px-4 py-[1.125rem] outline-none;
|
||||
}
|
||||
|
||||
// .input-control:focus,
|
||||
// .input-control:not(:placeholder-shown) {
|
||||
// @apply bg-white;
|
||||
// }
|
||||
|
||||
.input-control.ng-touched.ng-invalid {
|
||||
@apply border-brand;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ export class PriceUpdateItemComponent {
|
||||
itemId: Number(item?.product?.catalogProductNumber),
|
||||
branchId: defaultBranch?.id,
|
||||
})
|
||||
)
|
||||
),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="absolute right-0 top-0 h-14 rounded px-5 text-lg bg-cadet-blue flex flex-row flex-nowrap items-center justify-center"
|
||||
type="button"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
|
||||
Filter
|
||||
</button>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
<shell-filter-overlay #filterOverlay class="relative">
|
||||
<div class="relative">
|
||||
<button type="button" class="absolute top-4 right-4 text-cadet" (click)="closeFilterOverlay()">
|
||||
<shared-icon [icon]="'close'" [size]="28"></shared-icon>
|
||||
<ui-svg-icon [icon]="'close'" [size]="28"></ui-svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
import { PriceUpdateListModule } from './price-update-list';
|
||||
import { PriceUpdateComponent } from './price-update.component';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, PriceUpdateListModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule, IconComponent],
|
||||
imports: [CommonModule, PriceUpdateListModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
|
||||
exports: [PriceUpdateComponent],
|
||||
declarations: [PriceUpdateComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<ng-container *ngIf="!showRecommendations">
|
||||
<div #detailsContainer class="page-article-details__container px-5 relative">
|
||||
<div class="page-article-details__container px-5 relative">
|
||||
<ng-container *ngIf="store.item$ | async; let item">
|
||||
<div class="page-article-details__product-details mb-3">
|
||||
<div class="page-article-details__product-bookmark justify-self-end">
|
||||
@@ -183,18 +183,11 @@
|
||||
></div>
|
||||
<ng-template #showAvailabilityPickUpIcon>
|
||||
<div
|
||||
#uiOverlayTrigger="uiOverlayTrigger"
|
||||
[uiOverlayTrigger]="orderDeadlineTooltip"
|
||||
*ngIf="store.isPickUpAvailabilityAvailable$ | async"
|
||||
class="page-article-details__product-pick-up-availability w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
[class.tooltip-active]="uiOverlayTrigger.opened"
|
||||
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||
>
|
||||
<shared-icon icon="isa-box-out" [size]="24"></shared-icon>
|
||||
<ui-icon class="mx-1" icon="box_out" size="18px"></ui-icon>
|
||||
</div>
|
||||
|
||||
<ui-tooltip [warning]="true" yPosition="above" xPosition="after" [yOffset]="-12" #orderDeadlineTooltip [closeable]="true">
|
||||
<b #orderDeadline>{{ (store.pickUpAvailability$ | async)?.orderDeadline | orderDeadline }}</b>
|
||||
</ui-tooltip>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
@@ -226,6 +219,7 @@
|
||||
<span *ngIf="store.isDownload$ | async" class="flex flex-row items-center">
|
||||
<div class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3">
|
||||
<ui-icon class="mx-1" icon="download" size="18px"></ui-icon>
|
||||
<span class="font-bold">Download</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
@@ -240,7 +234,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__shelfinfo text-right" *ngIf="store.isDownload$ | async">
|
||||
<div class="page-article-details__shelfinfo" *ngIf="store.isDownload$ | async">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||
@@ -324,7 +318,11 @@
|
||||
|
||||
<hr class="bg-[#E6EFF9] border-t-2 my-3" />
|
||||
|
||||
<div #description class="page-article-details__product-description flex flex-col flex-grow" *ngIf="item.texts?.length > 0">
|
||||
<div
|
||||
#description
|
||||
class="page-article-details__product-description flex flex-col flex-grow overflow-hidden overflow-y-scroll"
|
||||
*ngIf="item.texts?.length > 0"
|
||||
>
|
||||
<div class="whitespace-pre-line">
|
||||
{{ item.texts[0].value }}
|
||||
</div>
|
||||
@@ -358,7 +356,7 @@
|
||||
<ng-container *ngIf="!showRecommendations">
|
||||
<div
|
||||
*ngIf="store.item$ | async; let item"
|
||||
class="page-article-details__actions w-full sticky text-center left-0 -mb-4 bottom-10 z-fixed"
|
||||
class="page-article-details__actions w-full absolute text-center left-0 bottom-10 z-fixed"
|
||||
>
|
||||
<button
|
||||
*ngIf="!(store.isDownload$ | async)"
|
||||
@@ -378,7 +376,8 @@
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="page-article-details__product-recommendations sticky bottom-0 -mx-5">
|
||||
|
||||
<div class="page-article-details__product-recommendations -mx-5">
|
||||
<button
|
||||
*ngIf="store.item$ | async; let item"
|
||||
class="shadow-[#dce2e9_0px_-2px_18px_0px] sticky bottom-4 border-none outline-none left-0 right-0 flex items-center px-5 h-14 min-h-[3.5rem] bg-white w-full"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.page-article-details__container {
|
||||
@apply h-full w-full overflow-y-scroll overflow-hidden bg-white rounded shadow-card flex flex-col;
|
||||
@apply h-full w-full bg-white rounded shadow-card flex flex-col;
|
||||
}
|
||||
|
||||
.page-article-details__product-details {
|
||||
@@ -93,11 +93,7 @@
|
||||
}
|
||||
|
||||
.page-article-details__actions {
|
||||
button:disabled {
|
||||
@apply bg-inactive-branch cursor-not-allowed;
|
||||
&:disabled {
|
||||
@apply bg-inactive-branch;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-active {
|
||||
@apply bg-[#596470] text-white;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
@@ -8,7 +8,7 @@ import { BranchDTO } from '@swagger/checkout';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { ModalReviewsComponent } from '@modal/reviews';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, filter, first, map, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { debounceTime, filter, first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleDetailsStore } from './article-details.store';
|
||||
import { ModalImagesComponent } from 'apps/modal/images/src/public-api';
|
||||
import { ProductImageService } from 'apps/cdn/product-image/src/public-api';
|
||||
@@ -21,7 +21,7 @@ import { DatePipe } from '@angular/common';
|
||||
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
|
||||
@Component({
|
||||
@@ -133,13 +133,6 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
showMore: boolean = false;
|
||||
|
||||
@ViewChild('detailsContainer', { read: ElementRef, static: false })
|
||||
detailsContainer: ElementRef;
|
||||
|
||||
get detailsContainerNative(): HTMLElement {
|
||||
return this.detailsContainer?.nativeElement;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly applicationService: ApplicationService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
@@ -150,10 +143,10 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private _dateAdapter: DateAdapter,
|
||||
private _datePipe: DatePipe,
|
||||
public elementRef: ElementRef,
|
||||
private _purchaseOptionsModalService: PurchaseOptionsModalService,
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _checkoutNavigationService: CheckoutNavigationService,
|
||||
private _environment: EnvironmentService,
|
||||
private _router: Router,
|
||||
private _domainCheckoutService: DomainCheckoutService
|
||||
@@ -182,13 +175,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
const id$ = this.activatedRoute.params.pipe(
|
||||
tap((_) => (this.showRecommendations = false)),
|
||||
map((params) => Number(params?.id) || undefined),
|
||||
filter((f) => !!f)
|
||||
);
|
||||
|
||||
const ean$ = this.activatedRoute.params.pipe(
|
||||
tap((_) => (this.showRecommendations = false)),
|
||||
map((params) => params?.ean || undefined),
|
||||
filter((f) => !!f)
|
||||
);
|
||||
@@ -344,9 +335,6 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
type: 'add',
|
||||
processId: this.applicationService.activatedProcessId,
|
||||
items: [item],
|
||||
pickupBranch: selectedBranch,
|
||||
inStoreBranch: selectedBranch,
|
||||
preSelectOption: !!selectedBranch ? { option: 'in-store', showOptionOnly: true } : undefined,
|
||||
})
|
||||
.afterClosed$.subscribe(async (result) => {
|
||||
if (result?.data === 'continue') {
|
||||
@@ -355,18 +343,20 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
if (customer) {
|
||||
await this.navigateToShoppingCart();
|
||||
this.navigateToShoppingCart();
|
||||
} else {
|
||||
await this.navigateToCustomerSearch();
|
||||
this.navigateToCustomerSearch();
|
||||
}
|
||||
console.log('continue');
|
||||
} else if (result?.data === 'continue-shopping') {
|
||||
this.navigateToResultList();
|
||||
console.log('continue-shopping');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async navigateToShoppingCart() {
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: this.applicationService.activatedProcessId });
|
||||
navigateToShoppingCart() {
|
||||
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/cart/review`]);
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch() {
|
||||
@@ -388,23 +378,23 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async navigateToResultList() {
|
||||
const processId = this.applicationService.activatedProcessId;
|
||||
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(this._navigationService.getArticleSearchResultsPath(processId), { queryParams: crumb.params });
|
||||
if (crumb) {
|
||||
this._router.navigate([crumb.path], { queryParams: crumb.params });
|
||||
} else {
|
||||
this._navigationService.navigateToProductSearch({ processId });
|
||||
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/product`]);
|
||||
}
|
||||
}
|
||||
|
||||
scrollTop(div: HTMLDivElement) {
|
||||
this.detailsContainerNative?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
div?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
loadImage() {
|
||||
|
||||
@@ -10,8 +10,6 @@ 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';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -23,9 +21,7 @@ import { IconModule } from '@shared/components/icon';
|
||||
UiSliderModule,
|
||||
UiCommonModule,
|
||||
UiTooltipModule,
|
||||
IconModule,
|
||||
PipesModule,
|
||||
OrderDeadlinePipeModule,
|
||||
],
|
||||
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||
|
||||
@@ -129,11 +129,7 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
|
||||
|
||||
//#region Abholung
|
||||
readonly fetchingPickUpAvailability$ = this.select((s) => s.fetchingPickUpAvailability);
|
||||
readonly pickUpAvailability$: Observable<AvailabilityDTO & { orderDeadline?: string }> = combineLatest([
|
||||
this.itemData$,
|
||||
this.branch$,
|
||||
this.isDownload$,
|
||||
]).pipe(
|
||||
readonly pickUpAvailability$: Observable<AvailabilityDTO> = combineLatest([this.itemData$, this.branch$, this.isDownload$]).pipe(
|
||||
tap(() => this.patchState({ fetchingPickUpAvailability: true, fetchingPickUpAvailabilityError: undefined })),
|
||||
switchMap(([item, branch, isDownload]) =>
|
||||
!!item && !!branch && !isDownload
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
<a
|
||||
class="article"
|
||||
*ngFor="let recommendation of store.recommendations$ | async"
|
||||
[routerLink]="getDetailsPath(recommendation.product.ean)"
|
||||
[queryParams]="{ main_qs: recommendation.product.ean }"
|
||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'details', 'ean', recommendation.product.ean]"
|
||||
(click)="close.emit()"
|
||||
>
|
||||
<img [src]="recommendation.product?.ean | productImage: 195:315:true" alt="product-image" />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { ArticleDetailsStore } from '../article-details.store';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-recommendations',
|
||||
@@ -12,13 +11,5 @@ export class ArticleRecommendationsComponent {
|
||||
@Output()
|
||||
close = new EventEmitter<void>();
|
||||
|
||||
constructor(
|
||||
public readonly store: ArticleDetailsStore,
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _navigationService: ProductCatalogNavigationService
|
||||
) {}
|
||||
|
||||
getDetailsPath(ean?: string) {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this._applicationService.activatedProcessId, ean });
|
||||
}
|
||||
constructor(public readonly store: ArticleDetailsStore, public readonly applicationService: ApplicationService) {}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ import { ArticleSearchService } from './article-search.store';
|
||||
import { FocusSearchboxEvent } from './focus-searchbox.event';
|
||||
import { ArticleSearchMainAutocompleteProvider } from './providers';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { FilterAutocompleteProvider } from 'apps/shared/components/filter/src/lib';
|
||||
import { isEqual } from 'lodash';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { FilterAutocompleteProvider } from '@shared/components/filter';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-search',
|
||||
|
||||
@@ -7,9 +7,10 @@ import { SearchResultsModule } from './search-results/search-results.module';
|
||||
import { SearchMainModule } from './search-main/search-main.module';
|
||||
import { SearchFilterModule } from './search-filter/search-filter.module';
|
||||
import { ArticleSearchService } from './article-search.store';
|
||||
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule],
|
||||
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule, SharedFilterOverlayModule],
|
||||
exports: [ArticleSearchComponent],
|
||||
declarations: [ArticleSearchComponent],
|
||||
providers: [ArticleSearchService],
|
||||
|
||||
@@ -152,7 +152,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
search = this.effect((options$: Observable<{ clear?: boolean }>) =>
|
||||
options$.pipe(
|
||||
tap((options) => {
|
||||
this.searchStarted.next({ clear: options?.clear });
|
||||
this.patchState({
|
||||
searchState: 'fetching',
|
||||
items: options.clear ? [] : this.items,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[routerLink]="closeFilterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon icon="close" [size]="25"></shared-icon>
|
||||
<ui-icon icon="close" size="15px"></ui-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
@@ -15,6 +15,9 @@ import { Filter, FilterComponent } from 'apps/shared/components/filter/src/lib';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
close = new EventEmitter();
|
||||
|
||||
_processId$ = this._activatedRoute.parent.data.pipe(map((data) => Number(data.processId)));
|
||||
|
||||
fetching$: Observable<boolean>;
|
||||
@@ -30,10 +33,6 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get showFilterClose$() {
|
||||
return this._environment.matchDesktop$.pipe(map((state) => !(state?.matches && this.leftOutlet === 'search')));
|
||||
}
|
||||
@@ -73,21 +72,6 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
ngOnInit() {
|
||||
this.fetching$ = this.articleSearch.fetching$;
|
||||
this.filter$ = this.articleSearch.filter$.pipe(map((filter) => Filter.create(filter)));
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this.articleSearch.searchStarted.pipe(takeUntil(this._onDestroy$)).subscribe(async (_) => {
|
||||
let queryParams = {
|
||||
...this.articleSearch.filter.getQueryParams(),
|
||||
...this.cleanupQueryParams(this.uiFilterComponent?.uiFilter?.getQueryParams()),
|
||||
};
|
||||
|
||||
// Always override query if not in tablet mode
|
||||
if (!!this.articleSearch.filter.getQueryParams()?.main_qs && !this.isTablet) {
|
||||
queryParams = { ...queryParams, main_qs: this.articleSearch.filter.getQueryParams()?.main_qs };
|
||||
}
|
||||
|
||||
await this.articleSearch.setDefaultFilter(queryParams);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -97,6 +81,7 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
|
||||
applyFilter(value: Filter) {
|
||||
this.uiFilterComponent?.cancelAutocomplete();
|
||||
this.articleSearch.setFilter(value);
|
||||
this.articleSearch.search({ clear: true });
|
||||
this.articleSearch.searchCompleted
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
@@ -128,18 +113,4 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
const queryParams = { main_qs: value?.getQueryParams()?.main_qs || '' };
|
||||
this.articleSearch.setDefaultFilter(queryParams);
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||
if (clean[key] == undefined) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
import { ArticleSearchFilterComponent } from './search-filter.component';
|
||||
import { FilterModule } from '@shared/components/filter';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule, FilterModule, UiSpinnerModule, IconComponent],
|
||||
imports: [CommonModule, RouterModule, FilterNextModule, UiIconModule, UiSpinnerModule],
|
||||
exports: [ArticleSearchFilterComponent],
|
||||
declarations: [ArticleSearchFilterComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<div
|
||||
class="bg-white rounded py-10 px-4 text-center shadow-[0_-2px_24px_0_#dce2e9] h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)]"
|
||||
>
|
||||
<div class="bg-white rounded py-10 px-4 text-center shadow-[0_-2px_24px_0_#dce2e9] h-full">
|
||||
<h1 class="text-h3 text-[1.625rem] font-bold mb-[0.375rem]">Artikelsuche</h1>
|
||||
<p class="text-lg mb-10">
|
||||
Welchen Artikel suchen Sie?
|
||||
@@ -28,24 +26,24 @@
|
||||
[routerLink]="openFilterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
|
||||
Filter
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-start ml-12 desktop:ml-8 py-6 bg-white overflow-hidden h-[calc(100%-13.5rem)]">
|
||||
<div class="flex flex-col items-start ml-12 desktop:ml-8 py-6 bg-white">
|
||||
<h3 class="text-p3 font-bold mb-3">Deine letzten Suchanfragen</h3>
|
||||
<ul class="flex flex-col justify-start overflow-hidden overflow-y-scroll items-start m-0 p-0 bg-white w-full">
|
||||
<ul class="flex flex-col justify-start overflow-hidden items-start m-0 p-0 bg-white w-full">
|
||||
<li class="list-none pb-3" *ngFor="let recentQuery of history$ | async">
|
||||
<button
|
||||
class="flex flex-row items-center outline-none border-none bg-white text-black text-p2 m-0 p-0"
|
||||
(click)="setQueryHistory(filter, recentQuery.friendlyName)"
|
||||
>
|
||||
<shared-icon
|
||||
<ui-icon
|
||||
class="flex w-8 h-8 justify-center items-center mr-3 rounded-full text-black bg-[#edeff0]"
|
||||
icon="magnify"
|
||||
[size]="20"
|
||||
></shared-icon>
|
||||
icon="search"
|
||||
size="0.875rem"
|
||||
></ui-icon>
|
||||
<p class="m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-[25rem]">{{ recentQuery.friendlyName }}</p>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ProductCatalogNavigationService } from '@shared/services';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
readonly history$ = this.catalog.getSearchHistory({ take: 7 }).pipe(catchError(() => NEVER));
|
||||
readonly history$ = this.catalog.getSearchHistory({ take: 5 }).pipe(catchError(() => NEVER));
|
||||
|
||||
fetching$ = this.searchService.fetching$;
|
||||
|
||||
@@ -112,17 +112,6 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this.subscriptions.add(
|
||||
this.searchService.searchStarted.subscribe(async (_) => {
|
||||
const queryParams = {
|
||||
...this.cleanupQueryParams(this.searchService.filter.getQueryParams()),
|
||||
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
|
||||
};
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { ArticleSearchMainComponent } from './search-main.component';
|
||||
import { FilterModule } from '@shared/components/filter';
|
||||
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { IconComponent, IconModule } from '@shared/components/icon';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, RouterModule, IconComponent, FilterModule, IconModule],
|
||||
imports: [CommonModule, RouterModule, UiIconModule, FilterNextModule],
|
||||
exports: [ArticleSearchMainComponent],
|
||||
declarations: [ArticleSearchMainComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { UiModalRef } from '@ui/modal';
|
||||
|
||||
@Component({
|
||||
@@ -12,27 +10,18 @@ import { UiModalRef } from '@ui/modal';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddedToCartModalComponent {
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
constructor(
|
||||
public ref: UiModalRef<any, any>,
|
||||
private readonly _router: Router,
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _navigation: ProductCatalogNavigationService,
|
||||
private readonly _environment: EnvironmentService,
|
||||
private readonly _checkoutNavigationService: CheckoutNavigationService
|
||||
private readonly _applicationService: ApplicationService
|
||||
) {}
|
||||
async continue() {
|
||||
if (this.isTablet) {
|
||||
await this._navigation.navigateToProductSearch({ processId: this._applicationService.activatedProcessId });
|
||||
}
|
||||
continue() {
|
||||
this._router.navigate([`/kunde/${this._applicationService.activatedProcessId}/product/search`]);
|
||||
this.ref.close();
|
||||
}
|
||||
|
||||
async toCart() {
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: this._applicationService.activatedProcessId });
|
||||
toCart() {
|
||||
this._router.navigate([`/kunde/${this._applicationService.activatedProcessId}/cart/review`]);
|
||||
this.ref.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ng-container *ngIf="!mainOutletActive; else mainOutlet">
|
||||
<ng-container *ngIf="!(mainOutletActive$ | async); else mainOutlet">
|
||||
<div class="bg-ucla-blue rounded w-[4.375rem] h-[5.625rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-col flex-grow">
|
||||
<div class="h-4 bg-ucla-blue ml-4 mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy, HostBinding, Input } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, HostBinding } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-search-result-item-loading',
|
||||
@@ -7,12 +10,13 @@ import { Component, ChangeDetectionStrategy, HostBinding, Input } from '@angular
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SearchResultItemLoadingComponent {
|
||||
@Input()
|
||||
mainOutletActive?: boolean = false;
|
||||
get mainOutletActive$() {
|
||||
return this._navigationService?.mainOutletActive$(this._activatedRoute).pipe(shareReplay());
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
constructor(private _navigationService: ProductCatalogNavigationService, private _activatedRoute: ActivatedRoute) {}
|
||||
|
||||
@HostBinding('style') get class() {
|
||||
return this.mainOutletActive ? { height: '6.125rem' } : '';
|
||||
return this._navigationService.mainOutletActive(this._activatedRoute) ? { height: '6.125rem' } : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<a
|
||||
class="page-search-result-item__item-card hover p-5 desktop-small:px-4 desktop-small:py-[0.625rem] h-[13.25rem] desktop-small:h-[11.3125rem] bg-white border border-solid border-transparent rounded"
|
||||
[class.page-search-result-item__item-card-main]="mainOutletActive"
|
||||
[class.page-search-result-item__item-card-main]="mainOutletActive$ | async"
|
||||
[routerLink]="detailsPath"
|
||||
[routerLinkActive]="!isTablet && !mainOutletActive ? 'active' : ''"
|
||||
[routerLinkActive]="!isTablet && !(mainOutletActive$ | async) ? 'active' : ''"
|
||||
[queryParamsHandling]="!isTablet ? 'preserve' : ''"
|
||||
(click)="isDesktop ? scrollIntoView() : ''"
|
||||
>
|
||||
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[50px] h-[79px]">
|
||||
<img
|
||||
@@ -16,7 +15,10 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="page-search-result-item__item-grid-container" [class.page-search-result-item__item-grid-container-main]="mainOutletActive">
|
||||
<div
|
||||
class="page-search-result-item__item-grid-container"
|
||||
[class.page-search-result-item__item-grid-container-main]="mainOutletActive$ | async"
|
||||
>
|
||||
<div
|
||||
class="page-search-result-item__item-contributors desktop-small:text-p3 font-bold text-[#0556B4] text-ellipsis overflow-hidden max-w-[24rem] whitespace-nowrap"
|
||||
>
|
||||
@@ -65,7 +67,7 @@
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-price desktop-small:text-p3 font-bold justify-self-end"
|
||||
[class.page-search-result-item__item-price-main]="mainOutletActive"
|
||||
[class.page-search-result-item__item-price-main]="mainOutletActive$ | async"
|
||||
>
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
||||
</div>
|
||||
@@ -83,7 +85,7 @@
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-stock desktop-small:text-p3 font-bold z-dropdown justify-self-start"
|
||||
[class.justify-self-end]="!mainOutletActive"
|
||||
[class.justify-self-end]="!(mainOutletActive$ | async)"
|
||||
[uiOverlayTrigger]="tooltip"
|
||||
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
|
||||
>
|
||||
@@ -113,9 +115,9 @@
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-ssc desktop-small:text-p3 w-full text-right overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
[class.page-search-result-item__item-ssc-main]="mainOutletActive"
|
||||
[class.page-search-result-item__item-ssc-main]="mainOutletActive$ | async"
|
||||
>
|
||||
<div class="hidden" [class.page-search-result-item__item-ssc-tooltip]="mainOutletActive">
|
||||
<div class="hidden" [class.page-search-result-item__item-ssc-tooltip]="mainOutletActive$ | async">
|
||||
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
|
||||
</div>
|
||||
<strong>{{ item?.catalogAvailability?.ssc }}</strong> - {{ item?.catalogAvailability?.sscText }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding, ElementRef } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostListener, HostBinding } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
|
||||
@@ -8,9 +8,10 @@ import { ItemDTO } from '@swagger/cat';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { debounceTime, switchMap, map, shareReplay, filter } from 'rxjs/operators';
|
||||
import { debounceTime, switchMap, map, shareReplay, filter, first } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
export interface SearchResultItemComponentState {
|
||||
item?: ItemDTO;
|
||||
@@ -50,9 +51,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
mainOutletActive?: boolean = false;
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<ItemDTO>();
|
||||
|
||||
@@ -77,10 +75,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
|
||||
get detailsPath() {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: this.item?.id });
|
||||
}
|
||||
@@ -89,6 +83,10 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId);
|
||||
}
|
||||
|
||||
get mainOutletActive$() {
|
||||
return this._navigationService?.mainOutletActive$(this._activatedRoute).pipe(shareReplay());
|
||||
}
|
||||
|
||||
defaultBranch$ = this._availability.getDefaultBranch();
|
||||
|
||||
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
|
||||
@@ -99,7 +97,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
const branch = selectedBranch ?? defaultBranch;
|
||||
return branch.branchType !== 4;
|
||||
})
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
stockTooltipText$ = combineLatest([this.defaultBranch$, this.selectedBranchId$]).pipe(
|
||||
@@ -115,7 +114,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
}
|
||||
}
|
||||
return '';
|
||||
})
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
inStock$ = combineLatest([this.item$, this.selectedBranchId$, this.defaultBranch$]).pipe(
|
||||
@@ -123,7 +123,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
filter(([item, branch, defaultBranch]) => !!item && !!defaultBranch),
|
||||
switchMap(([item, branch, defaultBranch]) =>
|
||||
this._stockService.getInStock$({ itemId: item.id, branchId: branch?.id ?? defaultBranch?.id })
|
||||
)
|
||||
),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
constructor(
|
||||
@@ -135,7 +136,7 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _elRef: ElementRef<HTMLElement>
|
||||
private _activatedRoute: ActivatedRoute
|
||||
) {
|
||||
super({
|
||||
selected: false,
|
||||
@@ -143,21 +144,16 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
});
|
||||
}
|
||||
|
||||
scrollIntoView() {
|
||||
this._elRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
setSelected() {
|
||||
const isSelected = this._articleSearchService.selectedItemIds.includes(this.item?.id);
|
||||
this._articleSearchService.setSelected({ selected: !isSelected, itemId: this.item?.id });
|
||||
|
||||
// #4135 Code auskommentiert bis zur Klärung
|
||||
// if (!this.isTablet) {
|
||||
// this.selectedChange.emit(this.item);
|
||||
// }
|
||||
if (!this.isTablet) {
|
||||
this.selectedChange.emit(this.item);
|
||||
}
|
||||
}
|
||||
|
||||
@HostBinding('style') get class() {
|
||||
return this.mainOutletActive ? { height: '6.125rem' } : '';
|
||||
return this._navigationService.mainOutletActive(this._activatedRoute) ? { height: '6.125rem' } : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[routerLink]="filterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
|
||||
Filter
|
||||
</a>
|
||||
</div>
|
||||
@@ -47,40 +47,32 @@
|
||||
</shared-order-by-filter>
|
||||
</div>
|
||||
|
||||
<div class="h-full relative">
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="product-list h-full"
|
||||
[itemSize]="(mainOutletActive$ | async) ? 98 : 181"
|
||||
minBufferPx="1200"
|
||||
[maxBufferPx]="maxBufferCdkScrollContainer$ | async"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
[class.page-search-results__result-item-main]="mainOutletActive$ | async"
|
||||
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[mainOutletActive]="mainOutletActive$ | async"
|
||||
></search-result-item>
|
||||
<page-search-result-item-loading
|
||||
[mainOutletActive]="mainOutletActive$ | async"
|
||||
*ngIf="fetching$ | async"
|
||||
></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="product-list"
|
||||
[itemSize]="(mainOutletActive$ | async) ? 98 : 187"
|
||||
minBufferPx="2800"
|
||||
maxBufferPx="2800"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
[class.page-search-results__result-item-main]="mainOutletActive$ | async"
|
||||
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
></search-result-item>
|
||||
<page-search-result-item-loading *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<!-- #4135 Code auskommentiert bis zur Klärung -->
|
||||
<!-- <div *ngIf="isTablet" class="actions z-fixed"> -->
|
||||
<div *ngIf="isTablet" class="actions z-fixed">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply flex sticky bottom-10 items-center justify-center;
|
||||
@apply fixed bottom-16 inline-grid grid-flow-col gap-7;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
.cta-cart {
|
||||
@apply border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none self-end whitespace-nowrap no-underline;
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
QueryList,
|
||||
TrackByFunction,
|
||||
AfterViewInit,
|
||||
} from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, ViewChildren, QueryList, TrackByFunction } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
@@ -21,7 +11,7 @@ import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { CacheService } from 'apps/core/cache/src/public-api';
|
||||
import { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||
import { debounceTime, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { debounceTime, first, map, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
||||
import { SearchResultItemComponent } from './search-result-item.component';
|
||||
@@ -34,7 +24,7 @@ import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/fi
|
||||
styleUrls: ['search-results.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
@ViewChildren(SearchResultItemComponent) listItems: QueryList<SearchResultItemComponent>;
|
||||
@ViewChild('scrollContainer', { static: true })
|
||||
scrollContainer: CdkVirtualScrollViewport;
|
||||
@@ -68,10 +58,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
|
||||
hasFilter$ = combineLatest([this.searchService.filter$, this.searchService.defaultSettings$]).pipe(
|
||||
map(([filter, defaultFilter]) => {
|
||||
const filterQueryParams = filter?.getQueryParams();
|
||||
@@ -88,27 +74,9 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
|
||||
get mainOutletActive$() {
|
||||
return this._environment.matchTablet$.pipe(map((state) => this._navigationService.mainOutletActive(this.route, state?.matches)));
|
||||
return this._navigationService?.mainOutletActive$(this.route).pipe(shareReplay());
|
||||
}
|
||||
|
||||
get rightOutletLocation() {
|
||||
return this._navigationService.getOutletLocations(this.route).right;
|
||||
}
|
||||
|
||||
// Ticket #4169 Splitscreen
|
||||
// Render genug Artikel um bei Navigation auf Trefferliste | PDP zum angewählten Artikel zu Scrollen
|
||||
maxBufferCdkScrollContainer$ = this.results$.pipe(
|
||||
withLatestFrom(this.mainOutletActive$),
|
||||
map(([results, mainOutlet]) => {
|
||||
if (!mainOutlet && results?.length > 0) {
|
||||
// Splitscreen mode: Items Length * Item Pixel Height
|
||||
return results.length * 181;
|
||||
} else {
|
||||
return 1200;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
constructor(
|
||||
public searchService: ArticleSearchService,
|
||||
private route: ActivatedRoute,
|
||||
@@ -158,22 +126,15 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
||||
if (this.rightOutletLocation !== 'filter') {
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
}
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
const data = this.getCachedData(processId, queryParams, selectedBranch?.id);
|
||||
if (data.items?.length > 0) {
|
||||
this.searchService.setItems(data.items);
|
||||
this.searchService.setHits(data.hits);
|
||||
}
|
||||
if (data.items?.length === 0 && this.rightOutletLocation !== 'filter') {
|
||||
this.searchService.setItems(data.items);
|
||||
this.searchService.setHits(data.hits);
|
||||
|
||||
if (data.items?.length === 0) {
|
||||
this.search();
|
||||
} else {
|
||||
if (!this.isDesktop || this._navigationService.mainOutletActive(this.route)) {
|
||||
this.scrollTop(Number(queryParams.scroll_position ?? 0));
|
||||
} else {
|
||||
this.scrollItemIntoView();
|
||||
}
|
||||
this.scrollTop(Number(queryParams.scroll_position ?? 0));
|
||||
const selectedItemIds: Array<string> = queryParams?.selected_item_ids?.split(',') ?? [];
|
||||
for (const id of selectedItemIds) {
|
||||
if (id) {
|
||||
@@ -183,12 +144,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
}
|
||||
|
||||
const process = await this.application.getProcessById$(processId).pipe(first()).toPromise();
|
||||
if (!!process) {
|
||||
await this.updateBreadcrumbs(processId, queryParams);
|
||||
await this.createBreadcrumb(processId, queryParams);
|
||||
}
|
||||
|
||||
await this.updateBreadcrumbs(processId, queryParams);
|
||||
await this.createBreadcrumb(processId, queryParams);
|
||||
if (this.isTablet || this.route?.outlet === 'main') {
|
||||
await this.removeDetailsBreadcrumb(processId);
|
||||
}
|
||||
@@ -201,23 +158,11 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
const params = state.filter.getQueryParams();
|
||||
if ((state.hits === 1 && this.isTablet) || (!this.isTablet && !this._navigationService.mainOutletActive(this.route))) {
|
||||
const item = state.items.find((f) => f);
|
||||
const ean = this.route?.snapshot?.params?.ean;
|
||||
const itemId = this.route?.snapshot?.params?.id ? Number(this.route?.snapshot?.params?.id) : item.id; // Nicht zum ersten Item der Liste springen wenn bereits eines selektiert ist
|
||||
|
||||
// Navigation from Cart uses ean
|
||||
if (!!ean) {
|
||||
this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
ean,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
} else {
|
||||
this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
itemId,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
}
|
||||
this._navigationService.navigateToDetails({
|
||||
processId,
|
||||
itemId: item.id,
|
||||
queryParams: this.isTablet ? undefined : params,
|
||||
});
|
||||
} else if (this.isTablet || this._navigationService.mainOutletActive(this.route)) {
|
||||
this._navigationService.navigateToResults({
|
||||
processId,
|
||||
@@ -227,24 +172,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this.subscriptions.add(
|
||||
this.searchService.searchStarted.subscribe(async (options) => {
|
||||
if (!options?.clear) {
|
||||
const queryParams = {
|
||||
...this.cleanupQueryParams(this.searchService.filter.getQueryParams()),
|
||||
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
|
||||
};
|
||||
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.scrollItemIntoView();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -272,20 +199,22 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
search(filter?: Filter) {
|
||||
if (!!filter) {
|
||||
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||
this.searchService.setFilter(filter);
|
||||
}
|
||||
|
||||
this.searchService.search({ clear: true });
|
||||
}
|
||||
|
||||
scrollTop(scrollPos: number) {
|
||||
setTimeout(() => this.scrollContainer.scrollTo({ top: scrollPos }), 0);
|
||||
async removeDetailBreadcrumb(processId: number) {
|
||||
const crumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'details']).pipe(first()).toPromise();
|
||||
|
||||
for (const crumb of crumbs) {
|
||||
this.breadcrumb.removeBreadcrumb(crumb.id);
|
||||
}
|
||||
}
|
||||
|
||||
scrollItemIntoView() {
|
||||
setTimeout(() => {
|
||||
const item = this.listItems?.find((item) => item.item.id === Number(this.route?.snapshot?.params?.id));
|
||||
item?.scrollIntoView();
|
||||
}, 0);
|
||||
scrollTop(scrollPos: number) {
|
||||
setTimeout(() => this.scrollContainer.scrollTo({ top: scrollPos }), 0);
|
||||
}
|
||||
|
||||
async scrolledIndexChange(index: number) {
|
||||
@@ -332,7 +261,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
await this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name,
|
||||
path: this._navigationService.getArticleSearchResultsPath(processId),
|
||||
path: this._navigationService.getArticleSearchResultsPath(this.application.activatedProcessId),
|
||||
params: queryParams,
|
||||
section: 'customer',
|
||||
tags: ['catalog', 'filter', 'results'],
|
||||
|
||||
@@ -15,10 +15,9 @@ import { SearchResultItemLoadingComponent } from './search-result-item-loading.c
|
||||
import { SearchResultItemComponent } from './search-result-item.component';
|
||||
import { ArticleSearchResultsComponent } from './search-results.component';
|
||||
import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe';
|
||||
import { FilterAutocompleteProvider, FilterModule, OrderByFilterModule } from '@shared/components/filter';
|
||||
import { FilterAutocompleteProvider, FilterNextModule, OrderByFilterModule } from 'apps/shared/components/filter/src/lib';
|
||||
import { FocusSearchboxEvent } from '../focus-searchbox.event';
|
||||
import { ArticleSearchMainAutocompleteProvider } from '../providers';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -33,8 +32,7 @@ import { IconComponent } from '@shared/components/icon';
|
||||
OrderByFilterModule,
|
||||
ScrollingModule,
|
||||
UiTooltipModule,
|
||||
FilterModule,
|
||||
IconComponent,
|
||||
FilterNextModule,
|
||||
],
|
||||
exports: [ArticleSearchResultsComponent, SearchResultItemComponent],
|
||||
declarations: [
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Subject } from 'rxjs';
|
||||
import { first, shareReplay, takeUntil } from 'rxjs/operators';
|
||||
import { CheckoutDummyData } from './checkout-dummy-data';
|
||||
import { CheckoutDummyStore } from './checkout-dummy.store';
|
||||
import { CheckoutNavigationService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-dummy',
|
||||
@@ -44,8 +43,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
private _modal: UiModalService,
|
||||
private _store: CheckoutDummyStore,
|
||||
private _ref: UiModalRef<any, CheckoutDummyData>,
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutNavigationService: CheckoutNavigationService
|
||||
private readonly _applicationService: ApplicationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -200,7 +198,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
queryParams: { customertype: filter.customertype },
|
||||
});
|
||||
} else {
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: this._applicationService.activatedProcessId });
|
||||
this._router.navigate(['/kunde', this._applicationService.activatedProcessId, 'cart', 'review']);
|
||||
}
|
||||
this._ref?.close();
|
||||
});
|
||||
@@ -217,7 +215,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
queryParams: { customertype: filter.customertype },
|
||||
});
|
||||
} else {
|
||||
await this._checkoutNavigationService.navigateToCheckoutReview({ processId: this._applicationService.activatedProcessId });
|
||||
this._router.navigate(['/kunde', this._applicationService.activatedProcessId, 'cart', 'review']);
|
||||
}
|
||||
this._ref?.close();
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<ng-container *ngIf="(groupedItems$ | async)?.length <= 0 && !(fetching$ | async); else shoppingCart">
|
||||
<div class="card stretch card-empty">
|
||||
<div class="empty-message">
|
||||
<span class="cart-icon flex items-center justify-center">
|
||||
<shared-icon icon="shopping-cart-bold" [size]="24"></shared-icon>
|
||||
<span class="cart-icon">
|
||||
<ui-icon icon="cart" size="16px"></ui-icon>
|
||||
</span>
|
||||
|
||||
<h1>Ihr Warenkorb ist leer.</h1>
|
||||
@@ -13,7 +13,7 @@
|
||||
</p>
|
||||
|
||||
<div class="btn-wrapper">
|
||||
<a class="cta-primary" [routerLink]="productSearchBasePath">Artikel suchen</a>
|
||||
<a class="cta-primary" [routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'search']">Artikel suchen</a>
|
||||
<button class="cta-secondary" (click)="openDummyModal()">Neuanlage</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,75 +33,110 @@
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="header">Warenkorb</h1>
|
||||
<h5 class="sub-header">Überprüfen Sie die Details.</h5>
|
||||
|
||||
<ng-container *ngIf="!(isDesktop$ | async)">
|
||||
<page-checkout-review-details></page-checkout-review-details>
|
||||
<ng-container *ngIf="payer$ | async">
|
||||
<hr />
|
||||
<div class="row">
|
||||
<ng-container *ngIf="showBillingAddress$ | async; else customerName">
|
||||
<div class="label">
|
||||
Rechnungsadresse
|
||||
</div>
|
||||
<div class="value">
|
||||
{{ payer$ | async | payerAddress | trim: 55 }}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #customerName>
|
||||
<div class="label">
|
||||
Name, Vorname
|
||||
</div>
|
||||
<div class="value" *ngIf="payer$ | async; let payer">{{ payer.lastName }}, {{ payer.firstName }}</div>
|
||||
</ng-template>
|
||||
|
||||
<div class="grow"></div>
|
||||
<div>
|
||||
<button *ngIf="payer$ | async" (click)="changeAddress()" class="cta-edit">
|
||||
Ändern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<hr />
|
||||
<ng-container *ngIf="showNotificationChannels$ | async">
|
||||
<form *ngIf="control" [formGroup]="control">
|
||||
<shared-notification-channel-control
|
||||
[communicationDetails]="communicationDetails$ | async"
|
||||
(channelActionEvent)="onNotificationChange($event)"
|
||||
[channelActionName]="'Speichern'"
|
||||
[channelActionLoading]="notificationChannelLoading$ | async"
|
||||
formGroupName="notificationChannel"
|
||||
>
|
||||
</shared-notification-channel-control>
|
||||
</form>
|
||||
<hr />
|
||||
</ng-container>
|
||||
<page-special-comment [ngModel]="specialComment$ | async" (ngModelChange)="setAgentComment($event)"> </page-special-comment>
|
||||
<ng-container *ngFor="let group of groupedItems$ | async; let lastGroup = last">
|
||||
<ng-container *ngIf="group?.orderType !== undefined">
|
||||
<hr />
|
||||
<div class="row item-group-header bg-[#F5F7FA]">
|
||||
<shared-icon
|
||||
<div class="row item-group-header">
|
||||
<ui-icon
|
||||
*ngIf="group.orderType !== 'Dummy'"
|
||||
class="icon-order-type"
|
||||
[size]="group.orderType === 'B2B-Versand' ? 36 : 24"
|
||||
[size]="group.orderType === 'B2B-Versand' ? '50px' : '25px'"
|
||||
[icon]="
|
||||
group.orderType === 'Abholung'
|
||||
? 'isa-box-out'
|
||||
? 'box_out'
|
||||
: group.orderType === 'Versand'
|
||||
? 'isa-truck'
|
||||
? 'truck'
|
||||
: group.orderType === 'Rücklage'
|
||||
? 'isa-shopping-bag'
|
||||
? 'shopping_bag'
|
||||
: group.orderType === 'B2B-Versand'
|
||||
? 'isa-b2b-truck'
|
||||
? 'truck_b2b'
|
||||
: group.orderType === 'Download'
|
||||
? 'isa-download'
|
||||
: 'isa-truck'
|
||||
? 'download'
|
||||
: 'truck'
|
||||
"
|
||||
></shared-icon>
|
||||
|
||||
></ui-icon>
|
||||
<div class="label" [class.dummy]="group.orderType === 'Dummy'">
|
||||
{{ group.orderType !== 'Dummy' ? group.orderType : 'Manuelle Anlage / Dummy Bestellung' }}
|
||||
<button
|
||||
*ngIf="group.orderType === 'Dummy'"
|
||||
class="text-brand border-none font-bold text-p1 outline-none pl-4"
|
||||
(click)="openDummyModal()"
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
<button *ngIf="group.orderType === 'Dummy'" class="cta-secondary" (click)="openDummyModal()">Hinzufügen</button>
|
||||
</div>
|
||||
|
||||
<div class="grow"></div>
|
||||
<div class="pl-4" *ngIf="group.orderType !== 'Download' && group.orderType !== 'Dummy'">
|
||||
<div *ngIf="group.orderType !== 'Download' && group.orderType !== 'Dummy'">
|
||||
<button class="cta-edit" (click)="showPurchasingListModal(group.items)">
|
||||
Lieferung Ändern
|
||||
Ändern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr *ngIf="group.orderType === 'Download'" />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="group.orderType === 'Versand' || group.orderType === 'B2B-Versand' || group.orderType === 'DIG-Versand'">
|
||||
<div class="flex flex-row items-center px-5 pt-0 pb-[0.875rem] -mt-2 bg-[#F5F7FA]">
|
||||
<div class="text-p2">
|
||||
{{ shippingAddress$ | async | shippingAddress }}
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="label">
|
||||
Lieferadresse
|
||||
</div>
|
||||
<div class="value">
|
||||
{{ shippingAddress$ | async | shippingAddress | trim: 55 }}
|
||||
</div>
|
||||
<div class="grow"></div>
|
||||
<div class="pl-4">
|
||||
<div>
|
||||
<button (click)="changeAddress()" class="cta-edit">
|
||||
Adresse Ändern
|
||||
Ändern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
</ng-container>
|
||||
<hr />
|
||||
<ng-container *ngFor="let item of group.items; let lastItem = last; let i = index">
|
||||
<ng-container
|
||||
*ngIf="group?.orderType !== undefined && (item.features?.orderType === 'Abholung' || item.features?.orderType === 'Rücklage')"
|
||||
>
|
||||
<ng-container *ngIf="item?.destination?.data?.targetBranch?.data; let targetBranch">
|
||||
<ng-container *ngIf="i === 0 || targetBranch.id !== group.items[i - 1].destination?.data?.targetBranch?.data.id">
|
||||
<div class="flex flex-row items-center px-5 pt-0 pb-[0.875rem] -mt-2 bg-[#F5F7FA]">
|
||||
<div class="row">
|
||||
<span class="branch-label">Filiale</span>
|
||||
<span class="branch-name">{{ targetBranch?.name }} | {{ targetBranch | branchAddress }}</span>
|
||||
</div>
|
||||
<hr />
|
||||
@@ -123,35 +158,35 @@
|
||||
<hr *ngIf="!lastItem" />
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<div class="h-[8.9375rem]"></div>
|
||||
</div>
|
||||
<div class="card footer flex flex-col justify-center items-center">
|
||||
<div class="flex flex-row items-start justify-between w-full mb-1">
|
||||
<ng-container *ngIf="totalItemCount$ | async; let totalItemCount">
|
||||
<div *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="total-item-reading-points w-full">
|
||||
{{ totalItemCount }} Artikel | {{ totalReadingPoints }} Lesepunkte
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="card footer row">
|
||||
<ng-container *ngIf="totalItemCount$ | async; let totalItemCount">
|
||||
<div *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="total-item-reading-points">
|
||||
{{ totalItemCount }} Artikel | {{ totalReadingPoints }} Lesepunkte
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="grow"></div>
|
||||
<div class="total-cta-container">
|
||||
<div class="total-container">
|
||||
<strong class="total-value">
|
||||
Zwischensumme {{ shoppingCart?.total?.value | currency: shoppingCart?.total?.currency:'code' }}
|
||||
</strong>
|
||||
<span class="shipping-cost-info">ohne Versandkosten</span>
|
||||
</div>
|
||||
<button
|
||||
class="cta-primary"
|
||||
(click)="order()"
|
||||
[disabled]="
|
||||
showOrderButtonSpinner ||
|
||||
((primaryCtaLabel$ | async) === 'Bestellen' && !(checkNotificationChannelControl$ | async)) ||
|
||||
control.invalid
|
||||
"
|
||||
>
|
||||
<ui-spinner [show]="showOrderButtonSpinner">
|
||||
{{ primaryCtaLabel$ | async }}
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="cta-primary"
|
||||
(click)="order()"
|
||||
[disabled]="
|
||||
showOrderButtonSpinner ||
|
||||
((primaryCtaLabel$ | async) === 'Bestellen' && !(checkNotificationChannelControl$ | async)) ||
|
||||
notificationsControl?.invalid
|
||||
"
|
||||
>
|
||||
<ui-spinner [show]="showOrderButtonSpinner">
|
||||
{{ primaryCtaLabel$ | async }}
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
:host {
|
||||
@apply box-border relative block h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
@apply block box-border relative;
|
||||
height: calc(100vh - 285px);
|
||||
}
|
||||
|
||||
.stretch {
|
||||
@apply overflow-scroll h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
@apply overflow-scroll;
|
||||
height: 100vh;
|
||||
max-height: calc(100vh - 390px);
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -42,8 +45,10 @@ button {
|
||||
}
|
||||
|
||||
.cart-icon {
|
||||
@apply justify-center items-center ml-auto mr-auto bg-wild-blue-yonder text-white mb-px-10 w-10 h-10;
|
||||
@apply justify-center items-center ml-auto mr-auto bg-wild-blue-yonder text-white mb-px-10;
|
||||
border-radius: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
ui-icon {
|
||||
@apply justify-center;
|
||||
@@ -53,7 +58,7 @@ button {
|
||||
}
|
||||
|
||||
.cta-print-wrapper {
|
||||
@apply px-5 pt-5 text-right;
|
||||
@apply pl-4 pr-4 pt-4 text-right;
|
||||
}
|
||||
|
||||
.cta-print,
|
||||
@@ -78,12 +83,16 @@ button {
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply text-center text-h2 desktop:pb-10 -mt-2;
|
||||
@apply text-center text-3xl my-0 mb-2;
|
||||
}
|
||||
|
||||
.sub-header {
|
||||
@apply text-center text-h3 font-normal my-0 mb-8;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 2px;
|
||||
@apply bg-[#EDEFF0];
|
||||
@apply bg-disabled-customer;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@@ -112,11 +121,12 @@ h1 {
|
||||
}
|
||||
|
||||
.icon-order-type {
|
||||
@apply text-black mr-2;
|
||||
@apply text-font-customer mr-3;
|
||||
}
|
||||
|
||||
.item-group-header {
|
||||
@apply px-5 py-[0.875rem] text-p1;
|
||||
@apply py-0 px-4 text-lg;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.branch-label {
|
||||
@@ -124,7 +134,7 @@ h1 {
|
||||
}
|
||||
|
||||
.branch-name {
|
||||
@apply text-p2 overflow-hidden overflow-ellipsis;
|
||||
@apply text-p2 overflow-hidden overflow-ellipsis ml-4;
|
||||
}
|
||||
|
||||
.book-icon {
|
||||
@@ -133,18 +143,26 @@ h1 {
|
||||
}
|
||||
|
||||
.footer {
|
||||
@apply absolute bottom-0 left-0 right-0 p-5;
|
||||
@apply absolute bottom-0 left-0 right-0 p-7;
|
||||
box-shadow: 0px -2px 24px 0px #dce2e9;
|
||||
}
|
||||
|
||||
.total-container {
|
||||
@apply flex flex-col ml-4;
|
||||
}
|
||||
|
||||
.total-cta-container {
|
||||
@apply flex flex-row whitespace-nowrap;
|
||||
}
|
||||
|
||||
.shipping-cost-info {
|
||||
@apply text-p3 self-end;
|
||||
@apply text-p3 mr-4 self-end;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
@apply text-p1 self-end;
|
||||
@apply text-lg mr-4;
|
||||
}
|
||||
|
||||
.total-item-reading-points {
|
||||
@apply text-p2 font-bold text-black;
|
||||
@apply text-p2 font-bold text-ucla-blue;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { AvailabilityDTO, DestinationDTO, NotificationChannel, ShoppingCartItemDTO, ShoppingCartDTO } from '@swagger/checkout';
|
||||
import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
||||
import { first, map, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { first, map, shareReplay, switchMap, take, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
import { CheckoutDummyComponent } from '../checkout-dummy/checkout-dummy.component';
|
||||
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/components/notification-channel-control';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { CheckoutDummyData } from '../checkout-dummy/checkout-dummy-data';
|
||||
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { CheckoutReviewStore } from './checkout-review.store';
|
||||
|
||||
export interface CheckoutReviewComponentState {
|
||||
shoppingCart: ShoppingCartDTO;
|
||||
shoppingCartItems: ShoppingCartItemDTO[];
|
||||
fetching: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-review',
|
||||
@@ -24,27 +31,53 @@ import { CheckoutReviewStore } from './checkout-review.store';
|
||||
styleUrls: ['checkout-review.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
payer$ = this._store.payer$;
|
||||
export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewComponentState> implements OnInit {
|
||||
private _orderCompleted = new Subject<void>();
|
||||
|
||||
shoppingCart$ = this._store.shoppingCart$;
|
||||
checkNotificationChannelControl$ = new BehaviorSubject<boolean>(true);
|
||||
|
||||
fetching$ = this._store.fetching$;
|
||||
get shoppingCart() {
|
||||
return this.get((s) => s.shoppingCart);
|
||||
}
|
||||
set shoppingCart(shoppingCart: ShoppingCartDTO) {
|
||||
this.patchState({ shoppingCart });
|
||||
}
|
||||
readonly shoppingCart$ = this.select((s) => s.shoppingCart);
|
||||
|
||||
notificationsControl = this._store.notificationsControl;
|
||||
get shoppingCartItems() {
|
||||
return this.get((s) => s.shoppingCartItems);
|
||||
}
|
||||
set shoppingCartItems(shoppingCartItems: ShoppingCartItemDTO[]) {
|
||||
this.patchState({ shoppingCartItems });
|
||||
}
|
||||
readonly shoppingCartItems$ = this.select((s) => s.shoppingCartItems);
|
||||
|
||||
get fetching() {
|
||||
return this.get((s) => s.fetching);
|
||||
}
|
||||
set fetching(fetching: boolean) {
|
||||
this.patchState({ fetching });
|
||||
}
|
||||
readonly fetching$ = this.select((s) => s.fetching);
|
||||
|
||||
payer$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getPayer({ processId })),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
shippingAddress$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getShippingAddress({ processId }))
|
||||
);
|
||||
|
||||
shoppingCartItemsWithoutOrderType$ = this._store.shoppingCartItems$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
shoppingCartItemsWithoutOrderType$ = this.shoppingCartItems$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map((items) => items?.filter((item) => item?.features?.orderType === undefined))
|
||||
);
|
||||
|
||||
groupedItems$ = this._store.shoppingCartItems$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
groupedItems$ = this.shoppingCartItems$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map((items) =>
|
||||
items.reduce((grouped, item) => {
|
||||
let index = grouped.findIndex((g) =>
|
||||
@@ -85,12 +118,16 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
);
|
||||
|
||||
totalItemCount$ = this._store.shoppingCartItems$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
specialComment$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.domainCheckoutService.getSpecialComment({ processId }))
|
||||
);
|
||||
|
||||
totalItemCount$ = this.shoppingCartItems$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map((items) => items.reduce((total, item) => total + item.quantity, 0))
|
||||
);
|
||||
|
||||
totalReadingPoints$ = this._store.shoppingCartItems$.pipe(
|
||||
totalReadingPoints$ = this.shoppingCartItems$.pipe(
|
||||
switchMap((displayOrders) => {
|
||||
if (displayOrders.length === 0) {
|
||||
return NEVER;
|
||||
@@ -118,9 +155,47 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
customerFeatures$ = this._store.customerFeatures$;
|
||||
customerFeatures$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getCustomerFeatures({ processId }))
|
||||
);
|
||||
|
||||
checkNotificationChannelControl$ = this._store.checkNotificationChannelControl$;
|
||||
control: UntypedFormGroup;
|
||||
|
||||
showBillingAddress$ = this.shoppingCartItems$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
withLatestFrom(this.customerFeatures$),
|
||||
map(
|
||||
([items, customerFeatures]) =>
|
||||
items.some(
|
||||
(item) =>
|
||||
item.features?.orderType === 'Versand' ||
|
||||
item.features?.orderType === 'B2B-Versand' ||
|
||||
item.features?.orderType === 'DIG-Versand'
|
||||
) || !!customerFeatures?.b2b
|
||||
)
|
||||
);
|
||||
|
||||
showNotificationChannels$ = combineLatest([this.shoppingCartItems$, this.payer$]).pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
map(
|
||||
([items, payer]) =>
|
||||
!!payer && items.some((item) => item.features?.orderType === 'Rücklage' || item.features?.orderType === 'Abholung')
|
||||
)
|
||||
);
|
||||
|
||||
notificationChannel$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getNotificationChannels({ processId }))
|
||||
);
|
||||
|
||||
communicationDetails$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
switchMap((processId) => this.domainCheckoutService.getBuyerCommunicationDetails({ processId })),
|
||||
map((communicationDetails) => communicationDetails ?? { email: undefined, mobile: undefined })
|
||||
);
|
||||
|
||||
notificationChannelLoading$ = new Subject<boolean>();
|
||||
|
||||
showQuantityControlSpinnerItemId: number;
|
||||
quantityError$ = new BehaviorSubject<{ [key: string]: string }>({});
|
||||
@@ -152,58 +227,76 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
loadingOnQuantityChangeById$ = new Subject<number>();
|
||||
showOrderButtonSpinner: boolean;
|
||||
|
||||
get productSearchBasePath() {
|
||||
return this._productNavigationService.getArticleSearchBasePath(this.applicationService.activatedProcessId);
|
||||
}
|
||||
|
||||
get isDesktop$() {
|
||||
return this._environmentService.matchDesktop$.pipe(
|
||||
map((state) => {
|
||||
return state.matches;
|
||||
}),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private domainCheckoutService: DomainCheckoutService,
|
||||
public applicationService: ApplicationService,
|
||||
private availabilityService: DomainAvailabilityService,
|
||||
private uiModal: UiModalService,
|
||||
private auth: AuthService,
|
||||
private router: Router,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private domainCatalogService: DomainCatalogService,
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private domainPrinterService: DomainPrinterService,
|
||||
private _purchaseOptionsModalService: PurchaseOptionsModalService,
|
||||
private _productNavigationService: ProductCatalogNavigationService,
|
||||
private _navigationService: CheckoutNavigationService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _store: CheckoutReviewStore
|
||||
) {}
|
||||
private _fb: UntypedFormBuilder,
|
||||
private _purchaseOptionsModalService: PurchaseOptionsModalService
|
||||
) {
|
||||
super({
|
||||
shoppingCart: undefined,
|
||||
shoppingCartItems: [],
|
||||
fetching: false,
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.applicationService.activatedProcessId$.pipe(takeUntil(this._onDestroy$)).subscribe((_) => {
|
||||
this._store.loadShoppingCart();
|
||||
this.applicationService.activatedProcessId$.pipe(takeUntil(this._orderCompleted)).subscribe((_) => {
|
||||
this.loadShoppingCart();
|
||||
});
|
||||
|
||||
await this.removeBreadcrumbs();
|
||||
await this.updateBreadcrumb();
|
||||
await this.initNotificationsControl();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.resetControl();
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
loadShoppingCart = this.effect(($) =>
|
||||
$.pipe(
|
||||
tap(() => (this.fetching = true)),
|
||||
withLatestFrom(this.applicationService.activatedProcessId$),
|
||||
switchMap(([_, processId]) => {
|
||||
return this.domainCheckoutService.getShoppingCart({ processId, latest: true }).pipe(
|
||||
tapResponse(
|
||||
(shoppingCart) => {
|
||||
const shoppingCartItems = shoppingCart?.items?.map((item) => item.data) || [];
|
||||
this.patchState({
|
||||
shoppingCart,
|
||||
shoppingCartItems,
|
||||
});
|
||||
// this.checkQuantityErrors(shoppingCartItems);
|
||||
},
|
||||
(err) => {},
|
||||
() => {}
|
||||
)
|
||||
);
|
||||
}),
|
||||
tap(() => (this.fetching = 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({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: 'Warenkorb',
|
||||
path: this._navigationService.getCheckoutReviewPath(this.applicationService.activatedProcessId),
|
||||
path: `/kunde/${this.applicationService.activatedProcessId}/cart/review`,
|
||||
tags: ['checkout', 'cart'],
|
||||
section: 'customer',
|
||||
});
|
||||
@@ -219,8 +312,99 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
resetControl() {
|
||||
this._store.notificationsControl = undefined;
|
||||
async initNotificationsControl() {
|
||||
const fb = this._fb;
|
||||
const notificationChannel = await this.notificationChannel$.pipe(first()).toPromise();
|
||||
const communicationDetails = await this.communicationDetails$.pipe(first()).toPromise();
|
||||
|
||||
let selectedNotificationChannel = 0;
|
||||
if ((notificationChannel & 1) === 1 && communicationDetails.email) {
|
||||
selectedNotificationChannel += 1;
|
||||
}
|
||||
if ((notificationChannel & 2) === 2 && communicationDetails.mobile) {
|
||||
selectedNotificationChannel += 2;
|
||||
}
|
||||
// #1967 Wenn E-Mail und SMS als NotificationChannel gesetzt sind, nur E-Mail anhaken
|
||||
if ((selectedNotificationChannel & 3) === 3) {
|
||||
selectedNotificationChannel = 1;
|
||||
}
|
||||
|
||||
this.control = fb.group({
|
||||
notificationChannel: new UntypedFormGroup({
|
||||
selected: new UntypedFormControl(selectedNotificationChannel),
|
||||
email: new UntypedFormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
|
||||
mobile: new UntypedFormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async onNotificationChange(notificationChannels?: NotificationChannel[]) {
|
||||
this.notificationChannelLoading$.next(true);
|
||||
|
||||
try {
|
||||
const control = this.control?.getRawValue();
|
||||
const notificationChannel = notificationChannels
|
||||
? (notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel)
|
||||
: control?.notificationChannel?.selected || 0;
|
||||
const processId = await this.applicationService.activatedProcessId$.pipe(first()).toPromise();
|
||||
const email = control?.notificationChannel?.email;
|
||||
const mobile = control?.notificationChannel?.mobile;
|
||||
|
||||
// Check if E-Mail and Mobilnumber is available if E-Mail or SMS checkbox is active
|
||||
if (notificationChannel === 3 && (!email || !mobile)) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else if (notificationChannel === 2 && !mobile) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else if (notificationChannel === 1 && !email) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else {
|
||||
this.checkNotificationChannelControl$.next(true);
|
||||
}
|
||||
|
||||
// NotificationChannel nur speichern, wenn Haken und Value gesetzt
|
||||
let setNotificationChannel = 0;
|
||||
if ((notificationChannel & 1) === 1 && email) {
|
||||
setNotificationChannel += 1;
|
||||
}
|
||||
if ((notificationChannel & 2) === 2 && mobile) {
|
||||
setNotificationChannel += 2;
|
||||
}
|
||||
|
||||
if (notificationChannel > 0) {
|
||||
this.setCommunicationDetails({ processId, notificationChannel, email, mobile });
|
||||
}
|
||||
this.domainCheckoutService.setNotificationChannels({
|
||||
processId,
|
||||
notificationChannels: (setNotificationChannel as NotificationChannel) || 0,
|
||||
});
|
||||
} catch (error) {
|
||||
this.uiModal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim setzen des Benachrichtigungskanals' });
|
||||
}
|
||||
|
||||
this.notificationChannelLoading$.next(false);
|
||||
}
|
||||
|
||||
setCommunicationDetails({
|
||||
processId,
|
||||
notificationChannel,
|
||||
email,
|
||||
mobile,
|
||||
}: {
|
||||
processId: number;
|
||||
notificationChannel: number;
|
||||
email: string;
|
||||
mobile: string;
|
||||
}) {
|
||||
const emailValid = this.control?.get('notificationChannel')?.get('email')?.valid;
|
||||
const mobileValid = this.control?.get('notificationChannel')?.get('mobile')?.valid;
|
||||
|
||||
if (notificationChannel === 3 && emailValid && mobileValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email, mobile });
|
||||
} else if (notificationChannel === 1 && emailValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email });
|
||||
} else if (notificationChannel === 2 && mobileValid) {
|
||||
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, mobile });
|
||||
}
|
||||
}
|
||||
|
||||
openDummyModal(data?: CheckoutDummyData) {
|
||||
@@ -231,6 +415,18 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
changeDummyItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
||||
// const data: CheckoutDummyData = {
|
||||
// itemType: shoppingCartItem.itemType,
|
||||
// price: shoppingCartItem?.availability?.price?.value?.value,
|
||||
// vat: shoppingCartItem?.availability?.price?.vat?.vatType,
|
||||
// supplier: shoppingCartItem?.availability?.supplier?.id,
|
||||
// estimatedShippingDate: shoppingCartItem?.estimatedShippingDate,
|
||||
// manufacturer: shoppingCartItem?.product?.manufacturer,
|
||||
// name: shoppingCartItem?.product?.name,
|
||||
// contributors: shoppingCartItem?.product?.contributors,
|
||||
// ean: shoppingCartItem?.product?.ean,
|
||||
// quantity: shoppingCartItem?.quantity,
|
||||
// };
|
||||
this.openDummyModal(shoppingCartItem);
|
||||
}
|
||||
|
||||
@@ -242,6 +438,10 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
setAgentComment(agentComment: string) {
|
||||
this.domainCheckoutService.setSpecialComment({ processId: this.applicationService.activatedProcessId, agentComment });
|
||||
}
|
||||
|
||||
async openPrintModal() {
|
||||
let shoppingCart = await this.shoppingCart$.pipe(first()).toPromise();
|
||||
this.uiModal.open({
|
||||
@@ -363,6 +563,7 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
// 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
|
||||
@@ -393,6 +594,17 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
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 });
|
||||
// }
|
||||
// }
|
||||
|
||||
// Bei unbekannten Kunden und DIG Bestellung findet ein Vergleich der Preise statt
|
||||
compareDeliveryAndCatalogPrice(availability: AvailabilityDTO, orderType: string, shoppingCartItemPrice: number) {
|
||||
if (['Versand', 'DIG-Versand'].includes(orderType) && shoppingCartItemPrice < availability?.price?.value?.value) {
|
||||
@@ -410,6 +622,17 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
return availability;
|
||||
}
|
||||
|
||||
async changeAddress() {
|
||||
const processId = this.applicationService.activatedProcessId;
|
||||
const customer = await this.domainCheckoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
if (!customer) {
|
||||
this.navigateToCustomerSearch(processId);
|
||||
return;
|
||||
}
|
||||
const customerId = customer.source;
|
||||
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', `${customerId}`]);
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch(processId: number) {
|
||||
try {
|
||||
const response = await this.customerFeatures$
|
||||
@@ -420,7 +643,6 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search'], {
|
||||
queryParams: { filter_customertype: response.filter.customertype },
|
||||
});
|
||||
@@ -437,17 +659,6 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
async changeAddress() {
|
||||
const processId = this.applicationService.activatedProcessId;
|
||||
const customer = await this.domainCheckoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
if (!customer) {
|
||||
this.navigateToCustomerSearch(processId);
|
||||
return;
|
||||
}
|
||||
const customerId = customer.source;
|
||||
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', `${customerId}`]);
|
||||
}
|
||||
|
||||
async order() {
|
||||
const shoppingCartItemsWithoutOrderType = await this.shoppingCartItemsWithoutOrderType$.pipe(first()).toPromise();
|
||||
|
||||
@@ -464,12 +675,12 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
try {
|
||||
this.showOrderButtonSpinner = true;
|
||||
// Ticket #3287 Um nur E-Mail und SMS Benachrichtigungen zu setzen und um alle anderen Benachrichtigungskanäle wie z.B. Brief zu deaktivieren
|
||||
await this._store.onNotificationChange();
|
||||
await this.onNotificationChange();
|
||||
const orders = await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
|
||||
const orderIds = orders.map((order) => order.id).join(',');
|
||||
this._store.orderCompleted.next();
|
||||
this._orderCompleted.next();
|
||||
await this.patchProcess(processId);
|
||||
await this._navigationService.navigateToCheckoutSummary({ processId, orderIds });
|
||||
await this.router.navigate(['/kunde', processId, 'cart', 'summary', orderIds]);
|
||||
} catch (error) {
|
||||
const response = error?.error;
|
||||
let message: string = response?.message ?? '';
|
||||
@@ -487,9 +698,9 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (error.status === 409) {
|
||||
this._store.orderCompleted.next();
|
||||
this._orderCompleted.next();
|
||||
await this.patchProcess(processId);
|
||||
await this._navigationService.navigateToCheckoutSummary({ processId });
|
||||
await this.router.navigate(['/kunde', processId, 'cart', 'summary']);
|
||||
}
|
||||
} finally {
|
||||
this.showOrderButtonSpinner = false;
|
||||
@@ -499,12 +710,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async patchProcess(processId: number) {
|
||||
const process = await this.applicationService.getProcessById$(processId).pipe(first()).toPromise();
|
||||
if (!!process) {
|
||||
this.applicationService.patchProcess(process.id, {
|
||||
name: `${process.name} Bestellbestätigung`,
|
||||
type: 'cart-checkout',
|
||||
});
|
||||
}
|
||||
this.applicationService.patchProcess(processId, {
|
||||
type: 'cart-checkout',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CheckoutReviewComponent } from './checkout-review.component';
|
||||
import { PageCheckoutPipeModule } from '../pipes/page-checkout-pipe.module';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { ProductImageModule } from 'apps/cdn/product-image/src/public-api';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
@@ -16,10 +17,6 @@ import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
import { SharedNotificationChannelControlModule } from '@shared/components/notification-channel-control';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-item.component';
|
||||
import { CheckoutReviewDetailsComponent } from './details/checkout-review-details.component';
|
||||
import { CheckoutReviewStore } from './checkout-review.store';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -27,7 +24,7 @@ import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
UiCommonModule,
|
||||
RouterModule,
|
||||
PageCheckoutPipeModule,
|
||||
IconModule,
|
||||
UiIconModule,
|
||||
UiQuantityDropdownModule,
|
||||
ProductImageModule,
|
||||
FormsModule,
|
||||
@@ -38,10 +35,8 @@ import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
UiInputModule,
|
||||
UiCheckboxModule,
|
||||
SharedNotificationChannelControlModule,
|
||||
TextFieldModule,
|
||||
],
|
||||
exports: [CheckoutReviewComponent, CheckoutReviewDetailsComponent],
|
||||
declarations: [CheckoutReviewComponent, SpecialCommentComponent, ShoppingCartItemComponent, CheckoutReviewDetailsComponent],
|
||||
providers: [CheckoutReviewStore],
|
||||
exports: [CheckoutReviewComponent],
|
||||
declarations: [CheckoutReviewComponent, SpecialCommentComponent, ShoppingCartItemComponent],
|
||||
})
|
||||
export class CheckoutReviewModule {}
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UntypedFormGroup } from '@angular/forms';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { NotificationChannel, PayerDTO, ShoppingCartDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { first, map, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
export interface CheckoutReviewState {
|
||||
payer: PayerDTO;
|
||||
shoppingCart: ShoppingCartDTO;
|
||||
shoppingCartItems: ShoppingCartItemDTO[];
|
||||
fetching: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CheckoutReviewStore extends ComponentStore<CheckoutReviewState> {
|
||||
orderCompleted = new Subject<void>();
|
||||
|
||||
get shoppingCart() {
|
||||
return this.get((s) => s.shoppingCart);
|
||||
}
|
||||
set shoppingCart(shoppingCart: ShoppingCartDTO) {
|
||||
this.patchState({ shoppingCart });
|
||||
}
|
||||
readonly shoppingCart$ = this.select((s) => s.shoppingCart);
|
||||
|
||||
get shoppingCartItems() {
|
||||
return this.get((s) => s.shoppingCartItems);
|
||||
}
|
||||
set shoppingCartItems(shoppingCartItems: ShoppingCartItemDTO[]) {
|
||||
this.patchState({ shoppingCartItems });
|
||||
}
|
||||
readonly shoppingCartItems$ = this.select((s) => s.shoppingCartItems);
|
||||
|
||||
get fetching() {
|
||||
return this.get((s) => s.fetching);
|
||||
}
|
||||
set fetching(fetching: boolean) {
|
||||
this.patchState({ fetching });
|
||||
}
|
||||
readonly fetching$ = this.select((s) => s.fetching);
|
||||
|
||||
customerFeatures$ = this._application.activatedProcessId$.pipe(
|
||||
takeUntil(this.orderCompleted),
|
||||
switchMap((processId) => this._domainCheckoutService.getCustomerFeatures({ processId }))
|
||||
);
|
||||
|
||||
payer$ = this._application.activatedProcessId$.pipe(
|
||||
takeUntil(this.orderCompleted),
|
||||
switchMap((processId) => this._domainCheckoutService.getPayer({ processId }))
|
||||
);
|
||||
|
||||
showBillingAddress$ = this.shoppingCartItems$.pipe(
|
||||
withLatestFrom(this.customerFeatures$),
|
||||
map(
|
||||
([items, customerFeatures]) =>
|
||||
items.some(
|
||||
(item) =>
|
||||
item.features?.orderType === 'Versand' ||
|
||||
item.features?.orderType === 'B2B-Versand' ||
|
||||
item.features?.orderType === 'DIG-Versand'
|
||||
) || !!customerFeatures?.b2b
|
||||
)
|
||||
);
|
||||
|
||||
checkNotificationChannelControl$ = new BehaviorSubject<boolean>(true);
|
||||
|
||||
notificationChannelLoading$ = new Subject<boolean>();
|
||||
|
||||
notificationsControl: UntypedFormGroup;
|
||||
|
||||
constructor(
|
||||
private _domainCheckoutService: DomainCheckoutService,
|
||||
private _application: ApplicationService,
|
||||
private _uiModal: UiModalService
|
||||
) {
|
||||
super({ payer: undefined, shoppingCart: undefined, shoppingCartItems: [], fetching: false });
|
||||
}
|
||||
|
||||
loadShoppingCart = this.effect(($) =>
|
||||
$.pipe(
|
||||
tap(() => (this.fetching = true)),
|
||||
withLatestFrom(this._application.activatedProcessId$),
|
||||
switchMap(([_, processId]) => {
|
||||
return this._domainCheckoutService.getShoppingCart({ processId, latest: true }).pipe(
|
||||
tapResponse(
|
||||
(shoppingCart) => {
|
||||
const shoppingCartItems = shoppingCart?.items?.map((item) => item.data) || [];
|
||||
this.patchState({
|
||||
shoppingCart,
|
||||
shoppingCartItems,
|
||||
});
|
||||
},
|
||||
(err) => {},
|
||||
() => {}
|
||||
)
|
||||
);
|
||||
}),
|
||||
tap(() => (this.fetching = false))
|
||||
)
|
||||
);
|
||||
|
||||
async onNotificationChange(notificationChannels?: NotificationChannel[]) {
|
||||
this.notificationChannelLoading$.next(true);
|
||||
|
||||
try {
|
||||
const control = this.notificationsControl?.getRawValue();
|
||||
const notificationChannel = notificationChannels
|
||||
? (notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel)
|
||||
: control?.notificationChannel?.selected || 0;
|
||||
const processId = await this._application.activatedProcessId$.pipe(first()).toPromise();
|
||||
const email = control?.notificationChannel?.email;
|
||||
const mobile = control?.notificationChannel?.mobile;
|
||||
|
||||
// Check if E-Mail and Mobilnumber is available if E-Mail or SMS checkbox is active
|
||||
if (notificationChannel === 3 && (!email || !mobile)) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else if (notificationChannel === 2 && !mobile) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else if (notificationChannel === 1 && !email) {
|
||||
this.checkNotificationChannelControl$.next(false);
|
||||
} else {
|
||||
this.checkNotificationChannelControl$.next(true);
|
||||
}
|
||||
|
||||
// NotificationChannel nur speichern, wenn Haken und Value gesetzt
|
||||
let setNotificationChannel = 0;
|
||||
if ((notificationChannel & 1) === 1 && email) {
|
||||
setNotificationChannel += 1;
|
||||
}
|
||||
if ((notificationChannel & 2) === 2 && mobile) {
|
||||
setNotificationChannel += 2;
|
||||
}
|
||||
|
||||
if (notificationChannel > 0) {
|
||||
this.setCommunicationDetails({ processId, notificationChannel, email, mobile });
|
||||
}
|
||||
this._domainCheckoutService.setNotificationChannels({
|
||||
processId,
|
||||
notificationChannels: (setNotificationChannel as NotificationChannel) || 0,
|
||||
});
|
||||
} catch (error) {
|
||||
this._uiModal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim setzen des Benachrichtigungskanals' });
|
||||
}
|
||||
|
||||
this.notificationChannelLoading$.next(false);
|
||||
}
|
||||
|
||||
setCommunicationDetails({
|
||||
processId,
|
||||
notificationChannel,
|
||||
email,
|
||||
mobile,
|
||||
}: {
|
||||
processId: number;
|
||||
notificationChannel: number;
|
||||
email: string;
|
||||
mobile: string;
|
||||
}) {
|
||||
const emailValid = this.notificationsControl?.get('notificationChannel')?.get('email')?.valid;
|
||||
const mobileValid = this.notificationsControl?.get('notificationChannel')?.get('mobile')?.valid;
|
||||
|
||||
if (notificationChannel === 3 && emailValid && mobileValid) {
|
||||
this._domainCheckoutService.setBuyerCommunicationDetails({ processId, email, mobile });
|
||||
} else if (notificationChannel === 1 && emailValid) {
|
||||
this._domainCheckoutService.setBuyerCommunicationDetails({ processId, email });
|
||||
} else if (notificationChannel === 2 && mobileValid) {
|
||||
this._domainCheckoutService.setBuyerCommunicationDetails({ processId, mobile });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<h1 class="text-center text-h3 desktop:text-h2 font-normal desktop:font-bold pb-10 desktop:py-10 px-12">Überprüfen Sie die Details.</h1>
|
||||
|
||||
<ng-container *ngIf="payer$ | async; let payer">
|
||||
<div *ngIf="!(showBillingAddress$ | async)" class="flex flex-row items-start justify-between p-5">
|
||||
<div class="flex flex-row flex-wrap pr-4">
|
||||
<div class="mr-3">Nachname, Vorname</div>
|
||||
<div class="font-bold" *ngIf="payer">{{ payer.lastName }}, {{ payer.firstName }}</div>
|
||||
</div>
|
||||
|
||||
<button *ngIf="payer" (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">
|
||||
Ändern
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="showNotificationChannels$ | async">
|
||||
<form *ngIf="control" [formGroup]="control">
|
||||
<shared-notification-channel-control
|
||||
[communicationDetails]="communicationDetails$ | async"
|
||||
(channelActionEvent)="updateNotifications($event)"
|
||||
[channelActionName]="'Speichern'"
|
||||
[channelActionLoading]="notificationChannelLoading$ | async"
|
||||
formGroupName="notificationChannel"
|
||||
>
|
||||
</shared-notification-channel-control>
|
||||
</form>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="payer$ | async; let payer">
|
||||
<div *ngIf="showBillingAddress$ | async" class="flex flex-row items-start justify-between p-5">
|
||||
<div class="flex flex-row flex-wrap pr-4">
|
||||
<div class="mr-3">Rechnungsadresse</div>
|
||||
<div class="font-bold">
|
||||
{{ payer | payerAddress }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button *ngIf="payer" (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">
|
||||
Ändern
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<page-special-comment
|
||||
class="mb-6 mt-4"
|
||||
[hasPayer]="!!(payer$ | async)"
|
||||
[ngModel]="specialComment$ | async"
|
||||
(ngModelChange)="setAgentComment($event)"
|
||||
>
|
||||
</page-special-comment>
|
||||
@@ -1,3 +0,0 @@
|
||||
:host {
|
||||
@apply desktop:bg-white box-border flex flex-col desktop:overflow-y-scroll h-auto desktop:h-[calc(100vh-15.1rem)] desktop:rounded;
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/components/notification-channel-control';
|
||||
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { CheckoutReviewStore } from '../checkout-review.store';
|
||||
import { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { Router } from '@angular/router';
|
||||
import { NotificationChannel } from '@swagger/checkout';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-review-details',
|
||||
templateUrl: 'checkout-review-details.component.html',
|
||||
styleUrls: ['checkout-review-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewDetailsComponent implements OnInit {
|
||||
control = this._store.notificationsControl;
|
||||
|
||||
customerFeatures$ = this._store.customerFeatures$;
|
||||
|
||||
payer$ = this._store.payer$;
|
||||
|
||||
showBillingAddress$ = this._store.shoppingCartItems$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
withLatestFrom(this.customerFeatures$),
|
||||
map(
|
||||
([items, customerFeatures]) =>
|
||||
items.some(
|
||||
(item) =>
|
||||
item.features?.orderType === 'Versand' ||
|
||||
item.features?.orderType === 'B2B-Versand' ||
|
||||
item.features?.orderType === 'DIG-Versand'
|
||||
) || !!customerFeatures?.b2b
|
||||
),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
showNotificationChannels$ = combineLatest([this._store.shoppingCartItems$, this.payer$]).pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
map(
|
||||
([items, payer]) =>
|
||||
!!payer && items.some((item) => item.features?.orderType === 'Rücklage' || item.features?.orderType === 'Abholung')
|
||||
)
|
||||
);
|
||||
|
||||
notificationChannel$ = this._application.activatedProcessId$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
switchMap((processId) => this._domainCheckoutService.getNotificationChannels({ processId }))
|
||||
);
|
||||
|
||||
communicationDetails$ = this._application.activatedProcessId$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
switchMap((processId) => this._domainCheckoutService.getBuyerCommunicationDetails({ processId })),
|
||||
map((communicationDetails) => communicationDetails ?? { email: undefined, mobile: undefined })
|
||||
);
|
||||
|
||||
specialComment$ = this._application.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this._domainCheckoutService.getSpecialComment({ processId }))
|
||||
);
|
||||
|
||||
notificationChannelLoading$ = this._store.notificationChannelLoading$;
|
||||
|
||||
constructor(
|
||||
private _fb: UntypedFormBuilder,
|
||||
private _store: CheckoutReviewStore,
|
||||
private _application: ApplicationService,
|
||||
private _domainCheckoutService: DomainCheckoutService,
|
||||
private _router: Router
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.initNotificationsControl();
|
||||
}
|
||||
|
||||
async initNotificationsControl() {
|
||||
const fb = this._fb;
|
||||
const notificationChannel = await this.notificationChannel$.pipe(first()).toPromise();
|
||||
const communicationDetails = await this.communicationDetails$.pipe(first()).toPromise();
|
||||
|
||||
let selectedNotificationChannel = 0;
|
||||
if ((notificationChannel & 1) === 1 && communicationDetails.email) {
|
||||
selectedNotificationChannel += 1;
|
||||
}
|
||||
if ((notificationChannel & 2) === 2 && communicationDetails.mobile) {
|
||||
selectedNotificationChannel += 2;
|
||||
}
|
||||
// #1967 Wenn E-Mail und SMS als NotificationChannel gesetzt sind, nur E-Mail anhaken
|
||||
if ((selectedNotificationChannel & 3) === 3) {
|
||||
selectedNotificationChannel = 1;
|
||||
}
|
||||
|
||||
this.control = fb.group({
|
||||
notificationChannel: new UntypedFormGroup({
|
||||
selected: new UntypedFormControl(selectedNotificationChannel),
|
||||
email: new UntypedFormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
|
||||
mobile: new UntypedFormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
|
||||
}),
|
||||
});
|
||||
|
||||
this._store.notificationsControl = this.control;
|
||||
}
|
||||
|
||||
setAgentComment(agentComment: string) {
|
||||
this._domainCheckoutService.setSpecialComment({ processId: this._application.activatedProcessId, agentComment });
|
||||
}
|
||||
|
||||
updateNotifications(notificationChannels?: NotificationChannel[]) {
|
||||
this._store.onNotificationChange(notificationChannels);
|
||||
}
|
||||
|
||||
async changeAddress() {
|
||||
const processId = this._application.activatedProcessId;
|
||||
const customer = await this._domainCheckoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
if (!customer) {
|
||||
this.navigateToCustomerSearch(processId);
|
||||
return;
|
||||
}
|
||||
const customerId = customer.source;
|
||||
this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', `${customerId}`]);
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch(processId: number) {
|
||||
try {
|
||||
const response = await this.customerFeatures$
|
||||
.pipe(
|
||||
first(),
|
||||
switchMap((customerFeatures) => {
|
||||
return this._domainCheckoutService.canSetCustomer({ processId, customerFeatures });
|
||||
})
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'search'], {
|
||||
queryParams: { filter_customertype: response.filter.customertype },
|
||||
});
|
||||
} catch (error) {
|
||||
this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'search']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="item-thumbnail">
|
||||
<a [routerLink]="productSearchDetailsPath" [queryParams]="{ main_qs: item?.product?.ean }">
|
||||
<a [routerLink]="['/kunde', application.activatedProcessId, 'product', 'details', 'ean', item?.product?.ean]">
|
||||
<img loading="lazy" *ngIf="item?.product?.ean | productImage; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.name" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="item-contributors">
|
||||
<a
|
||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||
[routerLink]="productSearchResultsPath"
|
||||
[routerLink]="['/kunde', application.activatedProcessId, 'product', 'search', 'results']"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
@@ -16,13 +16,16 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="item-title font-bold text-h2 mb-4"
|
||||
[class.text-h3]="item?.product?.name?.length >= 40 && isTablet"
|
||||
[class.text-p1]="item?.product?.name?.length >= 50 || !isTablet"
|
||||
[class.text-p2]="item?.product?.name?.length >= 60 && isTablet"
|
||||
[class.text-p3]="item?.product?.name?.length >= 100"
|
||||
class="item-title"
|
||||
[class.xl]="item?.product?.name?.length >= 35"
|
||||
[class.lg]="item?.product?.name?.length >= 40"
|
||||
[class.md]="item?.product?.name?.length >= 50"
|
||||
[class.sm]="item?.product?.name?.length >= 60"
|
||||
[class.xs]="item?.product?.name?.length >= 100"
|
||||
>
|
||||
<a [routerLink]="productSearchDetailsPath" [queryParams]="{ main_qs: item?.product?.ean }">{{ item?.product?.name }}</a>
|
||||
<a [routerLink]="['/kunde', application.activatedProcessId, 'product', 'details', 'ean', item?.product?.ean]">{{
|
||||
item?.product?.name
|
||||
}}</a>
|
||||
</div>
|
||||
|
||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
@@ -34,12 +37,10 @@
|
||||
{{ item?.product?.formatDetail }}
|
||||
</div>
|
||||
|
||||
<div class="item-info text-p2">
|
||||
<div class="mb-1">{{ item?.product?.manufacturer | substr: 25 }} | {{ item?.product?.ean }}</div>
|
||||
<div class="mb-1">
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ item?.product?.publicationDate | date }}
|
||||
</div>
|
||||
<div class="item-info">
|
||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }} <br />
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ item?.product?.publicationDate | date }}
|
||||
|
||||
<div class="item-date" *ngIf="orderType === 'Abholung'">Abholung ab {{ item?.availability?.estimatedShippingDate | date }}</div>
|
||||
<div class="item-date" *ngIf="orderType === 'Versand' || orderType === 'B2B-Versand' || orderType === 'DIG-Versand'">
|
||||
@@ -56,9 +57,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-price-stock flex flex-col">
|
||||
<div class="text-p2 font-bold">{{ item?.availability?.price?.value?.value | currency: 'EUR':'code' }}</div>
|
||||
<div class="text-p2 font-normal">
|
||||
<div class="item-price-stock">
|
||||
<div>{{ item?.availability?.price?.value?.value | currency: 'EUR':'code' }}</div>
|
||||
<div>
|
||||
<ui-quantity-dropdown
|
||||
*ngIf="!(isDummy$ | async); else quantityDummy"
|
||||
[ngModel]="item?.quantity"
|
||||
@@ -69,7 +70,7 @@
|
||||
>
|
||||
</ui-quantity-dropdown>
|
||||
<ng-template #quantityDummy>
|
||||
<div class="mt-2">{{ item?.quantity }}x</div>
|
||||
{{ item?.quantity }}
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="quantity-error" *ngIf="quantityError">
|
||||
@@ -84,7 +85,7 @@
|
||||
*ngIf="!(hasOrderType$ | async)"
|
||||
>
|
||||
<ui-spinner [show]="(loadingOnItemChangeById$ | async) === item?.id">
|
||||
Lieferung Auswählen
|
||||
Auswählen
|
||||
</ui-spinner>
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
:host {
|
||||
@apply text-black no-underline grid p-5 gap-x-4 gap-y-1;
|
||||
grid-template-columns: 3.75rem auto;
|
||||
@apply text-black no-underline grid p-4;
|
||||
grid-template-columns: 102px 50% auto;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas:
|
||||
'item-thumbnail item-contributors item-price-stock'
|
||||
'item-thumbnail item-contributors item-contributors'
|
||||
'item-thumbnail item-title item-price-stock'
|
||||
'item-thumbnail item-format item-format'
|
||||
'item-thumbnail item-info actions';
|
||||
'item-thumbnail item-format item-price-stock'
|
||||
'item-thumbnail item-info actions'
|
||||
'item-thumbnail item-date actions'
|
||||
'item-thumbnail item-ssc actions'
|
||||
'item-thumbnail item-availability actions';
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -15,35 +18,61 @@ button {
|
||||
|
||||
.item-thumbnail {
|
||||
grid-area: item-thumbnail;
|
||||
@apply mr-8 w-[3.75rem] h-[5.9375rem];
|
||||
width: 70px;
|
||||
@apply mr-8;
|
||||
img {
|
||||
@apply w-[3.75rem] h-[5.9375rem] rounded shadow-cta;
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
@apply rounded shadow-cta;
|
||||
}
|
||||
}
|
||||
|
||||
.item-contributors {
|
||||
grid-area: item-contributors;
|
||||
height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 600px;
|
||||
white-space: nowrap;
|
||||
|
||||
a {
|
||||
@apply text-[#0556B4] font-bold no-underline;
|
||||
@apply text-active-customer font-bold no-underline;
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
grid-area: item-title;
|
||||
@apply font-bold text-lg mb-4;
|
||||
max-height: 64px;
|
||||
|
||||
a {
|
||||
@apply text-black no-underline;
|
||||
@apply text-active-customer no-underline;
|
||||
}
|
||||
}
|
||||
|
||||
.item-title.xl {
|
||||
@apply font-bold text-xl;
|
||||
}
|
||||
|
||||
.item-title.lg {
|
||||
@apply font-bold text-lg;
|
||||
}
|
||||
|
||||
.item-title.md {
|
||||
@apply font-bold text-p2;
|
||||
}
|
||||
|
||||
.item-title.sm {
|
||||
@apply font-bold text-p3;
|
||||
}
|
||||
|
||||
.item-title.xs {
|
||||
@apply font-bold text-xs;
|
||||
}
|
||||
|
||||
.item-format {
|
||||
grid-area: item-format;
|
||||
@apply flex flex-row items-center font-bold text-p2 whitespace-nowrap;
|
||||
@apply flex flex-row items-center font-bold text-lg whitespace-nowrap;
|
||||
|
||||
img {
|
||||
@apply mr-2;
|
||||
@@ -52,7 +81,7 @@ button {
|
||||
|
||||
.item-price-stock {
|
||||
grid-area: item-price-stock;
|
||||
@apply text-right;
|
||||
@apply font-bold text-xl text-right;
|
||||
|
||||
.quantity {
|
||||
@apply flex flex-row justify-end items-center;
|
||||
@@ -63,7 +92,7 @@ button {
|
||||
}
|
||||
|
||||
ui-quantity-dropdown {
|
||||
@apply flex justify-end mt-2 font-normal;
|
||||
@apply flex justify-end mt-2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +101,7 @@ button {
|
||||
@apply flex flex-row justify-end items-baseline font-bold text-lg;
|
||||
|
||||
ui-icon {
|
||||
@apply text-active-customer mr-1;
|
||||
@apply text-active-customer mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +117,35 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.item-availability {
|
||||
@apply flex flex-row items-center mt-4;
|
||||
grid-area: item-availability;
|
||||
|
||||
.fetching {
|
||||
@apply w-52 h-px-20;
|
||||
background-color: #e6eff9;
|
||||
animation: load 0.75s linear infinite;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply mr-4;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply text-dark-cerulean mx-1;
|
||||
}
|
||||
|
||||
div {
|
||||
@apply ml-2 flex items-center;
|
||||
}
|
||||
|
||||
.truck {
|
||||
@apply -mb-px-5 -mt-px-5;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply flex items-end justify-end;
|
||||
@apply flex items-center justify-end;
|
||||
grid-area: actions;
|
||||
|
||||
button {
|
||||
@@ -100,9 +156,3 @@ button {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-shopping-cart-item ui-quantity-dropdown {
|
||||
.current-quantity {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { ItemType, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
@@ -112,27 +110,10 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
|
||||
.getOlaErrors({ processId: this.application.activatedProcessId })
|
||||
.pipe(map((ids) => ids?.find((id) => id === this.item.id)));
|
||||
|
||||
get productSearchResultsPath() {
|
||||
return this._productNavigationService.getArticleSearchResultsPath(this.application.activatedProcessId);
|
||||
}
|
||||
|
||||
get productSearchDetailsPath() {
|
||||
return this._productNavigationService.getArticleDetailsPath({
|
||||
processId: this.application.activatedProcessId,
|
||||
ean: this.item?.product?.ean,
|
||||
});
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private availabilityService: DomainAvailabilityService,
|
||||
private checkoutService: DomainCheckoutService,
|
||||
public application: ApplicationService,
|
||||
private _productNavigationService: ProductCatalogNavigationService,
|
||||
private _environment: EnvironmentService
|
||||
public application: ApplicationService
|
||||
) {
|
||||
super({ item: undefined, orderType: '' });
|
||||
}
|
||||
|
||||
@@ -1,37 +1,17 @@
|
||||
<div class="page-special-comment__wrapper flex flex-col items-start px-5">
|
||||
<div class="mb-[0.375rem]">Anmerkung</div>
|
||||
<label for="agent-comment">Anmerkung</label>
|
||||
<textarea
|
||||
#input
|
||||
type="text"
|
||||
id="agent-comment"
|
||||
name="agent-comment"
|
||||
[(ngModel)]="value"
|
||||
[rows]="rows"
|
||||
(ngModelChange)="check()"
|
||||
(blur)="save()"
|
||||
></textarea>
|
||||
|
||||
<div class="flex flex-row w-full mb-[0.375rem]">
|
||||
<textarea
|
||||
#input
|
||||
matInput
|
||||
cdkTextareaAutosize
|
||||
#autosize="cdkTextareaAutosize"
|
||||
maxlength="200"
|
||||
cdkAutosizeMinRows="1"
|
||||
cdkAutosizeMaxRows="5"
|
||||
#specialCommentInput
|
||||
(keydown.delete)="triggerResize()"
|
||||
(keydown.backspace)="triggerResize()"
|
||||
type="text"
|
||||
id="agent-comment"
|
||||
name="agent-comment"
|
||||
placeholder="Eine Anmerkung hinzufügen"
|
||||
[(ngModel)]="value"
|
||||
[rows]="rows"
|
||||
(ngModelChange)="check()"
|
||||
(blur)="save()"
|
||||
></textarea>
|
||||
|
||||
<div class="comment-actions py-4">
|
||||
<button type="reset" class="clear pl-4" *ngIf="!disabled && !!value" (click)="clear(); triggerResize()">
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
<button class="cta-save ml-4" type="submit" *ngIf="!disabled && isDirty" (click)="save()">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!hasPayer" class="text-p3">Zur Info: Sie haben dem Warenkorb noch keinen Kunden hinzugefügt.</div>
|
||||
<div class="action-wrapper">
|
||||
<button type="button" *ngIf="!disabled && !!value" (click)="clear()">
|
||||
<ui-icon icon="close" size="14px"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,45 +1,25 @@
|
||||
.page-special-comment__wrapper {
|
||||
textarea {
|
||||
@apply w-full flex-grow font-bold rounded bg-[#EDEFF0] border-[#AEB7C1] border border-solid outline-none text-p2 p-4;
|
||||
resize: none;
|
||||
height: auto !important;
|
||||
}
|
||||
:host {
|
||||
@apply flex flex-row box-border p-4 items-center;
|
||||
}
|
||||
|
||||
textarea.inactive {
|
||||
@apply text-warning font-bold;
|
||||
@apply w-full flex-grow rounded bg-[#EDEFF0] border-[#AEB7C1] border border-solid outline-none text-p2 p-4 text-warning font-bold;
|
||||
// ipad color fix
|
||||
-webkit-text-fill-color: rgb(190, 129, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
.action-wrapper {
|
||||
@apply self-start flex flex-row items-center;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
textarea::placeholder,
|
||||
textarea.inactive::placeholder {
|
||||
@apply text-[#89949E] font-normal;
|
||||
-webkit-text-fill-color: #89949e;
|
||||
}
|
||||
label {
|
||||
@apply font-bold self-start;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply flex-grow bg-transparent border-none outline-none text-p2 mx-4;
|
||||
}
|
||||
textarea {
|
||||
@apply flex-grow text-p2 border-none outline-none p-0 resize-none;
|
||||
}
|
||||
|
||||
input.inactive {
|
||||
@apply text-warning font-bold;
|
||||
@apply flex-grow bg-transparent border-none outline-none text-p2 mx-4 text-warning font-bold;
|
||||
// ipad color fix
|
||||
-webkit-text-fill-color: rgb(190, 129, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
button {
|
||||
@apply text-brand font-bold text-p1 outline-none border-none bg-transparent ml-1;
|
||||
|
||||
button {
|
||||
@apply bg-transparent text-brand font-bold text-p1 outline-none border-none;
|
||||
}
|
||||
|
||||
button.clear {
|
||||
@apply text-black;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
@apply flex justify-center items-center;
|
||||
ui-icon {
|
||||
@apply text-ucla-blue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
import { Component, ChangeDetectionStrategy, EventEmitter, ViewChild, ChangeDetectorRef, forwardRef, Output, Input } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
ChangeDetectorRef,
|
||||
forwardRef,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
@@ -10,8 +18,6 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SpecialCommentComponent), multi: true }],
|
||||
})
|
||||
export class SpecialCommentComponent implements ControlValueAccessor {
|
||||
@ViewChild('autosize') autosize: CdkTextareaAutosize;
|
||||
|
||||
private initialValue = '';
|
||||
value = '';
|
||||
|
||||
@@ -27,9 +33,6 @@ export class SpecialCommentComponent implements ControlValueAccessor {
|
||||
|
||||
isDirty = false;
|
||||
|
||||
@Input()
|
||||
hasPayer: boolean;
|
||||
|
||||
@Output()
|
||||
isDirtyChange = new EventEmitter<boolean>();
|
||||
|
||||
@@ -86,8 +89,4 @@ export class SpecialCommentComponent implements ControlValueAccessor {
|
||||
this.isDirty = isDirty;
|
||||
this.isDirtyChange.emit(isDirty);
|
||||
}
|
||||
|
||||
triggerResize() {
|
||||
this.autosize.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,15 +62,10 @@
|
||||
<ng-container *ngFor="let order of displayOrder.items">
|
||||
<div class="row between">
|
||||
<div class="product-name">
|
||||
<a [routerLink]="getProductSearchDetailsPath(order?.product?.ean)" [queryParams]="{ main_qs: order?.product?.ean }">
|
||||
<img class="thumbnail" [src]="order.product?.ean | productImage: 30:50:true" />
|
||||
</a>
|
||||
<a
|
||||
class="name"
|
||||
[routerLink]="getProductSearchDetailsPath(order?.product?.ean)"
|
||||
[queryParams]="{ main_qs: order?.product?.ean }"
|
||||
>{{ order?.product?.name }}</a
|
||||
>
|
||||
<img class="thumbnail" [src]="order.product?.ean | productImage: 30:50:true" />
|
||||
<a class="name" [routerLink]="['/kunde', processId, 'product', 'details', 'ean', order?.product?.ean]">{{
|
||||
order?.product?.name
|
||||
}}</a>
|
||||
</div>
|
||||
|
||||
<div class="product-details">
|
||||
|
||||
@@ -13,7 +13,6 @@ import { ApplicationService } from '@core/application';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
import { BehaviorSubject, combineLatest, NEVER, of, Subject } from 'rxjs';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-summary',
|
||||
@@ -123,9 +122,7 @@ export class CheckoutSummaryComponent implements OnDestroy {
|
||||
private breadcrumb: BreadcrumbService,
|
||||
public applicationService: ApplicationService,
|
||||
private domainPrinterService: DomainPrinterService,
|
||||
private dateAdapter: DateAdapter,
|
||||
private _navigation: CheckoutNavigationService,
|
||||
private _productNavigationService: ProductCatalogNavigationService
|
||||
private dateAdapter: DateAdapter
|
||||
) {
|
||||
this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['checkout'])
|
||||
@@ -137,10 +134,7 @@ export class CheckoutSummaryComponent implements OnDestroy {
|
||||
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: `/kunde/${this.applicationService.activatedProcessId}/cart/summary/${this._route.snapshot.params.orderIds}`,
|
||||
tags: ['checkout', 'cart'],
|
||||
section: 'customer',
|
||||
});
|
||||
@@ -162,13 +156,6 @@ export class CheckoutSummaryComponent implements OnDestroy {
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
getProductSearchDetailsPath(ean: string) {
|
||||
return this._productNavigationService.getArticleDetailsPath({
|
||||
processId: this.processId,
|
||||
ean,
|
||||
});
|
||||
}
|
||||
|
||||
openPrintModal(id: number) {
|
||||
this.uiModal.open({
|
||||
content: PrintModalComponent,
|
||||
|
||||
@@ -3,30 +3,6 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
import { CheckoutReviewComponent } from './checkout-review/checkout-review.component';
|
||||
import { CheckoutSummaryComponent } from './checkout-summary/checkout-summary.component';
|
||||
import { PageCheckoutComponent } from './page-checkout.component';
|
||||
import { CheckoutReviewDetailsComponent } from './checkout-review/details/checkout-review-details.component';
|
||||
|
||||
const auxiliaryRoutes = [
|
||||
{
|
||||
path: 'details',
|
||||
component: CheckoutReviewDetailsComponent,
|
||||
outlet: 'left',
|
||||
},
|
||||
{
|
||||
path: 'review',
|
||||
component: CheckoutReviewComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'summary',
|
||||
component: CheckoutSummaryComponent,
|
||||
outlet: 'main',
|
||||
},
|
||||
{
|
||||
path: 'summary/:orderIds',
|
||||
component: CheckoutSummaryComponent,
|
||||
outlet: 'main',
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -36,7 +12,6 @@ const routes: Routes = [
|
||||
{ path: 'summary', component: CheckoutSummaryComponent },
|
||||
{ path: 'summary/:orderIds', component: CheckoutSummaryComponent },
|
||||
{ path: 'review', component: CheckoutReviewComponent },
|
||||
...auxiliaryRoutes,
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'review' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,23 +1 @@
|
||||
<shared-breadcrumb class="my-4" [key]="breadcrumbKey$ | async" [tags]="['checkout']"></shared-breadcrumb>
|
||||
|
||||
<ng-container *ngIf="routerEvents$ | async">
|
||||
<ng-container *ngIf="!(isDesktop$ | async); else desktop">
|
||||
<router-outlet></router-outlet>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #desktop>
|
||||
<ng-container *ngIf="showMainOutlet$ | async">
|
||||
<router-outlet name="main"></router-outlet>
|
||||
</ng-container>
|
||||
|
||||
<div class="grid grid-cols-[minmax(31rem,.5fr)_1fr] gap-6">
|
||||
<div *ngIf="showLeftOutlet$ | async" class="block">
|
||||
<router-outlet name="left"></router-outlet>
|
||||
</div>
|
||||
|
||||
<div *ngIf="showRightOutlet$ | async">
|
||||
<router-outlet name="right"></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<shared-breadcrumb [key]="breadcrumbKey$ | async" [tags]="['checkout']"></shared-breadcrumb> <router-outlet></router-outlet>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout',
|
||||
@@ -13,29 +11,7 @@ import { map, shareReplay } from 'rxjs/operators';
|
||||
export class PageCheckoutComponent implements OnInit {
|
||||
readonly breadcrumbKey$ = this.applicationService.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||
|
||||
get isDesktop$() {
|
||||
return this._environmentService.matchDesktop$.pipe(
|
||||
map((state) => {
|
||||
return state.matches;
|
||||
}),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
routerEvents$ = this._router.events.pipe(shareReplay());
|
||||
|
||||
showMainOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'main')));
|
||||
|
||||
showLeftOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'left')));
|
||||
|
||||
showRightOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'right')));
|
||||
|
||||
constructor(
|
||||
private applicationService: ApplicationService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _router: Router,
|
||||
private _activatedRoute: ActivatedRoute
|
||||
) {}
|
||||
constructor(private applicationService: ApplicationService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
||||
|
||||
@@ -1,349 +0,0 @@
|
||||
<ng-container *ngIf="orderItem$ | async; let orderItem">
|
||||
<div class="grid grid-flow-row gap-px-2">
|
||||
<div class="bg-[#F5F7FA] flex flex-row justify-between items-center p-4 rounded-t">
|
||||
<div class="grid grid-flow-col gap-[0.4375rem] items-center" *ngIf="features$ | async; let features; else: featureLoading">
|
||||
<shared-icon *ngIf="features?.length > 0" [size]="24" icon="person"></shared-icon>
|
||||
<div class="grid grid-flow-col gap-2 items-center font-bold text-p2" *ngFor="let feature of features">
|
||||
{{ feature?.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
[disabled]="editButtonDisabled$ | async"
|
||||
class="page-customer-order-details-header__edit-cta bg-transparent text-brand font-bold border-none text-p1"
|
||||
*ngIf="editClick.observers.length"
|
||||
(click)="editClick.emit(orderItem)"
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-header__details bg-white px-4 pt-4 pb-5">
|
||||
<h2
|
||||
class="page-customer-order-details-header__details-header items-center"
|
||||
[class.mb-8]="!orderItem?.features?.paid && !isKulturpass"
|
||||
>
|
||||
<div class="text-h2">
|
||||
{{ orderItem?.organisation }}
|
||||
<ng-container *ngIf="!!orderItem?.organisation && (!!orderItem?.firstName || !!orderItem?.lastName)"> - </ng-container>
|
||||
{{ orderItem?.lastName }}
|
||||
{{ orderItem?.firstName }}
|
||||
</div>
|
||||
<div class="page-customer-order-details-header__header-compartment text-h3">
|
||||
{{ orderItem?.compartmentCode }}{{ orderItem?.compartmentInfo && '_' + orderItem?.compartmentInfo }}
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<div class="page-customer-order-details-header__paid-marker mt-[0.375rem]" *ngIf="orderItem?.features?.paid && !isKulturpass">
|
||||
<div class="font-bold w-fit desktop-small:text-p2 px-3 py-[0.125rem] rounded text-white bg-[#26830C]">
|
||||
{{ orderItem?.features?.paid }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-customer-order-details-header__paid-marker mt-[0.375rem] text-[#26830C]" *ngIf="isKulturpass">
|
||||
<svg class="fill-current mr-2" xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22">
|
||||
<path
|
||||
d="M880-740v520q0 24-18 42t-42 18H140q-24 0-42-18t-18-42v-520q0-24 18-42t42-18h680q24 0 42 18t18 42ZM140-631h680v-109H140v109Zm0 129v282h680v-282H140Zm0 282v-520 520Z"
|
||||
/>
|
||||
</svg>
|
||||
<strong> Bezahlt über KulturPass </strong>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-header__details-wrapper -mt-3">
|
||||
<div class="flex flex-row page-customer-order-details-header__buyer-number" data-detail-id="Kundennummer">
|
||||
<div class="w-[9rem]">Kundennummer</div>
|
||||
<div class="flex flex-row font-bold">{{ orderItem?.buyerNumber }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row page-customer-order-details-header__order-number" data-detail-id="VorgangId">
|
||||
<div class="w-[9rem]">Vorgang-ID</div>
|
||||
<div class="flex flex-row font-bold">{{ orderItem?.orderNumber }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row page-customer-order-details-header__order-date" data-detail-id="Bestelldatum">
|
||||
<div class="w-[9rem]">Bestelldatum</div>
|
||||
<div class="flex flex-row font-bold">{{ orderItem?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="flex flex-row page-customer-order-details-header__processing-status justify-between" data-detail-id="Status">
|
||||
<div class="w-[9rem]">Status</div>
|
||||
<div *ngIf="!(changeStatusLoader$ | async)" class="flex flex-row font-bold -mr-[0.125rem]">
|
||||
<shared-icon
|
||||
class="mr-2 text-black flex items-center justify-center"
|
||||
[size]="16"
|
||||
*ngIf="orderItem.processingStatus | processingStatus: 'icon'; let icon"
|
||||
[icon]="icon"
|
||||
></shared-icon>
|
||||
|
||||
<span *ngIf="!(canEditStatus$ | async)">
|
||||
{{ orderItem?.processingStatus | processingStatus }}
|
||||
</span>
|
||||
<ng-container *ngIf="canEditStatus$ | async">
|
||||
<button
|
||||
class="cta-status-dropdown"
|
||||
[uiOverlayTrigger]="statusDropdown"
|
||||
[disabled]="changeStatusDisabled$ | async"
|
||||
#dropdown="uiOverlayTrigger"
|
||||
>
|
||||
<div class="mr-[0.375rem]">
|
||||
{{ orderItem?.processingStatus | processingStatus }}
|
||||
</div>
|
||||
<shared-icon
|
||||
[size]="24"
|
||||
[class.rotate-0]="!dropdown.opened"
|
||||
[class.-rotate-180]="dropdown.opened"
|
||||
icon="arrow-drop-down"
|
||||
></shared-icon>
|
||||
</button>
|
||||
<ui-dropdown #statusDropdown yPosition="below" xPosition="after" [xOffset]="8">
|
||||
<button uiDropdownItem *ngFor="let action of statusActions$ | async" (click)="handleActionClick(action)">
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</ui-dropdown>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ui-spinner *ngIf="changeStatusLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"></ui-spinner>
|
||||
</div>
|
||||
<div class="flex flex-row page-customer-order-details-header__order-source" data-detail-id="Bestellkanal">
|
||||
<div class="w-[9rem]">Bestellkanal</div>
|
||||
<div class="flex flex-row font-bold">{{ order?.features?.orderSource }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-row page-customer-order-details-header__change-date justify-between"
|
||||
[ngSwitch]="orderItem.processingStatus"
|
||||
data-detail-id="Geaendert"
|
||||
>
|
||||
<!-- orderType 1 === Abholung / Rücklage; orderType 2 === Versand / B2B-Versand; orderType 4 === Download (ist manchmal auch orderType 2) -->
|
||||
<ng-container *ngIf="orderItem.orderType === 1; else changeDate">
|
||||
<ng-container *ngSwitchCase="16">
|
||||
<ng-container *ngTemplateOutlet="vslLieferdatum"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="8192">
|
||||
<ng-container *ngTemplateOutlet="vslLieferdatum"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="128">
|
||||
<ng-container *ngTemplateOutlet="abholfrist"></ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #changeDate>
|
||||
<div class="w-[9rem]">Geändert</div>
|
||||
<div class="flex flex-row font-bold">{{ orderItem?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row page-customer-order-details-header__pick-up justify-between"
|
||||
data-detail-id="Wunschdatum"
|
||||
*ngIf="orderItem.orderType === 1 && (orderItem.processingStatus === 16 || orderItem.processingStatus === 8192)"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="preferredPickUpDate"></ng-container>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col page-customer-order-details-header__dig-and-notification">
|
||||
<div
|
||||
*ngIf="orderItem.orderType === 1"
|
||||
class="flex flex-row page-customer-order-details-header__notification"
|
||||
data-detail-id="Benachrichtigung"
|
||||
>
|
||||
<div class="w-[9rem]">Benachrichtigung</div>
|
||||
<div class="flex flex-row font-bold">{{ (notificationsChannel | notificationsChannel) || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="!!digOrderNumber && orderItem.orderType !== 1"
|
||||
class="flex flex-row page-customer-order-details-header__dig-number"
|
||||
data-detail-id="Dig-Bestellnummer"
|
||||
>
|
||||
<div class="w-[9rem]">Dig-Bestell Nr.</div>
|
||||
<div class="flex flex-row font-bold">{{ digOrderNumber }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center relative bg-[#F5F7FA] p-4 rounded-t">
|
||||
<div *ngIf="showFeature" class="flex flex-row items-center mr-3">
|
||||
<ng-container [ngSwitch]="order.features.orderType">
|
||||
<ng-container *ngSwitchCase="'Versand'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-truck"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">Versand</p>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'DIG-Versand'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-truck"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">Versand</p>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'B2B-Versand'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-b2b-truck"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">B2B-Versand</p>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Abholung'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-box-out"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1 mr-3">Abholung</p>
|
||||
{{ orderItem.targetBranch }}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Rücklage'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-shopping-bag"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">Rücklage</p>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Download'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-download"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">Download</p>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-header__additional-addresses" *ngIf="showAddresses">
|
||||
<button (click)="openAddresses = !openAddresses" class="text-[#0556B4]">
|
||||
Lieferadresse / Rechnungsadresse {{ openAddresses ? 'ausblenden' : 'anzeigen' }}
|
||||
</button>
|
||||
|
||||
<div class="page-customer-order-details-header__addresses-popover" *ngIf="openAddresses">
|
||||
<button (click)="openAddresses = !openAddresses" class="close">
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
|
||||
<div class="page-customer-order-details-header__addresses-popover-data">
|
||||
<div *ngIf="order.shipping" class="page-customer-order-details-header__addresses-popover-delivery">
|
||||
<p>Lieferadresse</p>
|
||||
<div class="page-customer-order-details-header__addresses-popover-delivery-data">
|
||||
<ng-container *ngIf="order.shipping?.data?.organisation">
|
||||
<p>{{ order.shipping?.data?.organisation?.name }}</p>
|
||||
<p>{{ order.shipping?.data?.organisation?.department }}</p>
|
||||
</ng-container>
|
||||
<p>{{ order.shipping?.data?.firstName }} {{ order.shipping?.data?.lastName }}</p>
|
||||
<p>{{ order.shipping?.data?.address?.street }} {{ order.shipping?.data?.address?.streetNumber }}</p>
|
||||
<p>{{ order.shipping?.data?.address?.zipCode }} {{ order.shipping?.data?.address?.city }}</p>
|
||||
<p>{{ order.shipping?.data?.address?.info }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="order.billing" class="page-customer-order-details-header__addresses-popover-billing">
|
||||
<p>Rechnungsadresse</p>
|
||||
<div class="page-customer-order-details-header__addresses-popover-billing-data">
|
||||
<ng-container *ngIf="order.billing?.data?.organisation">
|
||||
<p>{{ order.billing?.data?.organisation?.name }}</p>
|
||||
<p>{{ order.billing?.data?.organisation?.department }}</p>
|
||||
</ng-container>
|
||||
<p>{{ order.billing?.data?.firstName }} {{ order.billing?.data?.lastName }}</p>
|
||||
<p>{{ order.billing?.data?.address?.street }} {{ order.billing?.data?.address?.streetNumber }}</p>
|
||||
<p>{{ order.billing?.data?.address?.zipCode }} {{ order.billing?.data?.address?.city }}</p>
|
||||
<p>{{ order.billing?.data?.address?.info }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-header__select grow" *ngIf="showMultiselect$ | async">
|
||||
<button class="cta-select-all" (click)="selectAll()">Alle auswählen</button>
|
||||
{{ selectedOrderItemCount$ | async }} von {{ orderItemCount$ | async }} Titeln
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #featureLoading>
|
||||
<div class="fetch-wrapper">
|
||||
<div class="fetching"></div>
|
||||
<div class="fetching"></div>
|
||||
<div class="fetching"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #abholfrist>
|
||||
<div class="w-[9rem]">Abholfrist</div>
|
||||
<div *ngIf="!(changeDateLoader$ | async)" class="flex flex-row font-bold">
|
||||
<button
|
||||
[uiOverlayTrigger]="deadlineDatepicker"
|
||||
#deadlineDatepickerTrigger="uiOverlayTrigger"
|
||||
[disabled]="!isKulturpass && (!!orderItem?.features?.paid || (changeDateDisabled$ | async))"
|
||||
class="cta-pickup-deadline"
|
||||
>
|
||||
<strong class="border-r border-[#AEB7C1] pr-4">
|
||||
{{ orderItem?.pickUpDeadline | date: 'dd.MM.yy' }}
|
||||
</strong>
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
#deadlineDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[xOffset]="8"
|
||||
[min]="minDateDatepicker"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="orderItem?.pickUpDeadline"
|
||||
(save)="updatePickupDeadline($event)"
|
||||
>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
<ui-spinner *ngIf="changeDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"></ui-spinner>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #preferredPickUpDate>
|
||||
<div class="w-[9rem]">Zurücklegen bis</div>
|
||||
<div *ngIf="!(changePreferredDateLoader$ | async)" class="flex flex-row font-bold">
|
||||
<button
|
||||
[uiOverlayTrigger]="preferredPickUpDatePicker"
|
||||
#preferredPickUpDatePickerTrigger="uiOverlayTrigger"
|
||||
[disabled]="(!isKulturpass && !!orderItem?.features?.paid) || (changeDateDisabled$ | async)"
|
||||
class="cta-pickup-preferred"
|
||||
>
|
||||
<strong *ngIf="preferredPickUpDate$ | async; let pickUpDate; else: selectTemplate">
|
||||
{{ pickUpDate | date: 'dd.MM.yy' }}
|
||||
</strong>
|
||||
<ng-template #selectTemplate>
|
||||
<strong class="border-r border-[#AEB7C1] pr-4">Auswählen</strong>
|
||||
</ng-template>
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
#preferredPickUpDatePicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[xOffset]="8"
|
||||
[min]="minDateDatepicker"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="(preferredPickUpDate$ | async) || today"
|
||||
(save)="updatePreferredPickUpDate($event)"
|
||||
>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
<ui-spinner *ngIf="changePreferredDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"> </ui-spinner>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #vslLieferdatum>
|
||||
<div class="w-[9rem]">vsl. Lieferdatum</div>
|
||||
<div *ngIf="!(changeDateLoader$ | async)" class="flex flex-row font-bold">
|
||||
<button
|
||||
class="cta-datepicker"
|
||||
[disabled]="changeDateDisabled$ | async"
|
||||
[uiOverlayTrigger]="uiDatepicker"
|
||||
#datepicker="uiOverlayTrigger"
|
||||
>
|
||||
<span class="border-r border-[#AEB7C1] pr-4">
|
||||
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
|
||||
</span>
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
#uiDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[xOffset]="8"
|
||||
[min]="minDateDatepicker"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="orderItem?.estimatedShippingDate"
|
||||
(save)="updateEstimatedShippingDate($event)"
|
||||
>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
<ui-spinner *ngIf="changeDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"></ui-spinner>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
@@ -1,140 +0,0 @@
|
||||
:host {
|
||||
@apply block mb-[0.125rem];
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__edit-cta {
|
||||
&:disabled {
|
||||
@apply text-inactive-customer;
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__paid-marker {
|
||||
@apply font-bold flex items-center justify-end;
|
||||
|
||||
shared-icon {
|
||||
@apply mr-2 text-white bg-green-600 w-px-25 h-px-25 flex items-center justify-center rounded-full;
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__details {
|
||||
@apply grid grid-flow-row pt-4;
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__details-header {
|
||||
@apply flex flex-row justify-between font-bold;
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__details-wrapper {
|
||||
@apply grid grid-flow-row gap-x-6 gap-y-[0.375rem];
|
||||
grid-template-columns: 50% auto;
|
||||
grid-template-areas:
|
||||
'buyernumber .'
|
||||
'ordernumber .'
|
||||
'orderdate processingstatus'
|
||||
'ordersource changedate'
|
||||
'dignotification pickup';
|
||||
|
||||
.detail {
|
||||
shared-icon {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__buyer-number {
|
||||
grid-area: buyernumber;
|
||||
}
|
||||
.page-customer-order-details-header__order-number {
|
||||
grid-area: ordernumber;
|
||||
}
|
||||
.page-customer-order-details-header__order-date {
|
||||
grid-area: orderdate;
|
||||
}
|
||||
.page-customer-order-details-header__processing-status {
|
||||
grid-area: processingstatus;
|
||||
}
|
||||
.page-customer-order-details-header__order-source {
|
||||
grid-area: ordersource;
|
||||
}
|
||||
.page-customer-order-details-header__change-date {
|
||||
grid-area: changedate;
|
||||
}
|
||||
.page-customer-order-details-header__pick-up {
|
||||
grid-area: pickup;
|
||||
}
|
||||
.page-customer-order-details-header__dig-and-notification {
|
||||
grid-area: dignotification;
|
||||
}
|
||||
|
||||
.cta-status-dropdown,
|
||||
.cta-datepicker,
|
||||
.cta-pickup-deadline,
|
||||
.cta-pickup-preferred {
|
||||
@apply flex flex-row border-none outline-none text-p2 font-bold bg-transparent items-center px-0 mx-0;
|
||||
|
||||
&:disabled {
|
||||
@apply text-disabled-customer cursor-not-allowed;
|
||||
|
||||
shared-icon {
|
||||
@apply text-disabled-customer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__select {
|
||||
@apply flex flex-col items-end;
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__additional-addresses {
|
||||
.page-customer-order-details-header__addresses-popover {
|
||||
@apply absolute inset-x-0 top-16 bottom-0 z-popover;
|
||||
|
||||
.close {
|
||||
@apply bg-white absolute right-0 p-6;
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-data {
|
||||
@apply flex flex-col bg-white p-6 z-popover min-h-[200px];
|
||||
box-shadow: 0px 6px 24px rgba(206, 212, 219, 0.8);
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-delivery {
|
||||
@apply grid mb-6;
|
||||
grid-template-columns: 153px auto;
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-delivery-data {
|
||||
p {
|
||||
@apply font-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-billing {
|
||||
@apply grid;
|
||||
grid-template-columns: 153px auto;
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-billing-data {
|
||||
p {
|
||||
@apply font-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cta-select-all {
|
||||
@apply text-brand bg-transparent text-p2 font-bold outline-none border-none;
|
||||
}
|
||||
|
||||
.fetch-wrapper {
|
||||
@apply grid grid-flow-col gap-4;
|
||||
}
|
||||
|
||||
.fetching {
|
||||
@apply w-24 h-px-20 bg-customer;
|
||||
animation: load 0.75s linear infinite;
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Output,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
import { NotificationChannel } from '@swagger/checkout';
|
||||
import { KeyValueDTOOfStringAndString, OrderDTO, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { CustomerOrderDetailsStore } from '../customer-order-details.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details-header',
|
||||
templateUrl: 'customer-order-details-header.component.html',
|
||||
styleUrls: ['customer-order-details-header.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderDetailsHeaderComponent implements OnChanges {
|
||||
@Output()
|
||||
editClick = new EventEmitter<OrderItemListItemDTO>();
|
||||
|
||||
@Output()
|
||||
handleAction = new EventEmitter<KeyValueDTOOfStringAndString>();
|
||||
|
||||
@Input()
|
||||
order: OrderDTO;
|
||||
|
||||
get isKulturpass() {
|
||||
return this.order?.features?.orderSource === 'KulturPass';
|
||||
}
|
||||
|
||||
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);
|
||||
today = this.dateAdapter.today();
|
||||
|
||||
selectedOrderItemCount$ = this._store.selectedeOrderItemSubsetIds$.pipe(map((ids) => ids?.length ?? 0));
|
||||
|
||||
orderItemCount$ = this._store.items$.pipe(map((items) => items?.length ?? 0));
|
||||
|
||||
orderItem$ = this._store.items$.pipe(map((orderItems) => orderItems?.find((_) => true)));
|
||||
|
||||
preferredPickUpDate$ = new BehaviorSubject<Date>(undefined);
|
||||
|
||||
notificationsChannel: NotificationChannel = 0;
|
||||
|
||||
changeDateLoader$ = new BehaviorSubject<boolean>(false);
|
||||
changePreferredDateLoader$ = new BehaviorSubject<boolean>(false);
|
||||
changeStatusLoader$ = new BehaviorSubject<boolean>(false);
|
||||
changeStatusDisabled$ = this._store.changeActionDisabled$;
|
||||
changeDateDisabled$ = this.changeStatusDisabled$;
|
||||
|
||||
features$ = this.orderItem$.pipe(
|
||||
filter((orderItem) => !!orderItem),
|
||||
switchMap((orderItem) =>
|
||||
this.customerService.getCustomers(orderItem.buyerNumber).pipe(
|
||||
map((res) => res.result.find((c) => c.customerNumber === orderItem.buyerNumber)),
|
||||
map((customer) => customer?.features || []),
|
||||
map((features) => features.filter((f) => f.enabled && !!f.description))
|
||||
)
|
||||
),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
statusActions$ = this.orderItem$.pipe(map((orderItem) => orderItem?.actions?.filter((action) => action.enabled === false)));
|
||||
|
||||
showMultiselect$ = combineLatest([this._store.items$, this._store.fetchPartial$, this._store.itemsSelectable$]).pipe(
|
||||
map(([orderItems, fetchPartial, multiSelect]) => multiSelect && fetchPartial && orderItems?.length > 1)
|
||||
);
|
||||
|
||||
crudaUpdate$ = this.orderItem$.pipe(map((orederItem) => !!(orederItem?.cruda & 4)));
|
||||
|
||||
editButtonDisabled$ = combineLatest([this.changeStatusLoader$, this.crudaUpdate$]).pipe(
|
||||
map(([changeStatusLoader, crudaUpdate]) => changeStatusLoader || !crudaUpdate)
|
||||
);
|
||||
|
||||
canEditStatus$ = combineLatest([this.statusActions$, this.crudaUpdate$]).pipe(
|
||||
map(([statusActions, crudaUpdate]) => statusActions?.length > 0 && crudaUpdate)
|
||||
);
|
||||
|
||||
openAddresses: boolean = false;
|
||||
|
||||
get digOrderNumber(): string {
|
||||
return this.order?.linkedRecords?.find((_) => true)?.number;
|
||||
}
|
||||
|
||||
get showAddresses(): boolean {
|
||||
return (this.order?.orderType === 2 || this.order?.orderType === 4) && (!!this.order?.shipping || !!this.order?.billing);
|
||||
}
|
||||
|
||||
get showFeature(): boolean {
|
||||
return !!this.order?.features && !!this.order?.features?.orderType;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _store: CustomerOrderDetailsStore,
|
||||
private customerService: CrmCustomerService,
|
||||
private dateAdapter: DateAdapter,
|
||||
private omsService: DomainOmsService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.order) {
|
||||
this.findLatestPreferredPickUpDate();
|
||||
this.computeNotificationChannel();
|
||||
}
|
||||
}
|
||||
|
||||
computeNotificationChannel() {
|
||||
const order = this.order;
|
||||
this.notificationsChannel = order?.notificationChannels ?? 0;
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
async updatePickupDeadline(deadline: Date) {
|
||||
this.changeDateLoader$.next(true);
|
||||
this.changeStatusDisabled$.next(true);
|
||||
const orderItems = cloneDeep(this._store.items);
|
||||
for (const item of orderItems) {
|
||||
if (this.dateAdapter.compareDate(deadline, new Date(item.pickUpDeadline)) !== 0) {
|
||||
try {
|
||||
const res = await this.omsService
|
||||
.setPickUpDeadline(item.orderId, item.orderItemId, item.orderItemSubsetId, deadline?.toISOString())
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
item.pickUpDeadline = deadline.toISOString();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.changeDateLoader$.next(false);
|
||||
this.changeStatusDisabled$.next(false);
|
||||
this._store.updateOrderItems(orderItems);
|
||||
}
|
||||
|
||||
async updateEstimatedShippingDate(estimatedShippingDate: Date) {
|
||||
this.changeDateLoader$.next(true);
|
||||
this.changeStatusDisabled$.next(true);
|
||||
const orderItems = cloneDeep(this._store.items);
|
||||
for (const item of orderItems) {
|
||||
if (this.dateAdapter.compareDate(estimatedShippingDate, new Date(item.pickUpDeadline)) !== 0) {
|
||||
try {
|
||||
const res = await this.omsService
|
||||
.setEstimatedShippingDate(item.orderId, item.orderItemId, item.orderItemSubsetId, estimatedShippingDate?.toISOString())
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
item.estimatedShippingDate = res.estimatedShippingDate;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.changeDateLoader$.next(false);
|
||||
this.changeStatusDisabled$.next(false);
|
||||
this._store.updateOrderItems(orderItems);
|
||||
}
|
||||
|
||||
async handleActionClick(action: KeyValueDTOOfStringAndString) {
|
||||
this.changeStatusLoader$.next(true);
|
||||
this.handleAction.emit(action);
|
||||
setTimeout(() => this.changeStatusLoader$.next(false), 1000);
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
this._store.selectAllOrderItems();
|
||||
}
|
||||
|
||||
async updatePreferredPickUpDate(date: Date) {
|
||||
this.changePreferredDateLoader$.next(true);
|
||||
this.changeStatusDisabled$.next(true);
|
||||
const orderItems = cloneDeep(this._store.items);
|
||||
|
||||
const data: Record<string, string> = {};
|
||||
orderItems.forEach((item) => {
|
||||
data[`${item.orderItemSubsetId}`] = date?.toISOString();
|
||||
});
|
||||
|
||||
try {
|
||||
await this.omsService.setPreferredPickUpDate({ data }).toPromise();
|
||||
this.order.items.forEach((item) => {
|
||||
item.data.subsetItems.forEach((subsetItem) => (subsetItem.data.preferredPickUpDate = date.toISOString()));
|
||||
});
|
||||
this.findLatestPreferredPickUpDate();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
this.changePreferredDateLoader$.next(false);
|
||||
this.changeStatusDisabled$.next(false);
|
||||
}
|
||||
|
||||
findLatestPreferredPickUpDate() {
|
||||
let latestDate;
|
||||
const subsetItems = this.order?.items
|
||||
?.reduce((acc, item) => {
|
||||
return [...acc, ...item.data.subsetItems];
|
||||
}, [])
|
||||
?.filter((a) => !!a.data.preferredPickUpDate);
|
||||
if (subsetItems?.length > 0) {
|
||||
latestDate = new Date(
|
||||
subsetItems?.reduce((a, b) => {
|
||||
return new Date(a.data.preferredPickUpDate) > new Date(b.data.preferredPickUpDate) ? a : b;
|
||||
})?.data?.preferredPickUpDate
|
||||
);
|
||||
}
|
||||
this.preferredPickUpDate$.next(latestDate);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// start:ng42.barrel
|
||||
export * from './customer-order-details-header.component';
|
||||
// end:ng42.barrel
|
||||
@@ -1,233 +0,0 @@
|
||||
<ng-container *ngIf="orderItem$ | async; let orderItem">
|
||||
<div #features class="page-customer-order-details-item__features">
|
||||
<ng-container *ngIf="orderItem?.features?.prebooked">
|
||||
<img [uiOverlayTrigger]="prebookedTooltip" src="/assets/images/tag_icon_preorder.svg" [alt]="orderItem?.features?.prebooked" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #prebookedTooltip [closeable]="true">
|
||||
Artikel wird für Sie vorgemerkt.
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="notificationsSent$ | async; let notificationsSent">
|
||||
<ng-container *ngIf="notificationsSent?.NOTIFICATION_EMAIL">
|
||||
<img [uiOverlayTrigger]="emailTooltip" src="/assets/images/email_bookmark.svg" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #emailTooltip [closeable]="true">
|
||||
Per E-Mail benachrichtigt <br />
|
||||
<ng-container *ngFor="let notification of notificationsSent?.NOTIFICATION_EMAIL">
|
||||
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
|
||||
</ng-container>
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="notificationsSent?.NOTIFICATION_SMS">
|
||||
<img [uiOverlayTrigger]="smsTooltip" src="/assets/images/sms_bookmark.svg" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #smsTooltip [closeable]="true">
|
||||
Per SMS benachrichtigt <br />
|
||||
<ng-container *ngFor="let notification of notificationsSent?.NOTIFICATION_SMS">
|
||||
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
|
||||
</ng-container>
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="page-customer-order-details-item__item-container">
|
||||
<div class="page-customer-order-details-item__thumbnail">
|
||||
<img [src]="orderItem.product?.ean | productImage" [alt]="orderItem.product?.name" />
|
||||
</div>
|
||||
<div class="page-customer-order-details-item__details">
|
||||
<div class="flex flex-row justify-between items-start mb-[1.3125rem]">
|
||||
<h3
|
||||
[uiElementDistance]="features"
|
||||
#elementDistance="uiElementDistance"
|
||||
[style.max-width.px]="elementDistance.distanceChange | async"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div class="font-normal mb-[0.375rem]">{{ orderItem.product?.contributors }}</div>
|
||||
<div>{{ orderItem.product?.name }}</div>
|
||||
</h3>
|
||||
<div class="history-wrapper flex flex-col items-end justify-center">
|
||||
<button class="cta-history text-p1" (click)="historyClick.emit(orderItem)">
|
||||
Historie
|
||||
</button>
|
||||
|
||||
<input
|
||||
*ngIf="selectable$ | async"
|
||||
[ngModel]="selected$ | async"
|
||||
(ngModelChange)="setSelected($event)"
|
||||
class="isa-select-bullet mt-4"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Menge</div>
|
||||
<div class="value">
|
||||
<ng-container *ngIf="!(canChangeQuantity$ | async)"> {{ orderItem?.quantity }}x </ng-container>
|
||||
<ui-quantity-dropdown
|
||||
*ngIf="canChangeQuantity$ | async"
|
||||
[showTrash]="false"
|
||||
[range]="orderItem?.quantity"
|
||||
[(ngModel)]="quantity"
|
||||
[showSpinner]="false"
|
||||
>
|
||||
</ui-quantity-dropdown>
|
||||
<span class="overall-quantity">(von {{ orderItem?.overallQuantity }})</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.product?.formatDetail">
|
||||
<div class="label">Format</div>
|
||||
<div class="value">
|
||||
<img
|
||||
*ngIf="orderItem?.product?.format && orderItem?.product?.format !== 'UNKNOWN'"
|
||||
class="format-icon"
|
||||
[src]="'/assets/images/Icon_' + orderItem.product?.format + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
<span>{{ orderItem.product?.formatDetail }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.product?.ean">
|
||||
<div class="label">ISBN/EAN</div>
|
||||
<div class="value">{{ orderItem.product?.ean }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.price">
|
||||
<div class="label">Preis</div>
|
||||
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.retailPrice?.vat?.inPercent">
|
||||
<div class="label">MwSt</div>
|
||||
<div class="value">{{ orderItem.retailPrice?.vat?.inPercent }}%</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
|
||||
<div class="detail" *ngIf="orderItem.supplier">
|
||||
<div class="label">Lieferant</div>
|
||||
<div class="value">{{ orderItem.supplier }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.ssc || !!orderItem.sscText">
|
||||
<div class="label">Meldenummer</div>
|
||||
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.targetBranch">
|
||||
<div class="label">Zielfiliale</div>
|
||||
<div class="value">{{ orderItem.targetBranch }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
orderItemFeature(orderItem) === 'Versand' ||
|
||||
orderItemFeature(orderItem) === 'B2B-Versand' ||
|
||||
orderItemFeature(orderItem) === 'DIG-Versand'
|
||||
"
|
||||
>{{ orderItem?.estimatedDelivery ? 'Lieferung zwischen' : 'Lieferung ab' }}</ng-container
|
||||
>
|
||||
<ng-container *ngIf="orderItemFeature(orderItem) === 'Abholung' || orderItemFeature(orderItem) === 'Rücklage'">
|
||||
Abholung ab
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!!orderItem?.estimatedDelivery || !!orderItem?.estimatedShippingDate">
|
||||
<div class="value bg-[#D8DFE5] rounded w-max px-2">
|
||||
<ng-container *ngIf="!!orderItem?.estimatedDelivery; else estimatedShippingDate">
|
||||
{{ orderItem?.estimatedDelivery?.start | date: 'dd.MM.yy' }} und
|
||||
{{ orderItem?.estimatedDelivery?.stop | date: 'dd.MM.yy' }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #estimatedShippingDate>
|
||||
<ng-container *ngIf="!!orderItem?.estimatedShippingDate">
|
||||
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
|
||||
<div class="detail" *ngIf="!!orderItem?.compartmentCode">
|
||||
<div class="label">Abholfachnr.</div>
|
||||
<div class="value">{{ orderItem?.compartmentCode }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Vormerker</div>
|
||||
<div class="value">{{ orderItem.isPrebooked ? 'Ja' : 'Nein' }}</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
|
||||
<div class="detail" *ngIf="!!orderItem.paymentProcessing">
|
||||
<div class="label">Zahlungsweg</div>
|
||||
<div class="value">{{ orderItem.paymentProcessing || '-' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.paymentType">
|
||||
<div class="label">Zahlungsart</div>
|
||||
<div class="value">{{ orderItem.paymentType | paymentType }}</div>
|
||||
</div>
|
||||
|
||||
<h4 class="receipt-header" *ngIf="receiptCount$ | async; let count">
|
||||
{{ count > 1 ? 'Belege' : 'Beleg' }}
|
||||
</h4>
|
||||
<ng-container *ngFor="let receipt of receipts$ | async">
|
||||
<div class="detail" *ngIf="!!receipt?.receiptNumber">
|
||||
<div class="label">Belegnummer</div>
|
||||
<div class="value">{{ receipt?.receiptNumber }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!receipt?.printedDate">
|
||||
<div class="label">Erstellt am</div>
|
||||
<div class="value">{{ receipt?.printedDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!receipt?.receiptText">
|
||||
<div class="label">Rechnungstext</div>
|
||||
<div class="value">{{ receipt?.receiptText || '-' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!receipt?.receiptType">
|
||||
<div class="label">Belegart</div>
|
||||
<div class="value">
|
||||
{{ receipt?.receiptType === 1 ? 'Lieferschein' : receipt?.receiptType === 64 ? 'Zahlungsbeleg' : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="page-customer-order-details-item__comment flex flex-col items-start mt-[1.625rem]">
|
||||
<div class="label mb-[0.375rem]">Anmerkung</div>
|
||||
|
||||
<div class="flex flex-row w-full">
|
||||
<textarea
|
||||
matInput
|
||||
cdkTextareaAutosize
|
||||
#autosize="cdkTextareaAutosize"
|
||||
cdkAutosizeMinRows="1"
|
||||
cdkAutosizeMaxRows="5"
|
||||
maxlength="200"
|
||||
#specialCommentInput
|
||||
(keydown.delete)="triggerResize()"
|
||||
(keydown.backspace)="triggerResize()"
|
||||
type="text"
|
||||
name="comment"
|
||||
placeholder="Eine Anmerkung hinzufügen"
|
||||
[formControl]="specialCommentControl"
|
||||
[class.inactive]="!specialCommentControl.dirty"
|
||||
></textarea>
|
||||
|
||||
<div class="comment-actions">
|
||||
<button
|
||||
type="reset"
|
||||
class="clear"
|
||||
*ngIf="!!specialCommentControl.value?.length"
|
||||
(click)="specialCommentControl.setValue(''); saveSpecialComment(); triggerResize()"
|
||||
>
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
<button
|
||||
class="cta-save"
|
||||
type="submit"
|
||||
*ngIf="specialCommentControl?.enabled && specialCommentControl.dirty"
|
||||
(click)="saveSpecialComment()"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
@@ -1,123 +0,0 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-0.5 relative;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply px-1;
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__features {
|
||||
@apply absolute grid grid-flow-col gap-2 -top-1 right-24;
|
||||
|
||||
img {
|
||||
@apply w-12 h-12;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__item-container {
|
||||
@apply grid gap-8 bg-white p-4;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__thumbnail {
|
||||
img {
|
||||
@apply rounded shadow-cta w-[3.625rem] h-[5.9375rem];
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__details {
|
||||
@apply relative;
|
||||
|
||||
h3 {
|
||||
@apply mt-0 font-bold;
|
||||
}
|
||||
|
||||
.detail {
|
||||
@apply grid gap-x-7 my-1;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
|
||||
.label {
|
||||
@apply w-[8.125rem];
|
||||
}
|
||||
|
||||
.value {
|
||||
@apply flex flex-row items-center font-bold;
|
||||
|
||||
.format-icon {
|
||||
@apply mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.overall-quantity {
|
||||
@apply ml-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__comment {
|
||||
textarea {
|
||||
@apply w-full flex-grow rounded bg-[#EDEFF0] border-[#AEB7C1] border border-solid outline-none text-p2 p-4;
|
||||
resize: none;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
textarea.inactive {
|
||||
@apply text-warning font-bold;
|
||||
@apply w-full flex-grow rounded bg-[#EDEFF0] border-[#AEB7C1] border border-solid outline-none text-p2 p-4 text-warning font-bold;
|
||||
// ipad color fix
|
||||
-webkit-text-fill-color: rgb(190, 129, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
textarea::placeholder,
|
||||
textarea.inactive::placeholder {
|
||||
@apply text-[#89949E] font-normal;
|
||||
-webkit-text-fill-color: #89949e;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply flex-grow bg-transparent border-none outline-none text-p2 mx-4;
|
||||
}
|
||||
|
||||
input.inactive {
|
||||
@apply text-warning font-bold;
|
||||
@apply flex-grow bg-transparent border-none outline-none text-p2 mx-4 text-warning font-bold;
|
||||
// ipad color fix
|
||||
-webkit-text-fill-color: rgb(190, 129, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply bg-transparent text-brand font-bold text-p2 outline-none border-none;
|
||||
}
|
||||
|
||||
button.clear {
|
||||
@apply text-black;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
}
|
||||
|
||||
.history-wrapper {
|
||||
@apply text-right;
|
||||
}
|
||||
|
||||
.cta-history {
|
||||
@apply font-bold text-brand outline-none border-none bg-transparent -mr-1;
|
||||
}
|
||||
|
||||
.cta-edit,
|
||||
.cta-save {
|
||||
@apply -mr-1;
|
||||
}
|
||||
|
||||
ui-select-bullet {
|
||||
@apply absolute top-20 right-0 p-5;
|
||||
}
|
||||
|
||||
.receipt-header {
|
||||
@apply mb-1;
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ChangeDetectorRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { DomainOmsService, DomainReceiptService } from '@domain/oms';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { OrderDTO, OrderItemListItemDTO, ReceiptDTO, ReceiptType } from '@swagger/oms';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest, NEVER, Subject, Observable } from 'rxjs';
|
||||
import { catchError, filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { CustomerOrderDetailsStore } from '../customer-order-details.store';
|
||||
|
||||
export interface CustomerOrderDetailsItemComponentState {
|
||||
orderItem?: OrderItemListItemDTO;
|
||||
order?: OrderDTO;
|
||||
quantity?: number;
|
||||
receipts?: ReceiptDTO[];
|
||||
selected?: boolean;
|
||||
more: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details-item',
|
||||
templateUrl: 'customer-order-details-item.component.html',
|
||||
styleUrls: ['customer-order-details-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOrderDetailsItemComponentState> implements OnInit, OnDestroy {
|
||||
@ViewChild('autosize') autosize: CdkTextareaAutosize;
|
||||
|
||||
@Output()
|
||||
historyClick = new EventEmitter<OrderItemListItemDTO>();
|
||||
|
||||
@Output()
|
||||
specialCommentChanged = new EventEmitter<void>();
|
||||
|
||||
@Input()
|
||||
get orderItem() {
|
||||
return this.get((s) => s.orderItem);
|
||||
}
|
||||
set orderItem(orderItem: OrderItemListItemDTO) {
|
||||
if (!isEqual(this.orderItem, orderItem)) {
|
||||
// Remove Prev OrderItem from selected list
|
||||
this._store.selectOrderItem(this.orderItem, false);
|
||||
|
||||
this.patchState({ orderItem, quantity: orderItem?.quantity, receipts: [], more: false });
|
||||
this.specialCommentControl.reset(orderItem?.specialComment);
|
||||
|
||||
// Add New OrderItem to selected list if selected was set to true by its input
|
||||
if (this.get((s) => s.selected)) {
|
||||
this._store.selectOrderItem(this.orderItem, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
get order() {
|
||||
return this.get((s) => s.order);
|
||||
}
|
||||
set order(order: OrderDTO) {
|
||||
this.patchState({ order });
|
||||
}
|
||||
|
||||
readonly orderItem$ = this.select((s) => s.orderItem);
|
||||
|
||||
notificationsSent$ = this.orderItem$.pipe(
|
||||
filter((oi) => !!oi),
|
||||
switchMap((oi) =>
|
||||
this._omsService
|
||||
.getCompletedTasks({ orderId: oi.orderId, orderItemId: oi.orderItemId, orderItemSubsetId: oi.orderItemSubsetId, take: 4, skip: 0 })
|
||||
.pipe(catchError(() => NEVER))
|
||||
)
|
||||
);
|
||||
|
||||
canChangeQuantity$ = combineLatest([this.orderItem$, this._store.fetchPartial$]).pipe(
|
||||
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1)
|
||||
);
|
||||
|
||||
get quantity() {
|
||||
return this.get((s) => s.quantity);
|
||||
}
|
||||
set quantity(quantity: number) {
|
||||
if (this.quantity !== quantity) {
|
||||
this.patchState({ quantity });
|
||||
}
|
||||
}
|
||||
|
||||
readonly quantity$ = this.select((s) => s.quantity);
|
||||
|
||||
@Input()
|
||||
get selected() {
|
||||
return this._store.selectedeOrderItemSubsetIds.includes(this.orderItem?.orderItemSubsetId);
|
||||
}
|
||||
set selected(selected: boolean) {
|
||||
if (this.selected !== selected) {
|
||||
this.patchState({ selected });
|
||||
this._store.selectOrderItem(this.orderItem, selected);
|
||||
}
|
||||
}
|
||||
|
||||
readonly selected$ = combineLatest([this.orderItem$, this._store.selectedeOrderItemSubsetIds$]).pipe(
|
||||
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId))
|
||||
);
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<boolean>();
|
||||
|
||||
get selectable() {
|
||||
return this._store.itemsSelectable && this._store.items.length > 1 && this._store.fetchPartial;
|
||||
}
|
||||
|
||||
readonly selectable$ = combineLatest([this._store.items$, this._store.itemsSelectable$, this._store.fetchPartial$]).pipe(
|
||||
map(([orderItems, selectable, fetchPartial]) => orderItems.length > 1 && selectable && fetchPartial)
|
||||
);
|
||||
|
||||
get receipts() {
|
||||
return this.get((s) => s.receipts);
|
||||
}
|
||||
set receipts(receipts: ReceiptDTO[]) {
|
||||
if (!isEqual(this.receipts, receipts)) {
|
||||
this.patchState({ receipts });
|
||||
this._store.updateReceipts(receipts);
|
||||
}
|
||||
}
|
||||
|
||||
readonly receipts$ = this.select((s) => s.receipts);
|
||||
|
||||
readonly receiptCount$ = this.select((s) => s.receipts?.length);
|
||||
|
||||
specialCommentControl = new UntypedFormControl();
|
||||
|
||||
more$ = this.select((s) => s.more);
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
constructor(
|
||||
private _store: CustomerOrderDetailsStore,
|
||||
private _domainReceiptService: DomainReceiptService,
|
||||
private _omsService: DomainOmsService,
|
||||
private _cdr: ChangeDetectorRef
|
||||
) {
|
||||
super({
|
||||
more: false,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
ngOnDestroy() {
|
||||
// Remove Prev OrderItem from selected list
|
||||
this._store.selectOrderItem(this.orderItem, false);
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
loadReceipts = this.effect((done$: Observable<(receipts: ReceiptDTO[]) => void | undefined>) =>
|
||||
done$.pipe(
|
||||
withLatestFrom(this.orderItem$),
|
||||
filter(([_, orderItem]) => !!orderItem),
|
||||
switchMap(([done, orderItem]) =>
|
||||
this._domainReceiptService
|
||||
.getReceipts({
|
||||
receiptType: 65 as ReceiptType,
|
||||
ids: [orderItem.orderItemSubsetId],
|
||||
eagerLoading: 1,
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
const receipts = res.result.map((r) => r.item3?.data).filter((f) => !!f);
|
||||
this.receipts = receipts;
|
||||
|
||||
if (typeof done === 'function') {
|
||||
done?.(receipts);
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
if (typeof done === 'function') {
|
||||
done?.([]);
|
||||
}
|
||||
},
|
||||
() => {}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
async saveSpecialComment() {
|
||||
const { orderId, orderItemId, orderItemSubsetId } = this.orderItem;
|
||||
|
||||
try {
|
||||
this.specialCommentControl.reset(this.specialCommentControl.value);
|
||||
const res = await this._omsService
|
||||
.patchComment({ orderId, orderItemId, orderItemSubsetId, specialComment: this.specialCommentControl.value ?? '' })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
this.orderItem = { ...this.orderItem, specialComment: this.specialCommentControl.value ?? '' };
|
||||
this._store.updateOrderItems([this.orderItem]);
|
||||
this.specialCommentChanged.emit();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
setMore(more: boolean) {
|
||||
this.patchState({ more });
|
||||
if (more && this.receipts.length === 0) {
|
||||
this.loadReceipts(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
this.selected = selected;
|
||||
this.selectedChange.emit(selected);
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
orderItemFeature(orderItemListItem: OrderItemListItemDTO) {
|
||||
const orderItems = this.order?.items;
|
||||
return orderItems?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)?.data?.features?.orderType;
|
||||
}
|
||||
|
||||
triggerResize() {
|
||||
this.autosize.reset();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// start:ng42.barrel
|
||||
export * from './customer-order-details-item.component';
|
||||
// end:ng42.barrel
|
||||
@@ -1,28 +0,0 @@
|
||||
<div class="page-customer-order-details-tags__wrapper">
|
||||
<button
|
||||
class="page-customer-order-details-tags__tag"
|
||||
type="button"
|
||||
[class.selected]="tag === (selected$ | async) && !inputFocus.focused"
|
||||
*ngFor="let tag of defaultTags"
|
||||
(click)="setCompartmentInfo(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</button>
|
||||
<button
|
||||
(click)="inputFocus.focus()"
|
||||
type="button"
|
||||
class="page-customer-order-details-tags__tag"
|
||||
[class.selected]="(inputValue$ | async) === (selected$ | async) && (inputValue$ | async)"
|
||||
>
|
||||
<input
|
||||
#inputFocus="uiFocus"
|
||||
uiFocus
|
||||
type="text"
|
||||
[ngModel]="inputValue$ | async"
|
||||
(ngModelChange)="inputValueSubject.next($event); setCompartmentInfo(inputValue)"
|
||||
placeholder="..."
|
||||
[size]="controlSize$ | async"
|
||||
maxlength="15"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,40 +0,0 @@
|
||||
:host {
|
||||
@apply block bg-white p-4;
|
||||
}
|
||||
|
||||
.page-customer-order-details-tags__wrapper {
|
||||
@apply grid grid-flow-col justify-center gap-2 w-auto mx-auto;
|
||||
}
|
||||
|
||||
.page-customer-order-details-tags__tag {
|
||||
@apply w-auto text-p2 border border-solid bg-white border-inactive-customer py-px-10 px-5 font-bold text-inactive-customer rounded-full;
|
||||
|
||||
&:focus:not(.selected) {
|
||||
@apply bg-white border-inactive-customer text-inactive-customer;
|
||||
}
|
||||
|
||||
&.selected,
|
||||
&:focus-within {
|
||||
@apply bg-inactive-customer text-white;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply border-none outline-none text-p2 font-bold w-auto text-center text-inactive-customer bg-transparent p-0 m-0;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
@apply text-inactive-customer;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
input:focus::placeholder {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
&.selected input:not(:focus) {
|
||||
@apply text-white;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user