Compare commits

..

10 Commits

Author SHA1 Message Date
Lorenz Hilpert
330c59f7e9 #4134 Einlösecode- Warenkorb Pop Up 2023-06-27 15:03:07 +02:00
Lorenz Hilpert
0c65e9dace #4133 vsl. Lieferdatum und Zurücklegen bis deaktiviert 2023-06-26 13:58:58 +02:00
Lorenz Hilpert
9b8ea11866 Kulturpass - loader anzeige 2023-06-23 17:32:49 +02:00
Lorenz Hilpert
17aed38316 Kulturpass Scanner Fix 2023-06-23 16:57:35 +02:00
Lorenz Hilpert
48a7cc4dd4 Kulturpass - Shoppingcart erstellung vor dem start und checkout beim bestellen 2023-06-23 16:51:50 +02:00
Lorenz Hilpert
4417fd9f0d Kulturpass Mengenänderung Verfügbarkeit Check 2023-06-23 15:30:41 +02:00
Lorenz Hilpert
3e429cb261 Kulturpass Hinweistexte 2023-06-23 12:12:47 +02:00
Lorenz Hilpert
48e30e795f Kulturpass - Hinweis leerer Warenkorb 2023-06-23 11:47:27 +02:00
Lorenz Hilpert
953e528298 Kulturpass Warenkorb Mengenänderung und Entfernen 2023-06-23 11:36:38 +02:00
Lorenz Hilpert
4276f427b9 Kulturpass Warenkorb #4133 #4134 2023-06-23 10:39:19 +02:00
52 changed files with 1172 additions and 26 deletions

View File

@@ -11,13 +11,9 @@ export class DevScanAdapter implements ScanAdapter {
constructor(private _modal: UiModalService, private _environmentService: EnvironmentService) {}
async init(): Promise<boolean> {
if (this._environmentService.isTablet()) {
return new Promise((resolve, reject) => {
resolve(isDevMode());
});
}
return false;
return new Promise((resolve, reject) => {
resolve(isDevMode());
});
}
scan(): Observable<string> {

View File

@@ -1,4 +1,6 @@
import { CommandService } from './command.service';
export abstract class ActionHandler<T = any> {
constructor(readonly action: string) {}
abstract handler(data: T): Promise<T>;
abstract handler(data: T, service?: CommandService): Promise<T>;
}

View File

@@ -1,8 +1,12 @@
import { ModuleWithProviders, NgModule, Type } from '@angular/core';
import { ModuleWithProviders, NgModule, Provider, 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> {

View File

@@ -16,7 +16,7 @@ export class CommandService {
throw new Error('Action Handler does not exist');
}
data = await handler.handler(data);
data = await handler.handler(data, this);
}
return data;
}

View File

@@ -28,7 +28,13 @@ import {
StoreCheckoutBranchService,
ItemsResult,
} from '@swagger/checkout';
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
import {
DisplayOrderDTO,
DisplayOrderItemDTO,
OrderCheckoutService,
ReorderValues,
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
} 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';
@@ -372,8 +378,9 @@ export class DomainCheckoutService {
_setBuyer({ processId, buyer }: { processId: number; buyer: BuyerDTO }): Observable<CheckoutDTO> {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this._buyerService
mergeMap((checkout) => {
console.log('checkout', checkout, processId);
return this._buyerService
.StoreCheckoutBuyerSetBuyerPOST({
checkoutId: checkout?.id,
buyerDTO: buyer,
@@ -381,8 +388,8 @@ export class DomainCheckoutService {
.pipe(
map((response) => response.result),
tap((checkout) => this.store.dispatch(DomainCheckoutActions.setCheckout({ processId, checkout })))
)
)
);
})
);
}
@@ -710,6 +717,47 @@ 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,

View File

@@ -38,4 +38,5 @@ 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';

View File

@@ -1,4 +1,4 @@
import { OrderItemListItemDTO, ReceiptDTO } from '@swagger/oms';
import { OrderItemListItemDTO, ReceiptDTO, OrderDTO } from '@swagger/oms';
export interface OrderItemsContext {
items: OrderItemListItemDTO[];
@@ -12,4 +12,6 @@ export interface OrderItemsContext {
receipts?: ReceiptDTO[];
shippingDelayComment?: string;
order?: OrderDTO;
}

View File

@@ -0,0 +1,78 @@
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();
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();
}
}

View File

@@ -70,5 +70,8 @@
"checkForUpdates": 3600000,
"licence": {
"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=="
},
"kulturpass": {
"wertcodes": ["4035511298765", "4035511298697", "4035511298703", "4035511298710", "4035511298727", "4035511298598", "4035511298604", "4035511298611", "4035511298628", "4035511298734", "4035511298741", "4035511298758"]
}
}

View File

@@ -69,5 +69,8 @@
"checkForUpdates": 3600000,
"licence": {
"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=="
}
},
"kulturpass": {
"wertcodes": ["4035511298765", "4035511298697", "4035511298703", "4035511298710", "4035511298727", "4035511298598", "4035511298604", "4035511298611", "4035511298628", "4035511298734", "4035511298741", "4035511298758"]
}
}

View File

@@ -71,5 +71,8 @@
"checkForUpdates": 3600000,
"licence": {
"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=="
},
"kulturpass": {
"wertcodes": ["4035511298765", "4035511298697", "4035511298703", "4035511298710", "4035511298727", "4035511298598", "4035511298604", "4035511298611", "4035511298628", "4035511298734", "4035511298741", "4035511298758"]
}
}

View File

@@ -70,5 +70,8 @@
"checkForUpdates": 3600000,
"licence": {
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
}
},
"kulturpass": {
"wertcodes": ["4035511298765", "4035511298697", "4035511298703", "4035511298710", "4035511298727", "4035511298598", "4035511298604", "4035511298611", "4035511298628", "4035511298734", "4035511298741", "4035511298758"]
}
}

View File

@@ -70,5 +70,8 @@
"checkForUpdates": 3600000,
"licence": {
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
}
},
"kulturpass": {
"wertcodes": ["4035511298765", "4035511298697", "4035511298703", "4035511298710", "4035511298727", "4035511298598", "4035511298604", "4035511298611", "4035511298628", "4035511298734", "4035511298741", "4035511298758"]
}
}

View File

@@ -71,5 +71,8 @@
"checkForUpdates": 3600000,
"licence": {
"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=="
},
"kulturpass": {
"wertcodes": ["4035511298765", "4035511298697", "4035511298703", "4035511298710", "4035511298727", "4035511298598", "4035511298604", "4035511298611", "4035511298628", "4035511298734", "4035511298741", "4035511298758"]
}
}

View File

@@ -41,6 +41,7 @@ import {
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
ShopWithKulturpassActionHandler,
} from '@domain/oms';
import { CoreCommandModule } from '@core/command';
import { CustomerOrderRoutingModule } from './customer-order-routing.module';
@@ -93,6 +94,7 @@ import { BreadcrumbModule } from '@shared/components/breadcrumb';
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
ShopWithKulturpassActionHandler,
]),
],
exports: [CustomerOrderComponent],

View File

@@ -43,6 +43,7 @@ import {
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
ShopWithKulturpassActionHandler,
} from '@domain/oms';
@NgModule({
declarations: [GoodsInComponent],
@@ -89,6 +90,7 @@ import {
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
ShopWithKulturpassActionHandler,
]),
],
exports: [GoodsInComponent],

View File

@@ -41,6 +41,7 @@ import {
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
ShopWithKulturpassActionHandler,
} from '@domain/oms';
import { CoreCommandModule } from '@core/command';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
@@ -91,6 +92,7 @@ import { GoodsInRoutingModule } from './good-out-routing.module';
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
ShopWithKulturpassActionHandler,
]),
],
exports: [GoodsOutComponent],

View File

@@ -288,7 +288,7 @@
<button
[uiOverlayTrigger]="preferredPickUpDatePicker"
#preferredPickUpDatePickerTrigger="uiOverlayTrigger"
[disabled]="(!isKulturpass && !!orderItem?.features?.paid) || (changeDateDisabled$ | async)"
[disabled]="preferredPickUpDateDisabled$ | async"
class="cta-pickup-preferred"
>
<strong *ngIf="preferredPickUpDate$ | async; let pickUpDate; else: selectTemplate">
@@ -319,7 +319,7 @@
<div *ngIf="!(changeDateLoader$ | async)" class="value">
<button
class="cta-datepicker"
[disabled]="changeDateDisabled$ | async"
[disabled]="vslLieferdatumDisabled$ | async"
[uiOverlayTrigger]="uiDatepicker"
#datepicker="uiOverlayTrigger"
>

View File

@@ -18,6 +18,8 @@ import { cloneDeep } from 'lodash';
import { BehaviorSubject, combineLatest } from 'rxjs';
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
import { SharedGoodsInOutOrderDetailsComponent } from '../goods-in-out-order-details.component';
import { Config } from '@core/config';
import { coerceArray } from '@angular/cdk/coercion';
@Component({
selector: 'shared-goods-in-out-order-details-header',
@@ -100,12 +102,41 @@ export class SharedGoodsInOutOrderDetailsHeaderComponent implements OnChanges {
return !!this.order?.features && !!this.order?.features?.orderType;
}
get kulturpassWertcodes() {
return coerceArray(this.config.get('kulturpass.wertcodes'));
}
preferredPickUpDateDisabled$ = combineLatest([this.changeDateDisabled$, this.orderItem$]).pipe(
map(([changeDateDisabled, orderItem]) => {
if (changeDateDisabled) {
return true;
}
if (this.kulturpassWertcodes.includes(orderItem?.product?.ean)) {
return true;
}
return !this.isKulturpass && !!orderItem?.features?.paid;
})
);
vslLieferdatumDisabled$ = combineLatest([this.changeDateDisabled$, this.orderItem$]).pipe(
map(([changeDateDisabled, orderItem]) => {
if (changeDateDisabled) {
return true;
}
return this.kulturpassWertcodes.includes(orderItem?.product?.ean);
})
);
constructor(
@Host() private host: SharedGoodsInOutOrderDetailsComponent,
private customerService: CrmCustomerService,
private dateAdapter: DateAdapter,
private omsService: DomainOmsService,
private cdr: ChangeDetectorRef
private cdr: ChangeDetectorRef,
private config: Config
) {}
ngOnChanges(changes: SimpleChanges): void {

View File

@@ -12,6 +12,7 @@ import { SharedGoodsInOutOrderDetailsCoversComponent } from './goods-in-out-orde
import { SharedGoodsInOutOrderDetailsItemComponent } from './goods-in-out-order-details-item';
import { SharedGoodsInOutOrderDetailsTagsComponent } from './goods-in-out-order-details-tags';
import { SharedGoodsInOutOrderDetailsStore } from './goods-in-out-order-details.store';
import { SharedGoodsInOutOrderDetailsHeaderComponent } from './goods-in-out-order-details-header';
@Component({
selector: 'shared-goods-in-out-order-details',
@@ -26,6 +27,9 @@ import { SharedGoodsInOutOrderDetailsStore } from './goods-in-out-order-details.
],
})
export class SharedGoodsInOutOrderDetailsComponent extends SharedGoodsInOutOrderDetailsStore implements AfterContentInit, OnDestroy {
@ContentChild(SharedGoodsInOutOrderDetailsHeaderComponent)
orderDetailsHeaderComponent: SharedGoodsInOutOrderDetailsHeaderComponent;
@ContentChildren(SharedGoodsInOutOrderDetailsItemComponent)
orderDetailsItemComponents: QueryList<SharedGoodsInOutOrderDetailsItemComponent>;
@@ -205,6 +209,7 @@ export class SharedGoodsInOutOrderDetailsComponent extends SharedGoodsInOutOrder
action.command.includes('PRINT_PRICEDIFFQRCODELABEL') && !compartmentCode ? this.orderItems[0]?.compartmentCode : compartmentCode,
itemQuantity: this.getItemQuantityMap(),
receipts,
order: this.orderDetailsHeaderComponent?.order,
};
try {
commandData = await this.commandService.handleCommand(command, commandData);

View File

@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
.shared-loader {
@apply relative block h-full;
}
.shared-loader__spinner {
@apply absolute inset-0 flex items-center justify-center;
}
.shared-loader__background {
@apply bg-white bg-opacity-75;
}

View File

@@ -0,0 +1,6 @@
<div class="shared-loader__spinner" *ngIf="showLoader" [class.shared-loader__background]="showBackground">
<ui-icon class="animate-spin" icon="spinner" [size]="spinnerSizeInRem"></ui-icon>
</div>
<div [class.invisible]="showLoader && contentHidden">
<ng-content></ng-content>
</div>

View File

@@ -0,0 +1,43 @@
import { BooleanInput, NumberInput, coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
import { NgIf } from '@angular/common';
import { Component, ChangeDetectionStrategy, ViewEncapsulation, Input } from '@angular/core';
import { UiIconModule } from '@ui/icon';
@Component({
selector: 'shared-loader',
templateUrl: 'loader.component.html',
styleUrls: ['loader.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: { class: 'shared-loader' },
standalone: true,
imports: [NgIf, UiIconModule],
})
export class LoaderComponent {
@Input() loading: BooleanInput;
@Input() background: BooleanInput;
@Input() spinnerSize: NumberInput = 24;
@Input() hideContent: BooleanInput;
get showLoader() {
console.log('showLoader', this.loading);
return coerceBooleanProperty(this.loading);
}
get showBackground() {
return coerceBooleanProperty(this.background);
}
get spinnerSizeInRem() {
return coerceNumberProperty(this.spinnerSize) / 16 + 'rem';
}
get contentHidden() {
return coerceBooleanProperty(this.hideContent);
}
constructor() {}
}

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { LoaderComponent } from './loader.component';
@NgModule({
imports: [LoaderComponent],
exports: [LoaderComponent],
})
export class LoaderModule {}

View File

@@ -0,0 +1,2 @@
export * from './lib/loader.component';
export * from './lib/loader.module';

View File

@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,4 @@
:host {
@apply grid px-4 py-2 gap-4;
grid-template-columns: auto 1fr auto;
}

View File

@@ -0,0 +1,27 @@
<div class="img-wrapper">
<img class="rounded shadow w-20" [src]="item?.product?.ean | productImage" [alt]="item?.product?.name" />
</div>
<div>
<div class="text-sm">{{ item?.product?.contributors }}</div>
<div class="font-bold">{{ item?.product?.name }}</div>
<div class="font-bold mt-4 flex flex-row items-center">
<img class="h-5 mr-2" [src]="iconSrc" [alt]="item?.product?.formatDetail" />
<span>{{ item?.product?.formatDetail }}</span>
</div>
<div class="text-sm">{{ item?.product?.manufacturer }} | {{ item?.product?.ean }}</div>
<div class="text-sm">{{ item?.product?.volume }} | {{ item?.product?.publicationDate | date }}</div>
</div>
<div class="text-right">
<div class="font-bold text-xl mt-4">{{ item?.unitPrice?.value?.value | currency: 'EUR' }}</div>
<div class="font-bold">
<ui-quantity-dropdown
[quantity]="itemQuantity$ | async"
[minQuantity]="availableQuantity$ | async"
(quantityChange)="onQuantityChange($event)"
></ui-quantity-dropdown>
</div>
<div *ngIf="maxQuantityHint" class="text-warning font-bold text-sm">
Es sind maximal <br />
{{ availableQuantity$ | async }} Exemplare verfügbar
</div>
</div>

View File

@@ -0,0 +1,62 @@
import { CommonModule } from '@angular/common';
import { Component, ChangeDetectionStrategy, Output, Input, EventEmitter } from '@angular/core';
import { ProductImageModule } from '@cdn/product-image';
import { provideComponentStore } from '@ngrx/component-store';
import { ShoppingCartItemDTO } from '@swagger/checkout';
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
import { KulturpassOrderItemStore } from './kulturpass-order-item.store';
@Component({
selector: 'shared-kulturpass-order-item',
templateUrl: 'kulturpass-order-item.component.html',
styleUrls: ['kulturpass-order-item.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'shared-kulturpass-order-item' },
standalone: true,
imports: [ProductImageModule, CommonModule, UiQuantityDropdownModule],
providers: [provideComponentStore(KulturpassOrderItemStore)],
})
export class KulturpassOrderItemComponent {
@Input()
set item(item: ShoppingCartItemDTO) {
this._store.updateItem(item);
this._store.fetchAvailability(item);
}
get item() {
return this._store.item;
}
@Output() quantityChange = new EventEmitter<ShoppingCartItemDTO>();
itemQuantity$ = this._store.itemQuantity$;
get iconSrc() {
return `/assets/images/Icon_${this.item?.product?.format}.svg`;
}
availableQuantity$ = this._store.availableQuantity$;
maxQuantityHint = false;
constructor(private _store: KulturpassOrderItemStore) {}
onQuantityChange(quantity: number) {
const initialQuantity = this.item.quantity;
this._store.updateItem({ ...this.item, quantity });
if (this._store.availableQuantity < quantity) {
quantity = this._store.availableQuantity;
this.maxQuantityHint = true;
} else {
this.maxQuantityHint = false;
}
if (initialQuantity !== quantity) {
this.quantityChange.emit({ ...this.item, quantity });
}
setTimeout(() => {
this._store.updateItem({ ...this.item, quantity });
}, 1);
}
}

View File

@@ -0,0 +1,67 @@
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ShoppingCartItemDTO, AvailabilityDTO } from '@swagger/checkout';
import { DomainAvailabilityService } from '@domain/availability';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
export interface KulturpassOrderItemState {
item: ShoppingCartItemDTO;
availability?: AvailabilityDTO;
}
@Injectable()
export class KulturpassOrderItemStore extends ComponentStore<KulturpassOrderItemState> {
get item() {
return this.get((s) => s.item);
}
get availability() {
return this.get((s) => s.availability);
}
get availableQuantity() {
return this.get((s) => s.availability?.inStock ?? s.item?.quantity ?? 0);
}
constructor(private _availabilityService: DomainAvailabilityService) {
super({ item: null });
}
readonly item$ = this.select((state) => state.item);
readonly updateItem = this.updater((state, item: ShoppingCartItemDTO) => ({ ...state, item }));
readonly itemQuantity$ = this.select((state) => state.item?.quantity ?? 0);
readonly availability$ = this.select((state) => state.availability);
readonly updateAvailability = this.updater((state, availability: AvailabilityDTO) => ({ ...state, availability }));
readonly availableQuantity$ = this.select((state) => state.availability?.inStock ?? state.item?.quantity ?? 0);
fetchAvailability = this.effect((item$: Observable<ShoppingCartItemDTO>) =>
item$.pipe(
switchMap((item) =>
this._availabilityService
.getTakeAwayAvailability({
item: {
ean: item.product.ean,
itemId: Number(item.product.catalogProductNumber),
price: item.unitPrice,
},
quantity: item.quantity,
})
.pipe(tapResponse(this.handleAvailabilityResponse, this.handleAvailabilityError))
)
)
);
handleAvailabilityResponse = (res: AvailabilityDTO) => {
this.updateAvailability(res);
};
handleAvailabilityError = (err) => {
console.log(err);
};
}

View File

@@ -0,0 +1,8 @@
:host {
@apply block;
}
.wrapper {
@apply grid max-h-[calc(100vh-8rem)] h-[calc(100vh-8rem)];
grid-template-rows: repeat(4, auto) 1fr auto;
}

View File

@@ -0,0 +1,70 @@
<shared-loader [loading]="fetchShoppingCart$ | async" background="true" spinnerSize="36">
<div class="wrapper">
<div>
<h1 class="text-center text-2xl font-bold">Kulturpass Warenkorb</h1>
<p class="text-center text-lg">Bitte scannen Sie Artikel, um den Code einzulösen.</p>
</div>
<div>
<shared-kulturpass-order-searchbox (addToShoppingCart)="addToShoppingCart($event)"></shared-kulturpass-order-searchbox>
</div>
<div class="border-y border-solid border-[#EFF1F5] py-3 px-4 -mx-4 flex flex-row items-center">
<div class="w-[6.875rem]">
Wertgutschein
</div>
<div class="ml-4 font-bold">
{{ kulturpassCode$ | async }}
</div>
</div>
<div class="border-b border-solid border-[#EFF1F5] py-3 px-4 -mx-4 flex flex-row items-center">
<div class="w-[6.875rem]">
Filiale
</div>
<div class="ml-4 font-bold">
{{ branch$ | async | branchName: 'name-address' }}
</div>
</div>
<div class="overflow-y-auto -mx-4 scroll-bar">
<div *ngIf="emptyShoppingCart$ | async" class="h-full grid items-center justify-center">
<h3 class="text-xl font-bold text-center text-gray-500">
Warenkorb ist leer, bitte suchen oder scannen <br />
Sie Artikel um den Warenkob zu füllen.
</h3>
</div>
<shared-kulturpass-order-item
class="border-b border-solid border-[#EFF1F5]"
*ngFor="let item of items$ | async; trackBy: trackItemById"
[item]="item"
(quantityChange)="quantityChange($event)"
>
</shared-kulturpass-order-item>
</div>
<div class="flex flex-row justify-evenly items-stretch border-t border-solid border-[#EFF1F5] py-3 px-4 -mx-4">
<div class="grid grid-flow-row text-sm">
<div class="grid grid-cols-2 gap-5">
<div>Guthaben</div>
<div class="font-bold text-right">{{ credit$ | async | currency: 'EUR' }}</div>
</div>
<div class="grid grid-cols-2 gap-5">
<div>Summe</div>
<div class="font-bold text-right">{{ total$ | async | currency: 'EUR' }}</div>
</div>
<div class="grid grid-cols-2 gap-5">
<div>Restbetrag</div>
<div class="font-bold text-right" [class.text-brand]="negativeBalance$ | async">{{ balance$ | async | currency: 'EUR' }}</div>
</div>
</div>
<div class="grid items-end justify-end">
<button
type="button"
class="bg-brand text-white px-6 py-3 font-bold rounded-full disabled:bg-disabled-branch disabled:text-active-branch"
[disabled]="orderButtonDisabled$ | async"
(click)="order()"
>
<shared-loader [loading]="ordering$ | async" hideContent="true">
Kauf abschließen und Rechnung drucken
</shared-loader>
</button>
</div>
</div>
</div>
</shared-loader>

View File

@@ -0,0 +1,78 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { KulturpassOrderSearchboxComponent } from './kulturpass-order-searchbox/kulturpass-order-searchbox.component';
import { provideComponentStore } from '@ngrx/component-store';
import { KulturpassOrderModalStore } from './kulturpass-order-modal.store';
import { UiModalRef } from '@ui/modal';
import { KulturpassOrderModalData } from './kulturpass-order-modal.data';
import { AddToShoppingCartDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { map } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
import { BranchNamePipe } from '@shared/pipes/branch';
import { combineLatest } from 'rxjs';
import { KulturpassOrderItemComponent } from './kulturpass-order-item/kulturpass-order-item.component';
import { LoaderComponent } from '@shared/components/loader';
import { DisplayOrderDTO, KeyValueDTOOfStringAndString } from '@swagger/oms';
@Component({
selector: 'shared-kulturpass-order-modal',
templateUrl: 'kulturpass-order-modal.component.html',
styleUrls: ['kulturpass-order-modal.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'shared-kulturpass-order-modal' },
standalone: true,
imports: [KulturpassOrderSearchboxComponent, CommonModule, BranchNamePipe, KulturpassOrderItemComponent, LoaderComponent],
providers: [provideComponentStore(KulturpassOrderModalStore)],
})
export class KulturpassOrderModalComponent implements OnInit {
branch$ = this._store.branch$;
fetchShoppingCart$ = this._store.fetchShoppingCart$;
items$ = this._store.shoppingCart$.pipe(map((shoppingCart) => shoppingCart?.items?.map((item) => item.data) ?? []));
trackItemById = (index: number, item: ShoppingCartItemDTO) => item.id;
kulturpassCode$ = this._store.orderItemListItem$.pipe(map((orderItemListItem) => orderItemListItem.buyerNumber));
credit$ = this._store.orderItemListItem$.pipe(map((orderItemListItem) => orderItemListItem?.retailPrice?.value?.value ?? 0));
total$ = this._store.shoppingCart$.pipe(map((shoppingCart) => shoppingCart?.total?.value ?? 0));
balance$ = combineLatest([this.credit$, this.total$]).pipe(map(([credit, total]) => credit - total));
negativeBalance$ = this.balance$.pipe(map((balance) => balance < 0));
emptyShoppingCart$ = this.items$.pipe(map((items) => items.length === 0));
ordering$ = this._store.ordering$;
orderButtonDisabled$ = combineLatest([this.emptyShoppingCart$, this.negativeBalance$, this.ordering$]).pipe(
map(([emptyShoppingCart, negativeBalance, ordering]) => emptyShoppingCart || negativeBalance || ordering)
);
constructor(
private _modalRef: UiModalRef<[DisplayOrderDTO, string], KulturpassOrderModalData>,
private _store: KulturpassOrderModalStore
) {}
ngOnInit() {
this._store.updateOrder(this._modalRef.data.order);
this._store.createShoppingCart(this._modalRef.data.orderItemListItem);
}
addToShoppingCart(addToShoppingCart: AddToShoppingCartDTO) {
this._store.addItem(addToShoppingCart);
}
quantityChange(shoppingCartItem: ShoppingCartItemDTO) {
this._store.quantityChange(shoppingCartItem);
}
order() {
this._store.onOrderSuccess = async (displayOrder: DisplayOrderDTO, action: KeyValueDTOOfStringAndString[]) => {
const command = action.map((action) => action.command).join('|');
this._modalRef.close([displayOrder, command]);
};
this._store.orderItems();
}
}

View File

@@ -0,0 +1,10 @@
import { OrderItemListItemDTO, OrderDTO } from '@swagger/oms';
export interface KulturpassOrderModalData {
/**
* Guthaben des Kulturpasses
*/
orderItemListItem: OrderItemListItemDTO;
order: OrderDTO;
}

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { KulturpassOrderModalComponent } from './kulturpass-order-modal.component';
@NgModule({
imports: [KulturpassOrderModalComponent],
exports: [KulturpassOrderModalComponent],
})
export class KulturpassOrderModalModule {}

View File

@@ -0,0 +1,20 @@
import { UiModalRef, UiModalService } from '@ui/modal';
import { KulturpassOrderModalData } from './kulturpass-order-modal.data';
import { KulturpassOrderModalComponent } from './kulturpass-order-modal.component';
import { Injectable } from '@angular/core';
import { DisplayOrderDTO } from '@swagger/oms';
@Injectable({ providedIn: 'root' })
export class KulturpassOrderModalService {
constructor(private modal: UiModalService) {}
open(data: KulturpassOrderModalData): UiModalRef<[DisplayOrderDTO, string], KulturpassOrderModalData> {
return this.modal.open<[DisplayOrderDTO, string], KulturpassOrderModalData>({
content: KulturpassOrderModalComponent,
data,
config: {
backdropClose: false,
},
});
}
}

View File

@@ -0,0 +1,191 @@
import { Injectable } from '@angular/core';
import { ComponentStore, OnStoreInit, tapResponse } from '@ngrx/component-store';
import { AddToShoppingCartDTO, BranchDTO, CheckoutDTO, ShoppingCartDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { DomainCheckoutService } from '@domain/checkout';
import { switchMap, tap, withLatestFrom } from 'rxjs/operators';
import {
BranchService,
DisplayOrderDTO,
KeyValueDTOOfStringAndString,
OrderDTO,
OrderItemListItemDTO,
ResponseArgsOfIEnumerableOfBranchDTO,
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
} from '@swagger/oms';
import { Observable } from 'rxjs';
import { AuthService } from '@core/auth';
import { UiModalService } from '@ui/modal';
export interface KulturpassOrderModalState {
orderItemListItem?: OrderItemListItemDTO;
shoppingCart?: ShoppingCartDTO;
fetchShoppingCart?: boolean;
branch?: BranchDTO;
order?: OrderDTO;
ordering?: boolean;
}
@Injectable()
export class KulturpassOrderModalStore extends ComponentStore<KulturpassOrderModalState> implements OnStoreInit {
readonly processId = Date.now();
get orderItemListItem() {
return this.get((s) => s.orderItemListItem);
}
get shoppingCart() {
return this.get((s) => s.shoppingCart);
}
get branch() {
return this.get((s) => s.branch);
}
get order() {
return this.get((s) => s.order);
}
get fetchShoppingCart() {
return this.get((s) => s.fetchShoppingCart);
}
constructor(
private _checkoutService: DomainCheckoutService,
private _branchService: BranchService,
private _authService: AuthService,
private _modal: UiModalService
) {
super({});
}
ngrxOnStoreInit = () => {
this.loadBranch();
};
readonly orderItemListItem$ = this.select((state) => state.orderItemListItem);
readonly shoppingCart$ = this.select((state) => state.shoppingCart);
readonly branch$ = this.select((state) => state.branch);
readonly order$ = this.select((state) => state.order);
readonly updateCheckout = this.updater((state, checkout: CheckoutDTO) => ({ ...state, checkout }));
readonly updateOrder = this.updater((state, order: OrderDTO) => ({ ...state, order }));
readonly fetchShoppingCart$ = this.select((state) => state.fetchShoppingCart);
readonly updateFetchShoppingCart = this.updater((state, fetchShoppingCart: boolean) => ({ ...state, fetchShoppingCart }));
readonly ordering$ = this.select((state) => state.ordering);
loadBranch = this.effect(($) =>
$.pipe(switchMap(() => this._branchService.BranchGetBranches({}).pipe(tapResponse(this.handleBranchResponse, this.handleBranchError))))
);
handleBranchResponse = (res: ResponseArgsOfIEnumerableOfBranchDTO) => {
const branchNumber = this._authService.getClaimByKey('branch_no');
this.patchState({ branch: res.result.find((b) => b.branchNumber === branchNumber) });
};
handleBranchError = (err) => {
this._modal.error('Fehler beim Laden der Filiale', err);
};
createShoppingCart = this.effect((orderItemListItem$: Observable<OrderItemListItemDTO>) =>
orderItemListItem$.pipe(
tap((orderItemListItem) => {
this.patchState({ orderItemListItem });
this.updateFetchShoppingCart(true);
}),
switchMap((orderItemListItem) =>
this._checkoutService
.getShoppingCart({ processId: this.processId })
.pipe(tapResponse(this.handleCreateShoppingCartResponse, this.handleCreateShoppingCartError))
)
)
);
handleCreateShoppingCartResponse = (res: ShoppingCartDTO) => {
this.patchState({ shoppingCart: res });
this._checkoutService.setBuyer({ processId: this.processId, buyer: this.order.buyer });
this._checkoutService.setPayer({ processId: this.processId, payer: this.order.billing?.data });
this.updateFetchShoppingCart(false);
};
handleCreateShoppingCartError = (err: any) => {
this._modal.error('Fehler beim Laden des Warenkorbs', err);
this.updateFetchShoppingCart(false);
};
addItem = this.effect((add$: Observable<AddToShoppingCartDTO>) =>
add$.pipe(
withLatestFrom(this.orderItemListItem$),
switchMap(([add, orderItemListItem]) =>
this._checkoutService
.addItemToShoppingCart({
processId: this.processId,
items: [add],
})
.pipe(tapResponse(this.handleAddItemResponse, this.handleAddItemError))
)
)
);
handleAddItemResponse = (res: ShoppingCartDTO) => {};
handleAddItemError = (err: any) => {
this._modal.error('Fehler beim Hinzufügen des Artikels', err);
};
quantityChange = this.effect((change$: Observable<ShoppingCartItemDTO>) =>
change$.pipe(
withLatestFrom(this.orderItemListItem$),
switchMap(([change, orderItemListItem]) =>
this._checkoutService
.updateItemInShoppingCart({
processId: this.processId,
shoppingCartItemId: change.id,
update: { quantity: change.quantity },
})
.pipe(tapResponse(this.handleQuantityChangeResponse, this.handleQuantityChangeError))
)
)
);
handleQuantityChangeResponse = (res: ShoppingCartDTO) => {};
handleQuantityChangeError = (err: any) => {
this._modal.error('Fehler beim Ändern der Menge', err);
};
orderItems = this.effect(($: Observable<void>) =>
$.pipe(
tap(() => this.patchState({ ordering: true })),
withLatestFrom(this.orderItemListItem$),
switchMap(([_, orderItemListItem]) =>
this._checkoutService
.completeKulturpassOrder({
processId: this.processId,
orderItemSubsetId: orderItemListItem.orderItemSubsetId,
})
.pipe(tapResponse(this.handleOrderResponse, this.handleOrderError))
)
)
);
handleOrderResponse = (res: ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString) => {
this.onOrderSuccess(res.result.item1[0], res.result.item2);
};
onOrderSuccess = (displayOrder: DisplayOrderDTO, action: KeyValueDTOOfStringAndString[]) => {};
handleOrderError = (err: any) => {
this._modal.error('Fehler beim Bestellen', err);
this.patchState({ ordering: false });
};
}

View File

@@ -0,0 +1,12 @@
<ui-searchbox
class="max-w-lg mx-auto my-6"
placeholder="EAN / ISBN"
[formControl]="queryControl"
[scanner]="true"
[focusAfterViewInit]="true"
[loading]="fetching$ | async"
(search)="search($event)"
(scan)="search($event)"
[hint]="hint$ | async"
>
</ui-searchbox>

View File

@@ -0,0 +1,59 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
import { UiSearchboxNextModule } from '@ui/searchbox';
import { KulturpassOrderSearchboxStore } from './kulturpass-order-searchbox.store';
import { provideComponentStore } from '@ngrx/component-store';
import { Subject } from 'rxjs';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { takeUntil } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
import { AddToShoppingCartDTO } from '@swagger/checkout';
@Component({
selector: 'shared-kulturpass-order-searchbox',
templateUrl: 'kulturpass-order-searchbox.component.html',
styleUrls: ['kulturpass-order-searchbox.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'shared-kulturpass-order-searchbox' },
standalone: true,
imports: [UiSearchboxNextModule, ReactiveFormsModule, CommonModule],
providers: [provideComponentStore(KulturpassOrderSearchboxStore)],
})
export class KulturpassOrderSearchboxComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject<void>();
queryControl = new FormControl(this._store.query);
fetching$ = this._store.fetching$;
@Output()
addToShoppingCart = new EventEmitter<AddToShoppingCartDTO>();
hint$ = this._store.hint$;
constructor(private _store: KulturpassOrderSearchboxStore, private _cdr: ChangeDetectorRef) {}
ngOnInit(): void {
this._store.query$.pipe(takeUntil(this._onDestroy$)).subscribe((query) => {
if (this.queryControl.value === query) {
return;
}
this.queryControl.setValue(query);
this._cdr.markForCheck();
});
this._store.addToShoppingCart$.pipe(takeUntil(this._onDestroy$)).subscribe((addToShoppingCart) => {
this.addToShoppingCart.emit(addToShoppingCart);
});
}
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
search(query: string) {
this._store.updateQuery(query);
this._store.search();
}
}

View File

@@ -0,0 +1,148 @@
import { Injectable } from '@angular/core';
import { ComponentStore, OnStoreInit, tapResponse } from '@ngrx/component-store';
import { ItemDTO, ResponseArgsOfItemDTO } from '@swagger/cat';
import { switchMap, withLatestFrom } from 'rxjs/operators';
import { Observable, Subject } from 'rxjs';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCatalogService } from '@domain/catalog';
import { AddToShoppingCartDTO, AvailabilityDTO, BranchDTO } from '@swagger/checkout';
import { tap } from 'rxjs/operators';
import { UiModalService } from '@ui/modal';
export interface KulturpassOrderSearchboxState {
query: string;
fetching: boolean;
branch?: BranchDTO;
}
@Injectable()
export class KulturpassOrderSearchboxStore extends ComponentStore<KulturpassOrderSearchboxState> implements OnStoreInit {
get query() {
return this.get((s) => s.query);
}
get fetching() {
return this.get((s) => s.fetching);
}
get branch() {
return this.get((s) => s.branch);
}
addToShoppingCart$ = new Subject<AddToShoppingCartDTO>();
hint$ = new Subject<string>();
constructor(
private _catalogService: DomainCatalogService,
private _availabilityService: DomainAvailabilityService,
private _modal: UiModalService
) {
super({ query: '', fetching: false });
}
readonly query$ = this.select((state) => state.query);
readonly updateQuery = this.updater((state, query: string) => ({ ...state, query }));
readonly fetching$ = this.select((state) => state.fetching);
readonly updateFetching = this.updater((state, fetching: boolean) => ({ ...state, fetching }));
readonly branch$ = this.select((state) => state.branch);
readonly updateBranch = this.updater((state, branch: BranchDTO) => ({ ...state, branch }));
ngrxOnStoreInit = () => {
this.loadBranch();
};
loadBranch = this.effect(($) =>
$.pipe(
switchMap(() => this._availabilityService.getDefaultBranch().pipe(tapResponse(this.handleBranchResponse, this.handleBranchError)))
)
);
handleBranchResponse = (res: BranchDTO) => {
this.patchState({ branch: res });
};
handleBranchError = (err) => {
this._modal.error('Fehler beim Laden der Filiale', err);
};
readonly search = this.effect(($) =>
$.pipe(
tap(() => this.patchState({ fetching: true })),
withLatestFrom(this.query$),
switchMap(([_, query]) =>
this._catalogService.getDetailsByEan({ ean: query }).pipe(tapResponse(this.handleSearchResponse, this.handleSearchError))
)
)
);
handleSearchResponse = (res: ResponseArgsOfItemDTO) => {
if (!res.result) {
this.hint$.next('Artikel nicht gefunden');
this.patchState({ query: '' });
return;
}
this.fetchAvailability(res.result);
};
handleSearchError = (err) => {
this.hint$.next('Artikel nicht gefunden');
this.patchState({ query: '', fetching: false });
};
fetchAvailability = this.effect((item$: Observable<ItemDTO>) =>
item$.pipe(
switchMap((item) =>
this._availabilityService
.getTakeAwayAvailability({
item: {
ean: item.product.ean,
itemId: item.id,
price: item.catalogAvailability.price,
},
quantity: 1,
})
.pipe(tapResponse(this.handleAvailabilityResponse(item), this.handleAvailabilityError))
)
)
);
handleAvailabilityResponse = (item: ItemDTO) => (availability: AvailabilityDTO) => {
if (!this._availabilityService.isAvailable({ availability }) || availability.inStock < 1) {
this.hint$.next('Artikel nicht vorrätig');
this.patchState({ query: '', fetching: false });
return;
}
const addToShoppingCartDTO: AddToShoppingCartDTO = {
quantity: 1,
availability: availability,
destination: {
data: {
target: 1,
targetBranch: { id: this.branch.id },
},
},
promotion: {
points: 0,
},
itemType: item.type,
product: { catalogProductNumber: String(item.id), ...item.product },
};
this.addToShoppingCart$.next(addToShoppingCartDTO);
this.patchState({ query: '', fetching: false });
};
handleAvailabilityError = (err) => {
this._modal.error('Fehler beim Laden der Verfügbarkeit', err);
this.patchState({ query: '', fetching: false });
};
}

View File

@@ -0,0 +1,4 @@
export * from './lib/kulturpass-order-modal.component';
export * from './lib/kulturpass-order-modal.module';
export * from './lib/kulturpass-order-modal.data';
export * from './lib/kulturpass-order-modal.service';

View File

@@ -1,7 +1,7 @@
import { Pipe, PipeTransform } from '@angular/core';
import { BranchDTO } from '@swagger/checkout';
export type BranchNameFormat = 'key' | 'name' | 'key-name';
export type BranchNameFormat = 'key' | 'name' | 'key-name' | 'name-address';
@Pipe({
name: 'branchName',
@@ -24,6 +24,10 @@ export class BranchNamePipe implements PipeTransform {
return value.key;
}
if (format === 'name-address') {
return `${value.name} | ${value.address?.street} ${value.address?.streetNumber}, ${value.address?.zipCode} ${value.address?.city}`;
}
return '';
}
}

View File

@@ -58,7 +58,8 @@ export { ResponseArgsOfIEnumerableOfLogisticianDTO } from './models/response-arg
export { LogisticianDTO } from './models/logistician-dto';
export { EntityDTOBaseOfLogisticianDTOAndILogistician } from './models/entity-dtobase-of-logistician-dtoand-ilogistician';
export { ResponseArgsOfLogisticianDTO } from './models/response-args-of-logistician-dto';
export { ResponseArgsOfIEnumerableOfDisplayOrderDTO } from './models/response-args-of-ienumerable-of-display-order-dto';
export { ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString } from './models/response-args-of-value-tuple-of-ienumerable-of-display-order-dtoand-ienumerable-of-key-value-dtoof-string-and-string';
export { ValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString } from './models/value-tuple-of-ienumerable-of-display-order-dtoand-ienumerable-of-key-value-dtoof-string-and-string';
export { DisplayOrderDTO } from './models/display-order-dto';
export { DisplayBranchDTO } from './models/display-branch-dto';
export { CommunicationDetailsDTO } from './models/communication-details-dto';
@@ -81,7 +82,10 @@ export { TermsOfDeliveryDTO } from './models/terms-of-delivery-dto';
export { TypeOfDelivery } from './models/type-of-delivery';
export { ShippingType } from './models/shipping-type';
export { NotificationChannel } from './models/notification-channel';
export { LinkedRecordDTO } from './models/linked-record-dto';
export { EntityDTOBaseOfDisplayOrderDTOAndIOrder } from './models/entity-dtobase-of-display-order-dtoand-iorder';
export { KulturPassOrderValues } from './models/kultur-pass-order-values';
export { ResponseArgsOfIEnumerableOfDisplayOrderDTO } from './models/response-args-of-ienumerable-of-display-order-dto';
export { ResponseArgsOfValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO } from './models/response-args-of-value-tuple-of-order-item-subset-dtoand-order-item-subset-dto';
export { ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO } from './models/value-tuple-of-order-item-subset-dtoand-order-item-subset-dto';
export { OrderItemSubsetDTO } from './models/order-item-subset-dto';
@@ -109,7 +113,6 @@ export { PaymentDTO } from './models/payment-dto';
export { EntityDTOBaseOfPaymentDTOAndIReadOnlyPayment } from './models/entity-dtobase-of-payment-dtoand-iread-only-payment';
export { ValidationStatus } from './models/validation-status';
export { PackagingInstructions } from './models/packaging-instructions';
export { LinkedRecordDTO } from './models/linked-record-dto';
export { EntityDTOBaseOfOrderDTOAndIOrder } from './models/entity-dtobase-of-order-dtoand-iorder';
export { OrderItemType } from './models/order-item-type';
export { EntityDTOContainerOfOrderItemSubsetDTO } from './models/entity-dtocontainer-of-order-item-subset-dto';

View File

@@ -1,9 +1,11 @@
/* tslint:disable */
import { EntityDTOBaseOfDisplayOrderDTOAndIOrder } from './entity-dtobase-of-display-order-dtoand-iorder';
import { KeyValueDTOOfStringAndString } from './key-value-dtoof-string-and-string';
import { DisplayAddresseeDTO } from './display-addressee-dto';
import { BuyerType } from './buyer-type';
import { EnvironmentChannel } from './environment-channel';
import { DisplayOrderItemDTO } from './display-order-item-dto';
import { LinkedRecordDTO } from './linked-record-dto';
import { DisplayLogisticianDTO } from './display-logistician-dto';
import { NotificationChannel } from './notification-channel';
import { DisplayBranchDTO } from './display-branch-dto';
@@ -11,6 +13,7 @@ import { OrderType } from './order-type';
import { DisplayOrderPaymentDTO } from './display-order-payment-dto';
import { TermsOfDeliveryDTO } from './terms-of-delivery-dto';
export interface DisplayOrderDTO extends EntityDTOBaseOfDisplayOrderDTOAndIOrder{
actions?: Array<KeyValueDTOOfStringAndString>;
buyer?: DisplayAddresseeDTO;
buyerComment?: string;
buyerIsGuestAccount?: boolean;
@@ -21,6 +24,7 @@ export interface DisplayOrderDTO extends EntityDTOBaseOfDisplayOrderDTOAndIOrder
features?: {[key: string]: string};
items?: Array<DisplayOrderItemDTO>;
itemsCount?: number;
linkedRecords?: Array<LinkedRecordDTO>;
logistician?: DisplayLogisticianDTO;
notificationChannels?: NotificationChannel;
orderBranch?: DisplayBranchDTO;

View File

@@ -0,0 +1,13 @@
/* tslint:disable */
export interface KulturPassOrderValues {
/**
* Checkout PK
*/
checkoutId: number;
/**
* PK des KulturPass-Einlösecode-Postens
*/
orderItemSubsetId?: string;
}

View File

@@ -0,0 +1,6 @@
/* tslint:disable */
import { ResponseArgs } from './response-args';
import { ValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString } from './value-tuple-of-ienumerable-of-display-order-dtoand-ienumerable-of-key-value-dtoof-string-and-string';
export interface ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString extends ResponseArgs{
result: ValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString;
}

View File

@@ -0,0 +1,7 @@
/* tslint:disable */
import { DisplayOrderDTO } from './display-order-dto';
import { KeyValueDTOOfStringAndString } from './key-value-dtoof-string-and-string';
export interface ValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString {
item1: Array<DisplayOrderDTO>;
item2: Array<KeyValueDTOOfStringAndString>;
}

View File

@@ -7,6 +7,8 @@ import { StrictHttpResponse as __StrictHttpResponse } from '../strict-http-respo
import { Observable as __Observable } from 'rxjs';
import { map as __map, filter as __filter } from 'rxjs/operators';
import { ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString } from '../models/response-args-of-value-tuple-of-ienumerable-of-display-order-dtoand-ienumerable-of-key-value-dtoof-string-and-string';
import { KulturPassOrderValues } from '../models/kultur-pass-order-values';
import { ResponseArgsOfIEnumerableOfDisplayOrderDTO } from '../models/response-args-of-ienumerable-of-display-order-dto';
import { ResponseArgsOfValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO } from '../models/response-args-of-value-tuple-of-order-item-subset-dtoand-order-item-subset-dto';
import { ReorderValues } from '../models/reorder-values';
@@ -16,6 +18,7 @@ import { ResponseArgsOfSupplierOrderResult } from '../models/response-args-of-su
providedIn: 'root',
})
class OrderCheckoutService extends __BaseService {
static readonly OrderCheckoutCreateKulturPassOrderPath = '/order/kulturpass';
static readonly OrderCheckoutCreateOrderPOSTPath = '/order/checkout/{checkoutId}';
static readonly OrderCheckoutCreateOrderPUTPath = '/order/checkout/{checkoutId}';
static readonly OrderCheckoutReorderPath = '/order/{orderId}/orderitem/{orderItemId}/orderitemsubset/{orderItemSubsetId}/reorder';
@@ -30,6 +33,55 @@ class OrderCheckoutService extends __BaseService {
super(config, http);
}
/**
* KulturPass-Filialentnahme
* @param params The `OrderCheckoutService.OrderCheckoutCreateKulturPassOrderParams` containing the following parameters:
*
* - `payload`:
*
* - `locale`:
*
* @return or
*/
OrderCheckoutCreateKulturPassOrderResponse(params: OrderCheckoutService.OrderCheckoutCreateKulturPassOrderParams): __Observable<__StrictHttpResponse<ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString | ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
__body = params.payload;
if (params.locale != null) __params = __params.set('locale', params.locale.toString());
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/order/kulturpass`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString | ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString>;
})
);
}
/**
* KulturPass-Filialentnahme
* @param params The `OrderCheckoutService.OrderCheckoutCreateKulturPassOrderParams` containing the following parameters:
*
* - `payload`:
*
* - `locale`:
*
* @return or
*/
OrderCheckoutCreateKulturPassOrder(params: OrderCheckoutService.OrderCheckoutCreateKulturPassOrderParams): __Observable<ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString | ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString> {
return this.OrderCheckoutCreateKulturPassOrderResponse(params).pipe(
__map(_r => _r.body as ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString | ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString)
);
}
/**
* @param params The `OrderCheckoutService.OrderCheckoutCreateOrderPOSTParams` containing the following parameters:
*
@@ -312,6 +364,14 @@ class OrderCheckoutService extends __BaseService {
module OrderCheckoutService {
/**
* Parameters for OrderCheckoutCreateKulturPassOrder
*/
export interface OrderCheckoutCreateKulturPassOrderParams {
payload: KulturPassOrderValues;
locale?: null | string;
}
/**
* Parameters for OrderCheckoutCreateOrderPOST
*/

View File

@@ -7,6 +7,8 @@ import {
forwardRef,
ChangeDetectorRef,
HostListener,
Output,
EventEmitter,
} from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
@@ -25,6 +27,9 @@ export class QuantityDropdownComponent implements ControlValueAccessor {
@Input()
quantity: number;
@Output()
quantityChange = new EventEmitter<number>();
@Input()
suffix: string = 'x';
@@ -75,6 +80,7 @@ export class QuantityDropdownComponent implements ControlValueAccessor {
this.quantity = quantity;
this.onChange(quantity);
this.quantityChange.emit(quantity);
this.showDropdown = false;
this.customInput = false;
}

View File

@@ -166,7 +166,7 @@ export class UiSearchboxNextComponent extends UiFormControlDirective<any>
startScan() {
if (!!this.scanAdapterService?.isReady()) {
this.subscriptions.add(
this.scanAdapterService.scan().subscribe((result) => {
this.scanAdapterService.scan({ include: ['Dev'] }).subscribe((result) => {
this.scan.emit(result);
this.setQuery(result);
this.autocomplete?.close();