mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merge branch 'develop' into release/1.5
This commit is contained in:
40
angular.json
40
angular.json
@@ -3256,6 +3256,46 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@shared/notification-channel-control": {
|
||||
"projectType": "library",
|
||||
"root": "apps/shared/notification-channel-control",
|
||||
"sourceRoot": "apps/shared/notification-channel-control/src",
|
||||
"prefix": "shared",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||
"options": {
|
||||
"tsConfig": "apps/shared/notification-channel-control/tsconfig.lib.json",
|
||||
"project": "apps/shared/notification-channel-control/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "apps/shared/notification-channel-control/tsconfig.lib.prod.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "apps/shared/notification-channel-control/src/test.ts",
|
||||
"tsConfig": "apps/shared/notification-channel-control/tsconfig.spec.json",
|
||||
"karmaConfig": "apps/shared/notification-channel-control/karma.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"apps/shared/notification-channel-control/tsconfig.lib.json",
|
||||
"apps/shared/notification-channel-control/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "sales"
|
||||
|
||||
@@ -20,6 +20,14 @@ export class DomainAvailabilityService {
|
||||
private _stock: StockService
|
||||
) {}
|
||||
|
||||
@memorize()
|
||||
getSuppliers(): Observable<SupplierDTO[]> {
|
||||
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
|
||||
map((response) => response.result),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getTakeAwaySupplier(): Observable<SupplierDTO> {
|
||||
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
|
||||
@@ -72,8 +80,8 @@ export class DomainAvailabilityService {
|
||||
availableQuantity: stockInfo.availableQuantity,
|
||||
availabilityType: quantity <= stockInfo.inStock ? 1024 : 1, // 1024 (=Available)
|
||||
inStock: stockInfo.inStock,
|
||||
ssc: quantity <= stockInfo.inStock ? '999' : '',
|
||||
sscText: quantity <= stockInfo.inStock ? 'Filialentnahme' : '',
|
||||
supplierSSC: quantity <= stockInfo.inStock ? '999' : '',
|
||||
supplierSSCText: quantity <= stockInfo.inStock ? 'Filialentnahme' : '',
|
||||
price,
|
||||
supplier: { id: supplier?.id },
|
||||
branchId: stockInfo.branchId,
|
||||
@@ -363,8 +371,8 @@ export class DomainAvailabilityService {
|
||||
const availability: AvailabilityDTO = {
|
||||
availabilityType: quantity <= inStock ? 1024 : 1, // 1024 (=Available)
|
||||
inStock: inStock,
|
||||
ssc: quantity <= inStock ? '999' : '',
|
||||
sscText: quantity <= inStock ? 'Filialentnahme' : '',
|
||||
supplierSSC: quantity <= inStock ? '999' : '',
|
||||
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
|
||||
price,
|
||||
supplier: { id: supplier?.id },
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActionHandler } from '@core/command';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { ActionHandler, CommandService } from '@core/command';
|
||||
import { ReorderModalComponent, ReorderResult } from '@modal/reorder';
|
||||
import { AvailabilityDTO2, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { isResponseArgs } from '@utils/object';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { DomainOmsService } from '../oms.service';
|
||||
@@ -9,23 +11,35 @@ import { OrderItemsContext } from './order-items.context';
|
||||
|
||||
@Injectable()
|
||||
export class OrderAtSupplierActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
constructor(private _domainOmsService: DomainOmsService) {
|
||||
constructor(private _command: CommandService, private _domainOmsService: DomainOmsService, private _uiModal: UiModalService) {
|
||||
super('ORDER_AT_SUPPLIER');
|
||||
}
|
||||
|
||||
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
|
||||
const updatedItems: OrderItemListItemDTO[] = [];
|
||||
for (const item of data?.items) {
|
||||
for (const orderItem of data.items) {
|
||||
const result = await this._uiModal
|
||||
.open<ReorderResult, OrderItemListItemDTO>({
|
||||
content: ReorderModalComponent,
|
||||
title: 'Artikel bestellen',
|
||||
data: orderItem,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
|
||||
try {
|
||||
const res = await this._domainOmsService
|
||||
.orderAtSupplier({
|
||||
orderId: item.orderId,
|
||||
orderItemId: item.orderItemId,
|
||||
orderItemSubsetId: item.orderItemSubsetId,
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
updatedItems.push({ ...item, processingStatus: 16 });
|
||||
if (result.data) {
|
||||
if (result.data.action === 'REORDER') {
|
||||
await this.patchOrderItemSubset(result.data.item, result.data.availability);
|
||||
await this.orderAtSupplier(result.data.item);
|
||||
updatedItems.push({ ...orderItem, processingStatus: 16 });
|
||||
} else if (result.data.action === 'NOTAVAILABLE') {
|
||||
let context = { ...data, items: [orderItem] };
|
||||
context = await this._command.handleCommand('NOTAVAILABLE', context);
|
||||
updatedItems.push(...context.items);
|
||||
}
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
|
||||
console.error('InvalidProperties: ', err.error.invalidProperties);
|
||||
@@ -37,6 +51,38 @@ export class OrderAtSupplierActionHandler extends ActionHandler<OrderItemsContex
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return { ...data, items: updatedItems };
|
||||
}
|
||||
|
||||
async patchOrderItemSubset(orderItem: OrderItemListItemDTO, availability: AvailabilityDTO2) {
|
||||
return await this._domainOmsService
|
||||
.patchOrderItemSubset({
|
||||
orderId: orderItem.orderId,
|
||||
orderItemId: orderItem.orderItemId,
|
||||
orderItemSubsetId: orderItem.orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
ssc: availability.ssc,
|
||||
sscText: availability.sscText,
|
||||
supplier: {
|
||||
id: availability.supplierId,
|
||||
},
|
||||
isPrebooked: availability.isPrebooked,
|
||||
estimatedShippingDate: availability.at,
|
||||
},
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
async orderAtSupplier(orderItem: OrderItemListItemDTO) {
|
||||
return await this._domainOmsService
|
||||
.orderAtSupplier({
|
||||
orderId: orderItem.orderId,
|
||||
orderItemId: orderItem.orderItemId,
|
||||
orderItemSubsetId: orderItem.orderItemSubsetId,
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ChangeStockStatusCodeValues,
|
||||
HistoryDTO,
|
||||
NotificationChannel,
|
||||
OrderCheckoutService,
|
||||
OrderDTO,
|
||||
OrderItemDTO,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
} from '@swagger/oms';
|
||||
import { memorize } from '@utils/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { map, mergeMap, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class DomainOmsService {
|
||||
@@ -162,4 +163,60 @@ export class DomainOmsService {
|
||||
orderItemSubsetId,
|
||||
});
|
||||
}
|
||||
|
||||
getNotifications(orderId: number): Observable<{ selected: NotificationChannel; email: string; mobile: string }> {
|
||||
return this.getOrder(orderId).pipe(
|
||||
map((order) => ({
|
||||
selected: order.notificationChannels,
|
||||
email: order.buyer?.communicationDetails?.email,
|
||||
mobile: order.buyer?.communicationDetails?.mobile,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
updateNotifications(orderId: number, changes: { selected: NotificationChannel; email: string; mobile: string }) {
|
||||
const communicationDetails = {
|
||||
email: changes.email,
|
||||
mobile: changes.mobile,
|
||||
};
|
||||
|
||||
if (!(changes.selected & 1)) {
|
||||
delete communicationDetails.email;
|
||||
}
|
||||
if (!(changes.selected & 2)) {
|
||||
delete communicationDetails.mobile;
|
||||
}
|
||||
|
||||
return this.orderService
|
||||
.OrderPatchOrder({
|
||||
orderId: orderId,
|
||||
order: {
|
||||
notificationChannels: changes.selected,
|
||||
buyer: { communicationDetails },
|
||||
},
|
||||
})
|
||||
.pipe(map((res) => res.result));
|
||||
}
|
||||
|
||||
getCompletedTasks({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
}: {
|
||||
orderId: number;
|
||||
orderItemId: number;
|
||||
orderItemSubsetId: number;
|
||||
}): Observable<Record<string, Date>> {
|
||||
return this.orderService
|
||||
.OrderGetOrderItemSubsetTasks({ orderId, orderItemId, orderItemSubsetId, completed: new Date(0).toISOString() })
|
||||
.pipe(
|
||||
map((res) =>
|
||||
res.result.reduce((data, result) => {
|
||||
data[result.name] = new Date(result.completed);
|
||||
|
||||
return data;
|
||||
}, {} as Record<string, Date>)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ export class DomainTaskCalendarService {
|
||||
* 4 = Completed
|
||||
* 8 = Overdue
|
||||
* 16 = Archived
|
||||
* 32 = Removed
|
||||
* 64 = Uncompleted
|
||||
*/
|
||||
const processingStatusList: ProcessingStatusList = [];
|
||||
|
||||
@@ -69,6 +69,7 @@ export class ReturnItemDtoToRemissionProductMapping implements Mapper<ReturnItem
|
||||
catalogProductNumber: source.product.catalogProductNumber,
|
||||
features: this.getFeatures(source.assortment), // assortment (semicolon separiert ohne 'SO') z.B. "Wirtschaft|B"
|
||||
isResidual: source.descendantOf && source.descendantOf.enabled ? true : false || !!source.impediment,
|
||||
impediment: source.impediment,
|
||||
} as RemissionProduct;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export class ReturnSuggestionDtoToRemissionProductMapping implements Mapper<Retu
|
||||
!!source.impediment ||
|
||||
// tslint:disable-next-line: no-string-literal
|
||||
(source['descendantOf'] && !!source['descendantOf'].enabled),
|
||||
impediment: source.impediment?.comment !== 'Restmenge' ? source.impediment : undefined,
|
||||
} as RemissionProduct;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ImpedimentDTO } from '@swagger/remi';
|
||||
import { RemissionPlacementType } from '../types/remission-placement-types';
|
||||
import { RemissionSupplier } from './remission-supplier';
|
||||
|
||||
@@ -117,4 +118,6 @@ export interface RemissionProduct {
|
||||
*
|
||||
*/
|
||||
department?: string;
|
||||
|
||||
impediment?: ImpedimentDTO;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
<button class="close-btn" (click)="close()">
|
||||
<ui-icon icon="close" size="21px"></ui-icon>
|
||||
</button>
|
||||
|
||||
<h1>Sie haben neue Nachrichten</h1>
|
||||
|
||||
<ng-container *ngFor="let notification of groupedNotifications$ | async">
|
||||
|
||||
@@ -5,10 +5,6 @@ modal-notifications {
|
||||
@apply text-xl font-bold text-center mb-10;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
@apply absolute right-0 top-0 bg-transparent border-none text-ucla-blue;
|
||||
}
|
||||
|
||||
.notification-card {
|
||||
@apply text-center text-xl text-inactive-branch block bg-white rounded-t-card font-bold no-underline py-4 border-none outline-none shadow-card -ml-4;
|
||||
width: calc(100% + 2rem);
|
||||
@@ -47,7 +43,7 @@ modal-notifications {
|
||||
|
||||
.notification-list {
|
||||
@apply overflow-y-scroll -ml-4;
|
||||
max-height: calc(100vh - 400px);
|
||||
max-height: calc(100vh - 450px);
|
||||
width: calc(100% + 2rem);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<span></span>
|
||||
<span class="number">Bestand</span>
|
||||
<span>MS</span>
|
||||
<span title="Vormerker">VM</span>
|
||||
<span>vsl. Lieferdatum</span>
|
||||
<span class="number">Preis</span>
|
||||
<span></span>
|
||||
@@ -39,6 +40,9 @@
|
||||
<span class="first-cell">{{ availability.supplier | supplierName }}</span>
|
||||
<span class="number">{{ availability.qty || 0 }}</span>
|
||||
<span>{{ availability.ssc }}</span>
|
||||
<span>
|
||||
<ui-checkbox *ngIf="availability.supplier !== 'F'" [(ngModel)]="availability.isPrebooked"> </ui-checkbox>
|
||||
</span>
|
||||
<span>{{ availability.at | date: 'dd.MM.yy' }}</span>
|
||||
<span class="number">{{ availability.price?.value?.value | currency: 'EUR':'code' }}</span>
|
||||
<span>
|
||||
|
||||
@@ -46,7 +46,7 @@ hr {
|
||||
|
||||
.supplier-grid {
|
||||
@apply grid font-bold;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
|
||||
span:not(.first-cell) {
|
||||
@apply text-center;
|
||||
@@ -62,7 +62,8 @@ hr {
|
||||
border-bottom: 2px solid #e0ebf5;
|
||||
}
|
||||
|
||||
ui-select-bullet {
|
||||
ui-select-bullet,
|
||||
ui-checkbox {
|
||||
@apply flex justify-center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ProductImageModule } from '@cdn/product-image';
|
||||
import { UiCheckboxModule } from '@ui/checkbox';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
@@ -19,6 +20,7 @@ import { SupplierNamePipe } from './supplier-name.pipe';
|
||||
ReactiveFormsModule,
|
||||
UiSpinnerModule,
|
||||
UiQuantityDropdownModule,
|
||||
UiCheckboxModule,
|
||||
],
|
||||
exports: [ReorderModalComponent, SupplierNamePipe],
|
||||
declarations: [ReorderModalComponent, SupplierNamePipe],
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<div class="price">
|
||||
{{ item.catalogAvailability?.price?.value?.value | currency: item.catalogAvailability?.price?.value?.currency:'code' }}
|
||||
</div>
|
||||
<div>{{ store.promotionPoints$ | async }} Lesepunkte</div>
|
||||
<div *ngIf="store.promotionPoints$ | async; let promotionPoints">{{ promotionPoints }} Lesepunkte</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
}
|
||||
|
||||
.product-description {
|
||||
@apply flex flex-col flex-grow justify-center px-5 py-5;
|
||||
@apply flex flex-col flex-grow px-5 py-5;
|
||||
|
||||
.info {
|
||||
@apply whitespace-pre-line;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { UiFilterAutocompleteProvider, UiFilterScanProvider } from '@ui/filter';
|
||||
import { UiFilter } from 'apps/ui/filter/src/lib/next';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { filter, first, map } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from './article-search.store';
|
||||
import { FocusSearchboxEvent } from './focus-searchbox.event';
|
||||
import { ArticleSearchMainAutocompleteProvider, ArticleSearchMainScanProviderService } from './providers';
|
||||
@@ -61,6 +61,7 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.removeBreadcrumbs();
|
||||
this.subscriptions.add(
|
||||
this.application.activatedProcessId$.subscribe((processId) => {
|
||||
this.setBreadcrumb(processId);
|
||||
@@ -88,6 +89,16 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async removeBreadcrumbs() {
|
||||
const checkoutCrumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.application.activatedProcessId, ['checkout'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
checkoutCrumbs.forEach(async (crumb) => {
|
||||
await this.breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
}
|
||||
|
||||
async setBreadcrumb(processId: number) {
|
||||
await this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
|
||||
@@ -74,7 +74,7 @@ ui-searchbox-autocomplete {
|
||||
}
|
||||
|
||||
.info-tooltip-button {
|
||||
@apply border-font-customer bg-white rounded-md text-base font-bold;
|
||||
@apply border-font-customer bg-white rounded-md text-base font-bold text-black;
|
||||
border-style: outset;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
|
||||
@@ -55,10 +55,10 @@ ui-filter-input-group-main {
|
||||
@apply list-none pb-px-15;
|
||||
|
||||
button {
|
||||
@apply flex flex-row items-center outline-none border-none bg-white text-base m-0 p-0;
|
||||
@apply flex flex-row items-center outline-none border-none bg-white text-black text-base m-0 p-0;
|
||||
|
||||
ui-icon {
|
||||
@apply flex w-px-35 h-px-35 justify-center items-center mr-3 rounded-full;
|
||||
@apply flex w-px-35 h-px-35 justify-center items-center mr-3 rounded-full text-black;
|
||||
background-color: #e6eff9;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { CheckoutDummyScanProvider } from './checkout-dummy-scan.provider';
|
||||
|
||||
describe('GoodsInSearchMainScanProvider', () => {
|
||||
let spectator: SpectatorService<CheckoutDummyScanProvider>;
|
||||
const naticeContainerMock = jasmine.createSpyObj<NativeContainerService>(['openScanner', 'isUiWebview']);
|
||||
|
||||
const createProvider = createServiceFactory({
|
||||
service: CheckoutDummyScanProvider,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createProvider();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(spectator.service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UiFilterScanProvider } from '@ui/filter';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { Observable } from 'rxjs';
|
||||
import { catchError, filter, map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class CheckoutDummyScanProvider extends UiFilterScanProvider {
|
||||
for = 'dummy';
|
||||
|
||||
constructor(private nativeContainer: NativeContainerService) {
|
||||
super();
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
return this.nativeContainer.openScanner('scanBook').pipe(
|
||||
filter((result) => result.status !== 'IN_PROGRESS'),
|
||||
map((result) => result?.data),
|
||||
catchError((err) => {
|
||||
return '';
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<div class="wrapper">
|
||||
<div class="header">
|
||||
<div class="headline">
|
||||
<h1 class="title">
|
||||
Neuanlage
|
||||
</h1>
|
||||
<p class="paragraph">
|
||||
Hier können Sie Artikel manuell<br />
|
||||
oder per Dummy anlegen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<form *ngIf="control" [formGroup]="control" (submit)="submit()">
|
||||
<ui-form-control class="searchbox-control" label="EAN/ISBN">
|
||||
<ui-searchbox
|
||||
formControlName="ean"
|
||||
[query]="query$ | async"
|
||||
(queryChange)="setQuery($event)"
|
||||
(search)="search($event)"
|
||||
(scan)="search($event)"
|
||||
[loading]="loading$ | async"
|
||||
[scanProvider]="scanProvider"
|
||||
[hint]="message$ | async"
|
||||
tabindex="0"
|
||||
>
|
||||
</ui-searchbox>
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Titel" requiredMark="*">
|
||||
<input tabindex="0" uiInput formControlName="name" />
|
||||
</ui-form-control>
|
||||
<div class="control-row">
|
||||
<ui-form-control label="Menge" requiredMark="*">
|
||||
<input tabindex="0" uiInput formControlName="quantity" />
|
||||
</ui-form-control>
|
||||
<ui-form-control class="datepicker" label="vsl. Lieferdatum" requiredMark="*">
|
||||
<button
|
||||
tabindex="-1"
|
||||
class="date-btn"
|
||||
type="button"
|
||||
[class.content-selected]="!!(estimatedShippingDate$ | async)"
|
||||
[uiOverlayTrigger]="uiDatepicker"
|
||||
#datepicker="uiOverlayTrigger"
|
||||
>
|
||||
<strong>
|
||||
{{ estimatedShippingDate$ | async | date: 'dd.MM.yy' }}
|
||||
</strong>
|
||||
<ui-icon icon="arrow_head" class="dp-button-icon" size="20px" [rotate]="datepicker.opened ? '270deg' : '90deg'"> </ui-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
formControlName="estimatedShippingDate"
|
||||
#uiDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[min]="minDate"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="estimatedShippingDate$ | async"
|
||||
(save)="changeEstimatedShippingDate($event); uiDatepicker.close()"
|
||||
>
|
||||
<ng-container #content>Übernehmen</ng-container>
|
||||
</ui-datepicker>
|
||||
</ui-form-control>
|
||||
</div>
|
||||
<ui-form-control label="Autor">
|
||||
<input tabindex="0" uiInput formControlName="contributors" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Verlag">
|
||||
<input tabindex="0" uiInput formControlName="manufacturer" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Lieferant" requiredMark="*">
|
||||
<ui-select tabindex="-1" formControlName="supplier">
|
||||
<ui-select-option *ngFor="let supplier of suppliers$ | async" [label]="supplier.name" [value]="supplier.id"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<div class="control-row">
|
||||
<ui-form-control class="price" label="Stückpreis" [suffix]="price.value ? '€' : ''" requiredMark="*">
|
||||
<input tabindex="0" #price uiInput formControlName="price" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="MwSt" requiredMark="*">
|
||||
<ui-select tabindex="-1" formControlName="vat">
|
||||
<ui-select-option *ngFor="let vat of vats$ | async" [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button
|
||||
class="cta-secondary"
|
||||
(click)="nextItem()"
|
||||
[disabled]="control.invalid || control.disabled || (loading$ | async)"
|
||||
type="button"
|
||||
>
|
||||
Weitere Artikel hinzufügen
|
||||
</button>
|
||||
<button class="cta-primary" [disabled]="control.invalid || control.disabled || (loading$ | async)" type="submit">
|
||||
Bestellung anlegen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,110 @@
|
||||
:host {
|
||||
@apply block overflow-y-scroll;
|
||||
max-height: calc(100vh - 135px - 80px);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
@apply block bg-white shadow-card rounded-card pt-6 pb-10 mb-40;
|
||||
|
||||
.header {
|
||||
@apply text-right;
|
||||
|
||||
.headline {
|
||||
@apply text-center;
|
||||
|
||||
.title {
|
||||
@apply text-2xl text-page-heading font-bold;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
@apply text-2xl mt-1 mb-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
@apply px-20;
|
||||
|
||||
.control-row {
|
||||
@apply flex flex-row gap-8;
|
||||
|
||||
ui-form-control {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
ui-searchbox {
|
||||
@apply mt-4 shadow-none border-0 border-solid rounded-none;
|
||||
border-bottom-width: 2px;
|
||||
border-color: #e1ebf5;
|
||||
}
|
||||
|
||||
.datepicker {
|
||||
@apply border-0 border-solid justify-end;
|
||||
border-bottom-width: 2px;
|
||||
border-color: #e1ebf5;
|
||||
|
||||
.date-btn {
|
||||
@apply flex flex-row-reverse items-center p-0 mr-1 w-full text-right outline-none border-none bg-transparent text-lg;
|
||||
}
|
||||
|
||||
.content-selected {
|
||||
@apply flex-row justify-end;
|
||||
}
|
||||
|
||||
.dp-button-icon {
|
||||
@apply inline-flex ml-2 text-inactive-customer;
|
||||
transition: transform 200ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply absolute flex items-center justify-center left-0 right-0 bottom-0;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.cta-primary {
|
||||
@apply bg-brand text-white font-bold text-lg outline-none border-brand border-solid border-2 rounded-full px-6 py-3;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-customer border-inactive-customer;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-secondary {
|
||||
@apply bg-white text-brand border-brand font-bold text-lg outline-none border-solid border-2 rounded-full px-6 py-3 mr-4;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-inactive-customer border-inactive-customer text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-checkout-dummy ui-select ui-icon {
|
||||
@apply text-inactive-customer;
|
||||
}
|
||||
|
||||
::ng-deep page-checkout-dummy ui-select .ui-icon {
|
||||
@apply w-px-20 h-px-20;
|
||||
}
|
||||
|
||||
::ng-deep page-checkout-dummy .datepicker .input-wrapper {
|
||||
flex-grow: 0 !important;
|
||||
}
|
||||
|
||||
::ng-deep page-checkout-dummy ui-searchbox .ui-searchbox-input {
|
||||
@apply pl-0 !important;
|
||||
}
|
||||
|
||||
::ng-deep page-checkout-dummy ui-searchbox .hint {
|
||||
@apply mr-10 !important;
|
||||
}
|
||||
|
||||
::ng-deep page-checkout-dummy .price .suffix {
|
||||
margin-top: 7.5px;
|
||||
}
|
||||
|
||||
::ng-deep page-checkout-dummy .searchbox-control .input-wrapper {
|
||||
@apply block;
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit, Optional } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { UiFilterScanProvider } from '@ui/filter';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { Subject } from 'rxjs';
|
||||
import { first, shareReplay, takeUntil } from 'rxjs/operators';
|
||||
import { threadId } from 'worker_threads';
|
||||
import { CheckoutDummyScanProvider } from './checkout-dummy-scan.provider';
|
||||
import { CheckoutDummyStore } from './checkout-dummy.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-dummy',
|
||||
templateUrl: 'checkout-dummy.component.html',
|
||||
styleUrls: ['checkout-dummy.component.scss'],
|
||||
providers: [
|
||||
CheckoutDummyStore,
|
||||
{
|
||||
provide: UiFilterScanProvider,
|
||||
useClass: CheckoutDummyScanProvider,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutDummyComponent implements OnInit, OnDestroy {
|
||||
// Form
|
||||
control: FormGroup;
|
||||
|
||||
// Searchbox
|
||||
query$ = this._store.query$;
|
||||
message$ = this._store.message$;
|
||||
loading$ = this._store.fetching$.pipe(shareReplay());
|
||||
|
||||
private _scanProvider: UiFilterScanProvider;
|
||||
get scanProvider() {
|
||||
return this._scanProvider;
|
||||
}
|
||||
|
||||
// Datepicker
|
||||
minDate = this._dateAdapter.addCalendarDays(new Date(), -1);
|
||||
estimatedShippingDate$ = this._store.estimatedShippingDate$.pipe(shareReplay());
|
||||
|
||||
// Dropdowns
|
||||
vats$ = this._store.vats$;
|
||||
suppliers$ = this._store.suppliers$;
|
||||
|
||||
params: Params;
|
||||
|
||||
_onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@Inject(UiFilterScanProvider) @Optional() private scanProviders: UiFilterScanProvider[],
|
||||
private _router: Router,
|
||||
private _route: ActivatedRoute,
|
||||
private _application: ApplicationService,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _fb: FormBuilder,
|
||||
private _dateAdapter: DateAdapter,
|
||||
private _modal: UiModalService,
|
||||
private _store: CheckoutDummyStore
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this._scanProvider = this.scanProviders?.find((provider) => provider.for === 'dummy');
|
||||
this.initForm();
|
||||
this.updateBreadcrumb();
|
||||
|
||||
this._store.item$.pipe(takeUntil(this._onDestroy$)).subscribe((item) => {
|
||||
if (item) {
|
||||
this.clearForm();
|
||||
this.populateForm(item);
|
||||
}
|
||||
});
|
||||
|
||||
const params = this._route.snapshot.queryParams;
|
||||
if (Object.keys(params).length !== 0) {
|
||||
this.params = params;
|
||||
const item = {
|
||||
ean: params.ean || '',
|
||||
name: params.name || '',
|
||||
quantity: params.quantity || '',
|
||||
estimatedShippingDate: params.estimatedShippingDate || this._dateAdapter.today().toISOString(),
|
||||
contributors: params.contributors || '',
|
||||
manufacturer: params.manufacturer || '',
|
||||
supplier: params.supplier || 5,
|
||||
price: params.price || '',
|
||||
vat: params.vat || '',
|
||||
};
|
||||
this.populateFormFromParams(item);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
async updateBreadcrumb() {
|
||||
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this._application.activatedProcessId,
|
||||
name: 'Neuanlage',
|
||||
path: '/cart/dummy',
|
||||
tags: ['checkout', 'cart', 'dummy'],
|
||||
section: 'customer',
|
||||
});
|
||||
}
|
||||
|
||||
setQuery(ean: string) {
|
||||
if (ean) {
|
||||
this._store.query = ean;
|
||||
}
|
||||
}
|
||||
|
||||
search(ean: string) {
|
||||
this.setQuery(ean);
|
||||
this._store.search();
|
||||
}
|
||||
|
||||
changeEstimatedShippingDate(date: Date) {
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
this._store.estimatedShippingDate = date.toISOString();
|
||||
}
|
||||
|
||||
initForm() {
|
||||
const fb = this._fb;
|
||||
this.control = fb.group({
|
||||
ean: fb.control('', Validators.pattern('^[0-9]{13}$')),
|
||||
name: fb.control('', Validators.required),
|
||||
quantity: fb.control('', [Validators.required, Validators.pattern('^[0-9]*$'), Validators.min(1)]),
|
||||
estimatedShippingDate: fb.control(this._dateAdapter.today().toISOString(), Validators.required),
|
||||
contributors: fb.control(''),
|
||||
manufacturer: fb.control(''),
|
||||
supplier: fb.control(5, Validators.required), // 5 === Dummy
|
||||
price: fb.control('', [Validators.required, Validators.pattern(/^\d+([\,]\d{1,2})?$/)]),
|
||||
vat: fb.control('', Validators.required),
|
||||
});
|
||||
this.changeEstimatedShippingDate(this._dateAdapter.today()); // Update View
|
||||
}
|
||||
|
||||
populateForm(item: ItemDTO) {
|
||||
this.control.get('ean').setValue(item?.product?.ean);
|
||||
this.control.get('name').setValue(item?.product?.name);
|
||||
this.control.get('contributors').setValue(item?.product?.contributors);
|
||||
this.control.get('manufacturer').setValue(item?.product?.manufacturer);
|
||||
this.control
|
||||
.get('price')
|
||||
.setValue(
|
||||
item?.catalogAvailability?.price?.value?.value ? String(item?.catalogAvailability?.price?.value?.value).replace('.', ',') : ''
|
||||
);
|
||||
this.control.get('vat').setValue(item?.catalogAvailability?.price?.vat?.vatType);
|
||||
}
|
||||
|
||||
populateFormFromParams(item: any) {
|
||||
this.control.get('name').setValue(item.name);
|
||||
this.control.get('contributors').setValue(item.contributors);
|
||||
this.control.get('manufacturer').setValue(item.manufacturer);
|
||||
this.control.get('price').setValue(item.price ? String(item.price).replace('.', ',') : '');
|
||||
this.control.get('vat').setValue(Number(item.vat));
|
||||
this.control.get('quantity').setValue(item.quantity);
|
||||
this.control.get('ean').setValue(item.ean);
|
||||
this.control.get('supplier').setValue(Number(item.supplier));
|
||||
this.control.get('estimatedShippingDate').setValue(item.estimatedShippingDate);
|
||||
this.changeEstimatedShippingDate(new Date(item.estimatedShippingDate)); // Update View
|
||||
}
|
||||
|
||||
clearForm(withEan?: boolean) {
|
||||
if (withEan) {
|
||||
this.control.reset();
|
||||
} else {
|
||||
Object.keys(this.control.controls).forEach((key) => {
|
||||
if (key !== 'ean') {
|
||||
this.control.get(key).reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.control.get('supplier').setValue(5);
|
||||
this.control.get('estimatedShippingDate').setValue(this._dateAdapter.today().toISOString());
|
||||
this.changeEstimatedShippingDate(this._dateAdapter.today());
|
||||
this.control.markAsUntouched();
|
||||
}
|
||||
|
||||
async nextItem() {
|
||||
if (this.control.invalid || this.control.disabled) {
|
||||
this.control.enable();
|
||||
return;
|
||||
}
|
||||
this.control.disable();
|
||||
|
||||
try {
|
||||
const branch = await this._store.currentBranch$.pipe(first()).toPromise();
|
||||
if (!this.params) {
|
||||
await this._store.createAddToCartItem(this.control, branch);
|
||||
this._store.addToCart(() => {});
|
||||
} else {
|
||||
await this._store.createAddToCartItem(this.control, branch, true);
|
||||
this._store.updateCart(() => {});
|
||||
}
|
||||
} catch (error) {
|
||||
this._modal.open({
|
||||
title: 'Bestellung konnte nicht angelegt werden',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
this.clearForm(true);
|
||||
this.control.enable();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.control.invalid || this.control.disabled) {
|
||||
this.control.enable();
|
||||
return;
|
||||
}
|
||||
this.control.disable();
|
||||
|
||||
try {
|
||||
const branch = await this._store.currentBranch$.pipe(first()).toPromise();
|
||||
if (!this.params) {
|
||||
await this._store.createAddToCartItem(this.control, branch);
|
||||
this._store.addToCart(async () => {
|
||||
// Set filter for navigation to customer search if customer is not set
|
||||
const customer = await this._store.customer$.pipe(first()).toPromise();
|
||||
const customerFilter = await this._store.customerFilter$.pipe(first()).toPromise();
|
||||
let filter: { [key: string]: string };
|
||||
if (!customer) {
|
||||
filter = customerFilter;
|
||||
this._router.navigate(['/customer', 'search'], { queryParams: { customertype: filter.customertype } });
|
||||
} else {
|
||||
this._router.navigate(['/cart', 'review']);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await this._store.createAddToCartItem(this.control, branch, true);
|
||||
this._store.updateCart(async () => {
|
||||
// Set filter for navigation to customer search if customer is not set
|
||||
const customer = await this._store.customer$.pipe(first()).toPromise();
|
||||
const customerFilter = await this._store.customerFilter$.pipe(first()).toPromise();
|
||||
let filter: { [key: string]: string };
|
||||
if (!customer) {
|
||||
filter = customerFilter;
|
||||
this._router.navigate(['/customer', 'search'], { queryParams: { customertype: filter.customertype } });
|
||||
} else {
|
||||
this._router.navigate(['/cart', 'review']);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this._modal.open({
|
||||
title: 'Bestellung konnte nicht angelegt werden',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
this.clearForm();
|
||||
this.control.enable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiDatepickerModule } from '@ui/datepicker';
|
||||
import { UiDropdownModule } from '@ui/dropdown';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { UiSearchboxNextModule } from '@ui/searchbox';
|
||||
import { UiSelectModule } from '@ui/select';
|
||||
|
||||
import { CheckoutDummyComponent } from './checkout-dummy.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
UiIconModule,
|
||||
UiDatepickerModule,
|
||||
UiDropdownModule,
|
||||
UiSelectModule,
|
||||
UiCommonModule,
|
||||
UiFormControlModule,
|
||||
UiInputModule,
|
||||
UiSearchboxNextModule,
|
||||
],
|
||||
exports: [CheckoutDummyComponent],
|
||||
declarations: [CheckoutDummyComponent],
|
||||
providers: [],
|
||||
})
|
||||
export class CheckoutDummyModule {}
|
||||
@@ -0,0 +1,393 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AddToShoppingCartDTO, AvailabilityDTO, BranchDTO, DestinationDTO, PriceDTO, ProductDTO, PromotionDTO } from '@swagger/checkout';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { Observable } from 'rxjs';
|
||||
import { first, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
interface CheckoutDummyState {
|
||||
item: ItemDTO;
|
||||
shoppingCartItemId: number;
|
||||
addToCartItem: AddToShoppingCartDTO;
|
||||
estimatedShippingDate: string;
|
||||
message: string;
|
||||
query: string;
|
||||
fetching: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CheckoutDummyStore extends ComponentStore<CheckoutDummyState> {
|
||||
get item() {
|
||||
return this.get((s) => s.item);
|
||||
}
|
||||
|
||||
set item(item: ItemDTO) {
|
||||
if (this.item !== item) {
|
||||
this.patchState({ item });
|
||||
}
|
||||
}
|
||||
|
||||
readonly item$ = this.select((s) => s.item);
|
||||
|
||||
get shoppingCartItemId() {
|
||||
return this.get((s) => s.shoppingCartItemId);
|
||||
}
|
||||
|
||||
set shoppingCartItemId(shoppingCartItemId: number) {
|
||||
if (this.shoppingCartItemId !== shoppingCartItemId) {
|
||||
this.patchState({ shoppingCartItemId });
|
||||
}
|
||||
}
|
||||
|
||||
readonly shoppingCartItemId$ = this.select((s) => s.shoppingCartItemId);
|
||||
|
||||
get addToCartItem() {
|
||||
return this.get((s) => s.addToCartItem);
|
||||
}
|
||||
|
||||
set addToCartItem(addToCartItem: AddToShoppingCartDTO) {
|
||||
if (this.addToCartItem !== addToCartItem) {
|
||||
this.patchState({ addToCartItem });
|
||||
}
|
||||
}
|
||||
|
||||
readonly addToCartItem$ = this.select((s) => s.addToCartItem);
|
||||
|
||||
get query() {
|
||||
return this.get((s) => s.query);
|
||||
}
|
||||
|
||||
set query(query: string) {
|
||||
if (this.query !== query) {
|
||||
this.patchState({ query });
|
||||
}
|
||||
}
|
||||
|
||||
readonly query$ = this.select((s) => s.query);
|
||||
|
||||
get fetching() {
|
||||
return this.get((s) => s.fetching);
|
||||
}
|
||||
|
||||
set fetching(fetching: boolean) {
|
||||
this.patchState({ fetching });
|
||||
}
|
||||
|
||||
readonly fetching$ = this.select((s) => s.fetching);
|
||||
|
||||
get message() {
|
||||
return this.get((s) => s.message);
|
||||
}
|
||||
|
||||
set message(message: string) {
|
||||
this.patchState({ message });
|
||||
}
|
||||
|
||||
readonly message$ = this.select((s) => s.message);
|
||||
|
||||
get estimatedShippingDate() {
|
||||
return this.get((s) => s.message);
|
||||
}
|
||||
|
||||
set estimatedShippingDate(estimatedShippingDate: string) {
|
||||
this.patchState({ estimatedShippingDate });
|
||||
}
|
||||
|
||||
readonly processId$ = this._application.activatedProcessId$;
|
||||
|
||||
readonly customer$ = this.processId$.pipe(switchMap((processId) => this._checkoutService.getBuyer({ processId })));
|
||||
|
||||
readonly customerFeatures$ = this.processId$.pipe(switchMap((processId) => this._checkoutService.getCustomerFeatures({ processId })));
|
||||
|
||||
readonly customerFilter$ = this.customerFeatures$.pipe(
|
||||
withLatestFrom(this.processId$),
|
||||
switchMap(([customerFeatures, processId]) => this._checkoutService.canSetCustomer({ processId, customerFeatures })),
|
||||
map((res) => res.filter)
|
||||
);
|
||||
|
||||
readonly estimatedShippingDate$ = this.select((s) => s.estimatedShippingDate);
|
||||
|
||||
readonly vats$ = this._omsService.getVATs();
|
||||
readonly suppliers$ = this._availabilityService.getSuppliers().pipe(
|
||||
map((suppliers) =>
|
||||
suppliers
|
||||
.filter((supplier) => {
|
||||
const displaySupplierIds = [2, 3, 4, 5, 6, 8, 10, 13, 15, 16];
|
||||
return displaySupplierIds.find((id) => id === supplier.id);
|
||||
})
|
||||
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
||||
)
|
||||
);
|
||||
readonly currentBranch$ = this._availabilityService.getCurrentBranch();
|
||||
|
||||
constructor(
|
||||
private _application: ApplicationService,
|
||||
private _omsService: DomainOmsService,
|
||||
private _checkoutService: DomainCheckoutService,
|
||||
private _availabilityService: DomainAvailabilityService,
|
||||
private _catalogService: DomainCatalogService,
|
||||
private _modal: UiModalService
|
||||
) {
|
||||
super({
|
||||
item: undefined,
|
||||
shoppingCartItemId: undefined,
|
||||
addToCartItem: undefined,
|
||||
estimatedShippingDate: '',
|
||||
query: '',
|
||||
message: '',
|
||||
fetching: false,
|
||||
});
|
||||
}
|
||||
|
||||
search = this.effect(($) =>
|
||||
$.pipe(
|
||||
tap((_) => this.patchState({ fetching: true })),
|
||||
withLatestFrom(this.query$),
|
||||
switchMap(([_, ean]) =>
|
||||
this.searchRequest(ean).pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
const item = res.result[0];
|
||||
if (!!item && item?.product?.format !== 'EB' && item?.product?.format !== 'DL') {
|
||||
this.patchState({
|
||||
item: res.result[0],
|
||||
message: '',
|
||||
fetching: false,
|
||||
});
|
||||
} else {
|
||||
this.patchState({
|
||||
item: undefined,
|
||||
message: 'Keine Suchergebnisse',
|
||||
fetching: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
(error: Error) => {
|
||||
this._modal.open({
|
||||
title: 'Fehler bei der Suche nach der EAN',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
this.patchState({ fetching: false, message: '' });
|
||||
console.error('CheckoutDummyStore.search()', error);
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
addToCart = this.effect((cb$: Observable<Function>) =>
|
||||
cb$.pipe(
|
||||
tap((_) => this.patchState({ fetching: true })),
|
||||
withLatestFrom(this.processId$, this.addToCartItem$),
|
||||
switchMap(([cb, processId, newItem]) =>
|
||||
this.addToCartRequest(processId, newItem).pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
this.patchState({
|
||||
fetching: false,
|
||||
});
|
||||
cb?.call(undefined);
|
||||
},
|
||||
(error: Error) => {
|
||||
this._modal.open({
|
||||
title: 'Fehler beim Hinzufügen zum Warenkorb',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
this.patchState({ fetching: false });
|
||||
console.error('CheckoutDummyStore.addToCart()', error);
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
updateCart = this.effect((cb$: Observable<Function>) =>
|
||||
cb$.pipe(
|
||||
tap((_) => this.patchState({ fetching: true })),
|
||||
withLatestFrom(this.processId$, this.addToCartItem$, this.shoppingCartItemId$),
|
||||
switchMap(([cb, processId, newItem, shoppingCartItemId]) => {
|
||||
const availability = newItem.availability;
|
||||
const quantity = newItem.quantity;
|
||||
const destination = newItem.destination;
|
||||
return this.updateCartRequest({ processId, shoppingCartItemId, availability, quantity, destination }).pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
this.patchState({
|
||||
fetching: false,
|
||||
});
|
||||
cb?.call(undefined);
|
||||
},
|
||||
(error: Error) => {
|
||||
this._modal.open({
|
||||
title: 'Fehler beim Updaten des Warenkorbs',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
this.patchState({ fetching: false });
|
||||
console.error('CheckoutDummyStore.updateCart()', error);
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
searchRequest(ean: string) {
|
||||
const queryToken = {
|
||||
input: {
|
||||
qs: ean,
|
||||
},
|
||||
take: 1,
|
||||
doNotTrack: true,
|
||||
};
|
||||
return this._catalogService.search({ queryToken });
|
||||
}
|
||||
|
||||
addToCartRequest(processId: number, newItem: AddToShoppingCartDTO) {
|
||||
return this._checkoutService.addItemToShoppingCart({ processId, items: [newItem] });
|
||||
}
|
||||
|
||||
updateCartRequest({
|
||||
processId,
|
||||
shoppingCartItemId,
|
||||
availability,
|
||||
quantity,
|
||||
destination,
|
||||
}: {
|
||||
processId: number;
|
||||
shoppingCartItemId: number;
|
||||
availability: AvailabilityDTO;
|
||||
quantity: number;
|
||||
destination: DestinationDTO;
|
||||
}) {
|
||||
return this._checkoutService.updateItemInShoppingCart({
|
||||
processId,
|
||||
shoppingCartItemId,
|
||||
update: {
|
||||
availability,
|
||||
quantity,
|
||||
destination,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async createAddToCartItem(control: FormGroup, branch: BranchDTO, update?: boolean) {
|
||||
let item: ItemDTO;
|
||||
const quantity = Number(control.get('quantity').value);
|
||||
const price = this._createPriceDTO(control);
|
||||
let promoPoints: number;
|
||||
// Check if item exists or ean inside the control changed in the meantime
|
||||
if (!!this.item && this.item.product.ean === control.get('ean').value) {
|
||||
item = this.item;
|
||||
promoPoints = await this._getPromoPoints({ itemId: item.id, quantity, price: price.value.value });
|
||||
} else {
|
||||
item = undefined;
|
||||
}
|
||||
const availability = this._createAvailabilityDTO({ price, control });
|
||||
const product = this._createProductDTO({ item, control });
|
||||
const newItem: AddToShoppingCartDTO = {
|
||||
quantity,
|
||||
availability,
|
||||
product,
|
||||
promotion: !!item ? { points: promoPoints } : undefined,
|
||||
destination: {
|
||||
data: { target: 1, targetBranch: { id: branch.id } },
|
||||
},
|
||||
};
|
||||
|
||||
if (update) {
|
||||
const shoppingCart = await this._checkoutService
|
||||
.getShoppingCart({ processId: this._application.activatedProcessId })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const existingItem = shoppingCart?.items?.find(
|
||||
(i) => i?.data?.product?.ean === i?.data?.product?.ean && i?.data?.features['orderType'] === 'Abholung'
|
||||
);
|
||||
this.patchState({ addToCartItem: newItem, shoppingCartItemId: existingItem?.id });
|
||||
}
|
||||
this.patchState({ addToCartItem: newItem });
|
||||
}
|
||||
|
||||
private async _getPromoPoints({ itemId, quantity, price }: { itemId: number; quantity: number; price: number }) {
|
||||
let points: number;
|
||||
try {
|
||||
points = await this._catalogService
|
||||
.getPromotionPoints({
|
||||
items: [
|
||||
{
|
||||
id: itemId,
|
||||
quantity,
|
||||
price,
|
||||
},
|
||||
],
|
||||
})
|
||||
.pipe(
|
||||
first(),
|
||||
map((response) => response.result.itemId)
|
||||
)
|
||||
.toPromise();
|
||||
} catch (error) {
|
||||
this._modal.open({
|
||||
title: 'Fehler beim Abfragen der Lesepunkte',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
console.error('CheckoutDummyStore._getPromoPoints()', error);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private _createPriceDTO(control: FormGroup): PriceDTO {
|
||||
return {
|
||||
value: {
|
||||
value: Number(String(control.get('price').value).replace(',', '.')),
|
||||
currency: 'EUR',
|
||||
},
|
||||
vat: {
|
||||
vatType: control.get('vat').value || null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private _createAvailabilityDTO({ price, control }: { price: PriceDTO; control: FormGroup }): AvailabilityDTO {
|
||||
return {
|
||||
availabilityType: 1024,
|
||||
supplier: {
|
||||
id: control.get('supplier').value,
|
||||
},
|
||||
price,
|
||||
estimatedShippingDate: control.get('estimatedShippingDate').value || null,
|
||||
supplyChannel: 'MANUALLY',
|
||||
};
|
||||
}
|
||||
|
||||
private _createProductDTO({ item, control }: { item?: ItemDTO; control: FormGroup }): ProductDTO {
|
||||
const formValues: Partial<ProductDTO> = {
|
||||
ean: control.get('ean').value,
|
||||
name: control.get('name').value,
|
||||
contributors: control.get('contributors').value,
|
||||
manufacturer: control.get('manufacturer').value,
|
||||
};
|
||||
return !!item
|
||||
? {
|
||||
catalogProductNumber: String(item.id),
|
||||
...item.product,
|
||||
...formValues,
|
||||
}
|
||||
: {
|
||||
catalogProductNumber: '',
|
||||
...formValues,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@
|
||||
</p>
|
||||
|
||||
<div class="btn-wrapper">
|
||||
<a class="cta-article" [routerLink]="['/product', 'search']">Artikel suchen</a>
|
||||
<a class="cta-primary" [routerLink]="['/product', 'search']">Artikel suchen</a>
|
||||
<a class="cta-secondary" [routerLink]="['/cart', 'dummy']">Neuanlage</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,6 +61,7 @@
|
||||
<ng-container *ngFor="let group of groupedItems$ | async; let lastGroup = last">
|
||||
<div class="row item-group-header">
|
||||
<ui-icon
|
||||
*ngIf="group.orderType !== 'Dummy'"
|
||||
class="icon-order-type"
|
||||
[size]="group.orderType === 'B2B-Versand' ? '50px' : '25px'"
|
||||
[icon]="
|
||||
@@ -76,8 +78,9 @@
|
||||
: 'truck'
|
||||
"
|
||||
></ui-icon>
|
||||
<div class="label">
|
||||
{{ group.orderType }}
|
||||
<div class="label" [class.dummy]="group.orderType === 'Dummy'">
|
||||
{{ group.orderType !== 'Dummy' ? group.orderType : 'Manuelle Anlage / Dummy Bestellung' }}
|
||||
<a *ngIf="group.orderType === 'Dummy'" class="cta-secondary" [routerLink]="['/cart', 'dummy']">Hinzufügen</a>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="group.orderType === 'Versand' || group.orderType === 'B2B-Versand' || group.orderType === 'DIG-Versand'">
|
||||
@@ -126,24 +129,50 @@
|
||||
<img class="book-icon" [src]="'/assets/images/Icon_' + item?.product?.format + '.svg'" alt="book-icon" />
|
||||
{{ item?.product?.manufacturer + ' | ' + item?.product?.contributors | trim: 30 }}
|
||||
</div>
|
||||
<div *ngIf="group.orderType !== 'Rücklage' && group.orderType !== 'Download'" class="product-delivery">
|
||||
<div
|
||||
*ngIf="group.orderType !== 'Rücklage' && group.orderType !== 'Download' && group.orderType !== 'Dummy'"
|
||||
class="product-delivery"
|
||||
>
|
||||
{{ group.orderType }} {{ group.orderType === 'Abholung' ? 'ab' : '' }}
|
||||
{{ item?.availability?.estimatedShippingDate | date }}
|
||||
</div>
|
||||
<div *ngIf="group.orderType === 'Dummy'" class="product-delivery">
|
||||
Abholung {{ group.orderType === 'Dummy' ? 'ab' : '' }}
|
||||
{{ item?.availability?.estimatedShippingDate ? (item?.availability?.estimatedShippingDate | date) : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-price">
|
||||
{{ item?.unitPrice?.value?.value | currency: item?.unitPrice?.value?.currency:'code' }}
|
||||
</div>
|
||||
<div class="product-quantity">
|
||||
<ui-quantity-dropdown
|
||||
*ngIf="group.orderType !== 'Dummy'; else quantityDummy"
|
||||
[ngModel]="item?.quantity"
|
||||
(ngModelChange)="updateItemQuantity(item, $event)"
|
||||
[showSpinner]="showQuantityControlSpinnerItemId === item.id"
|
||||
>
|
||||
</ui-quantity-dropdown>
|
||||
<ng-template #quantityDummy>
|
||||
{{ item?.quantity }}
|
||||
</ng-template>
|
||||
</div>
|
||||
<div>
|
||||
<button class="cta-edit" (click)="changeItem(item)" [disabled]="showChangeButtonSpinnerItemId">
|
||||
<div class="product-actions">
|
||||
<button
|
||||
*ngIf="group.orderType === 'Dummy'"
|
||||
class="cta-edit"
|
||||
(click)="changeDummyItem(item)"
|
||||
[disabled]="showChangeButtonSpinnerItemId"
|
||||
>
|
||||
<ui-spinner [show]="showChangeButtonSpinnerItemId === item.id">
|
||||
Ändern
|
||||
</ui-spinner>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="group.orderType !== 'Download' && group.orderType !== 'Dummy'"
|
||||
class="cta-edit"
|
||||
(click)="changeItem(item)"
|
||||
[disabled]="showChangeButtonSpinnerItemId"
|
||||
>
|
||||
<ui-spinner [show]="showChangeButtonSpinnerItemId === item.id">
|
||||
Ändern
|
||||
</ui-spinner>
|
||||
@@ -170,7 +199,7 @@
|
||||
<span class="shipping-cost-info">ohne Versandkosten</span>
|
||||
</div>
|
||||
<button
|
||||
class="cta-order"
|
||||
class="cta-primary"
|
||||
(click)="order()"
|
||||
[disabled]="showOrderButtonSpinner || specialCommentIsDirty"
|
||||
[class.special-comment-dirty]="specialCommentIsDirty"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
@apply mt-px-40 text-center;
|
||||
@apply mt-px-40 flex flex-col items-center;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -62,9 +62,12 @@
|
||||
@apply bg-transparent text-brand font-bold text-xl outline-none border-none;
|
||||
}
|
||||
|
||||
.cta-order,
|
||||
.cta-article {
|
||||
@apply bg-brand text-white font-bold text-lg outline-none border-none rounded-full px-6 py-3;
|
||||
.cta-primary {
|
||||
@apply bg-brand text-white font-bold text-lg outline-none border-brand border-solid border-2 rounded-full px-6 py-3;
|
||||
}
|
||||
|
||||
.cta-secondary {
|
||||
@apply bg-white text-brand border-brand font-bold text-lg outline-none px-6 py-3 mt-4;
|
||||
}
|
||||
|
||||
.cta-order.special-comment-dirty {
|
||||
@@ -101,6 +104,15 @@ hr {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.dummy {
|
||||
@apply w-full flex justify-between items-center;
|
||||
|
||||
.cta-secondary {
|
||||
@apply mt-0 pr-0;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-order-type {
|
||||
@apply text-font-customer mr-3;
|
||||
}
|
||||
@@ -158,6 +170,10 @@ hr {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.product-actions {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@apply absolute bottom-0 left-0 right-0 p-7;
|
||||
box-shadow: 0px -2px 24px 0px #dce2e9;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, 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, BranchDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
||||
import { first, map, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
|
||||
@@ -14,8 +14,6 @@ import { Subject, NEVER } from 'rxjs';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ModalAvailabilitiesComponent } from '@modal/availabilities';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-review',
|
||||
@@ -23,9 +21,8 @@ import { ModalAvailabilitiesComponent } from '@modal/availabilities';
|
||||
styleUrls: ['checkout-review.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CheckoutReviewComponent implements OnDestroy {
|
||||
export class CheckoutReviewComponent implements OnInit {
|
||||
private _orderCompleted = new Subject<void>();
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
shoppingCart$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._orderCompleted),
|
||||
@@ -53,11 +50,17 @@ export class CheckoutReviewComponent implements OnDestroy {
|
||||
map((items) =>
|
||||
items.reduce((grouped, item) => {
|
||||
// let index = grouped.findIndex((g) => g?.orderType === item?.features?.orderType && g.destination?.id === item?.destination?.id);
|
||||
let index = grouped.findIndex((g) => g?.orderType === item?.features?.orderType);
|
||||
let index = grouped.findIndex((g) => g?.orderType === 'Dummy' || g?.orderType === item?.features?.orderType);
|
||||
let group = index !== -1 ? grouped[index] : undefined;
|
||||
|
||||
if (!group) {
|
||||
group = { orderType: item.features.orderType, destination: item.destination?.data, items: [] };
|
||||
group = {
|
||||
orderType: item?.availability?.supplyChannel === 'MANUALLY' ? 'Dummy' : item.features.orderType,
|
||||
destination: item.destination?.data,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
group.items = [...group.items, item]?.sort((a, b) => a.destination?.data?.targetBranch?.id - b.destination?.data?.targetBranch?.id);
|
||||
|
||||
if (index !== -1) {
|
||||
@@ -90,15 +93,26 @@ export class CheckoutReviewComponent implements OnDestroy {
|
||||
if (displayOrders.length === 0) {
|
||||
return NEVER;
|
||||
}
|
||||
return this.domainCatalogService
|
||||
.getPromotionPoints({
|
||||
items: displayOrders.map((i) => ({
|
||||
id: Number(i.product?.catalogProductNumber),
|
||||
quantity: i.quantity,
|
||||
price: i.unitPrice?.value?.value,
|
||||
})),
|
||||
const items = displayOrders
|
||||
.map((i) => {
|
||||
if (i?.product?.catalogProductNumber) {
|
||||
return {
|
||||
id: Number(i.product?.catalogProductNumber),
|
||||
quantity: i.quantity,
|
||||
price: i.unitPrice?.value?.value,
|
||||
};
|
||||
}
|
||||
})
|
||||
.pipe(map((response) => Object.values(response.result).reduce((sum, points) => sum + points, 0)));
|
||||
.filter((item) => item !== undefined);
|
||||
if (items.length !== 0) {
|
||||
return this.domainCatalogService
|
||||
.getPromotionPoints({
|
||||
items,
|
||||
})
|
||||
.pipe(map((response) => Object.values(response.result).reduce((sum, points) => sum + points, 0)));
|
||||
} else {
|
||||
return NEVER;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -127,27 +141,47 @@ export class CheckoutReviewComponent implements OnDestroy {
|
||||
private domainCatalogService: DomainCatalogService,
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private domainPrinterService: DomainPrinterService
|
||||
) {
|
||||
this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTag$(this.applicationService.activatedProcessId, 'checkout')
|
||||
.pipe(first())
|
||||
.subscribe(async (crumbs) => {
|
||||
for await (const crumb of crumbs) {
|
||||
this.breadcrumb.removeBreadcrumb(crumb.id);
|
||||
}
|
||||
this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: 'Warenkorb',
|
||||
path: '/cart/review',
|
||||
tags: ['checkout', 'cart'],
|
||||
section: 'customer',
|
||||
});
|
||||
});
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.removeBreadcrumbs();
|
||||
await this.updateBreadcrumb();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
async updateBreadcrumb() {
|
||||
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: 'Warenkorb',
|
||||
path: '/cart/review',
|
||||
tags: ['checkout', 'cart'],
|
||||
section: 'customer',
|
||||
});
|
||||
}
|
||||
|
||||
async removeBreadcrumbs() {
|
||||
const checkoutDummyCrumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['checkout', 'cart', 'dummy'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
checkoutDummyCrumbs.forEach(async (crumb) => {
|
||||
await this.breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
}
|
||||
|
||||
changeDummyItem(shoppingCartItem: ShoppingCartItemDTO) {
|
||||
this.router.navigate(['/cart', 'dummy'], {
|
||||
queryParams: {
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async changeItem(shoppingCartItem: ShoppingCartItemDTO) {
|
||||
|
||||
@@ -105,7 +105,9 @@
|
||||
|
||||
<div class="footer">
|
||||
<div class="overview">
|
||||
<span class="promotion-points">{{ totalItemCount$ | async }} Artikel | {{ totalReadingPoints$ | async }} Lesepunkte</span>
|
||||
<span *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="promotion-points"
|
||||
>{{ totalItemCount$ | async }} Artikel | {{ totalReadingPoints }} Lesepunkte</span
|
||||
>
|
||||
|
||||
<div class="price-wrapper">
|
||||
<div class="total-price">Gesamtsumme {{ totalPrice$ | async | currency: ' ' }} {{ totalPriceCurrency$ | async }}</div>
|
||||
|
||||
@@ -140,12 +140,12 @@
|
||||
box-shadow: 0px -2px 24px 0px #dce2e9;
|
||||
|
||||
.overview {
|
||||
@apply flex flex-row items-center justify-between;
|
||||
@apply flex flex-row items-center justify-end;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.promotion-points {
|
||||
@apply text-ucla-blue text-regular font-bold;
|
||||
@apply text-ucla-blue text-regular font-bold flex-grow;
|
||||
}
|
||||
|
||||
.total-price {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { DisplayOrderItemDTO } from '@swagger/oms';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
import { NEVER } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'page-checkout-summary',
|
||||
@@ -31,19 +32,27 @@ export class CheckoutSummaryComponent {
|
||||
);
|
||||
|
||||
totalReadingPoints$ = this.displayOrders$.pipe(
|
||||
switchMap((displayOrders) =>
|
||||
this.domainCatalogService
|
||||
.getPromotionPoints({
|
||||
items: displayOrders
|
||||
.reduce<DisplayOrderItemDTO[]>((items, order) => [...items, ...order.items], [])
|
||||
.map((i) => ({
|
||||
switchMap((displayOrders) => {
|
||||
const items = displayOrders
|
||||
.reduce<DisplayOrderItemDTO[]>((items, order) => [...items, ...order.items], [])
|
||||
.map((i) => {
|
||||
if (i?.product?.catalogProductNumber) {
|
||||
return {
|
||||
id: Number(i.product?.catalogProductNumber),
|
||||
quantity: i.quantity,
|
||||
price: i.price?.value?.value,
|
||||
})),
|
||||
};
|
||||
}
|
||||
})
|
||||
.pipe(map((response) => Object.values(response.result).reduce((sum, points) => sum + points, 0)))
|
||||
)
|
||||
.filter((item) => item !== undefined);
|
||||
if (items.length !== 0) {
|
||||
return this.domainCatalogService
|
||||
.getPromotionPoints({ items })
|
||||
.pipe(map((response) => Object.values(response.result).reduce((sum, points) => sum + points, 0)));
|
||||
} else {
|
||||
return NEVER;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
totalPrice$ = this.displayOrders$.pipe(
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<form *ngIf="control" [formGroup]="control">
|
||||
<ui-form-control label="MwSt" variant="default">
|
||||
<ui-select formControlName="vat">
|
||||
<ui-select-option *ngFor="let vat of vats$ | async" [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
|
||||
<ui-form-control class="price" label="Preis" variant="default">
|
||||
<input uiInput formControlName="price" />
|
||||
</ui-form-control>
|
||||
</form>
|
||||
@@ -0,0 +1,11 @@
|
||||
form {
|
||||
@apply grid grid-flow-col items-center justify-end gap-4 mb-2;
|
||||
}
|
||||
|
||||
ui-form-control {
|
||||
@apply w-32;
|
||||
}
|
||||
|
||||
::ng-deep page-purchasing-options-modal-price-input ui-form-control .input-wrapper input {
|
||||
@apply w-20;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
import { VATDTO } from '@swagger/oms';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-purchasing-options-modal-price-input',
|
||||
templateUrl: 'purchasing-options-modal-price-input.component.html',
|
||||
styleUrls: ['purchasing-options-modal-price-input.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PurchasingOptionsModalPriceInputComponent implements OnInit {
|
||||
control: FormGroup;
|
||||
|
||||
vats$: Observable<VATDTO[]> = this._omsService.getVATs().pipe(shareReplay());
|
||||
|
||||
@Output()
|
||||
priceChanged = new EventEmitter<number>();
|
||||
|
||||
@Output()
|
||||
vatChanged = new EventEmitter<VATDTO>();
|
||||
|
||||
private _subscriptions = new Subscription();
|
||||
|
||||
constructor(private _omsService: DomainOmsService, private _fb: FormBuilder) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.initForm();
|
||||
}
|
||||
|
||||
initForm() {
|
||||
const fb = this._fb;
|
||||
this.control = fb.group({
|
||||
price: fb.control(undefined, [Validators.required, Validators.pattern(/^\d+([\,]\d{1,2})?$/), Validators.max(99999)]),
|
||||
vat: fb.control(undefined, [Validators.required]),
|
||||
});
|
||||
|
||||
this._subscriptions.add(
|
||||
this.control.get('price').valueChanges.subscribe((price) => this.priceChanged.emit(Number(String(price).replace(',', '.'))))
|
||||
);
|
||||
this._subscriptions.add(this.control.get('vat').valueChanges.subscribe(this.vatChanged));
|
||||
|
||||
this.control.markAllAsTouched();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { UiSelectModule } from '@ui/select';
|
||||
|
||||
import { PurchasingOptionsModalPriceInputComponent } from './purchasing-options-modal-price-input.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiFormControlModule, UiInputModule, UiSelectModule, FormsModule, ReactiveFormsModule],
|
||||
exports: [PurchasingOptionsModalPriceInputComponent],
|
||||
declarations: [PurchasingOptionsModalPriceInputComponent],
|
||||
providers: [],
|
||||
})
|
||||
export class PurchasingOptionsModalPriceInputModule {}
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="grow"></div>
|
||||
<div class="format">{{ item?.product?.formatDetail }}</div>
|
||||
<div class="price">
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: item?.catalogAvailability?.price?.value?.currency:'code' }}
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: item?.catalogAvailability?.price?.value?.currency || 'EUR':'code' }}
|
||||
</div>
|
||||
<div class="date" *ngIf="option$ | async; let option">
|
||||
<ng-container *ngIf="option === 'pick-up'">Abholung ab</ng-container>
|
||||
@@ -60,12 +60,21 @@
|
||||
<div class="quantity-error" *ngIf="quantityError$ | async; let message">{{ message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-price" *ngIf="showCustomPrice$ | async">
|
||||
<page-purchasing-options-modal-price-input
|
||||
(priceChanged)="changeCustomPrice($event)"
|
||||
(vatChanged)="changeCustomVat($event)"
|
||||
></page-purchasing-options-modal-price-input>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="summary-row" *ngIf="quantity$ | async; let quantity">
|
||||
<div class="reading-points">{{ quantity }} Artikel | {{ promoPoints$ | async }} Lesepunkte</div>
|
||||
<div class="reading-points">
|
||||
{{ quantity }} Artikel
|
||||
<ng-container *ngIf="promoPoints$ | async; let promoPoints"> | {{ promoPoints }} Lesepunkte </ng-container>
|
||||
</div>
|
||||
<div class="subtotal">
|
||||
Zwischensumme
|
||||
{{ item?.catalogAvailability?.price?.value?.value * quantity | currency: item?.catalogAvailability?.price?.value?.currency:'code' }}
|
||||
{{ (price$ | async) * quantity | currency: item?.catalogAvailability?.price?.value?.currency || 'EUR':'code' }}
|
||||
<div class="shipping-cost" *ngIf="showDeliveryInfo$ | async">
|
||||
ohne Versandkosten
|
||||
</div>
|
||||
@@ -76,7 +85,7 @@
|
||||
<button
|
||||
*ngIf="canContinueShopping$ | async"
|
||||
class="cta-continue-shopping"
|
||||
[disabled]="(fetching$ | async) || (canContinueShopping$ | async) === false"
|
||||
[disabled]="(fetching$ | async) || (canContinueShopping$ | async) === false || (customPriceInvalid$ | async) === true"
|
||||
(click)="continue('continue-shopping')"
|
||||
>
|
||||
Weiter einkaufen
|
||||
@@ -86,7 +95,7 @@
|
||||
</button>
|
||||
<button
|
||||
*ngIf="showTakeAwayButton$ | async"
|
||||
[disabled]="(fetching$ | async) || (canAdd$ | async) === false"
|
||||
[disabled]="(fetching$ | async) || (canAdd$ | async) === false || (customPriceInvalid$ | async) === true"
|
||||
class="cta-continue"
|
||||
(click)="continue()"
|
||||
>
|
||||
@@ -96,7 +105,7 @@
|
||||
*ngIf="showDefaultContinueButton$ | async"
|
||||
class="cta-continue"
|
||||
(click)="continue()"
|
||||
[disabled]="(fetching$ | async) || (canAdd$ | async) === false"
|
||||
[disabled]="(fetching$ | async) || (canAdd$ | async) === false || (customPriceInvalid$ | async) === true"
|
||||
>
|
||||
Fortfahren
|
||||
</button>
|
||||
|
||||
@@ -88,6 +88,10 @@ img.thumbnail {
|
||||
::ng-deep.spin {
|
||||
@apply text-brand;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@apply text-inactive-branch border-inactive-branch;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-continue,
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { AddToShoppingCartDTO, AvailabilityDTO } from '@swagger/checkout';
|
||||
import { AddToShoppingCartDTO, AvailabilityDTO, VATType } from '@swagger/checkout';
|
||||
import { UiModalRef } from '@ui/modal';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { debounceTime, first, map, switchMap } from 'rxjs/operators';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { PurchasingOptionsModalData } from './purchasing-options-modal.data';
|
||||
import { PurchasingOptions, PurchasingOptionsModalStore } from './purchasing-options-modal.store';
|
||||
@@ -45,6 +45,14 @@ export class PurchasingOptionsModalComponent {
|
||||
|
||||
readonly quantityError$ = this.purchasingOptionsModalStore.selectQuantityError;
|
||||
|
||||
readonly showCustomPrice$ = this.item$.pipe(map((item) => !item?.catalogAvailability?.price?.value?.value));
|
||||
|
||||
readonly customPriceInvalid$ = combineLatest([
|
||||
this.showCustomPrice$,
|
||||
this.purchasingOptionsModalStore.selectCustomPrice,
|
||||
this.purchasingOptionsModalStore.selectCustomVat,
|
||||
]).pipe(map(([showCustomPrice, customPrice, customVat]) => showCustomPrice && (!customPrice || !customVat)));
|
||||
|
||||
readonly showTakeAwayButton$ = combineLatest([
|
||||
this.option$,
|
||||
this.purchasingOptionsModalStore.selectFetchingAvailability,
|
||||
@@ -105,15 +113,24 @@ export class PurchasingOptionsModalComponent {
|
||||
map((buyer) => buyer.source)
|
||||
);
|
||||
|
||||
readonly promoPoints$ = combineLatest([this.item$, this.quantity$]).pipe(
|
||||
switchMap(([item, quantity]) =>
|
||||
price$ = combineLatest([this.item$, this.purchasingOptionsModalStore.selectCustomPrice]).pipe(
|
||||
map(([item, customPrice]) => item?.catalogAvailability?.price?.value?.value ?? customPrice ?? 0)
|
||||
);
|
||||
|
||||
vat$ = combineLatest([this.item$, this.purchasingOptionsModalStore.selectCustomVat]).pipe(
|
||||
map(([item, customVat]) => item?.catalogAvailability?.price.vat ?? customVat)
|
||||
);
|
||||
|
||||
readonly promoPoints$ = combineLatest([this.item$, this.quantity$, this.price$]).pipe(
|
||||
debounceTime(250),
|
||||
switchMap(([item, quantity, price]) =>
|
||||
this.domainCatalogService
|
||||
.getPromotionPoints({
|
||||
items: [
|
||||
{
|
||||
id: item.id,
|
||||
quantity: quantity,
|
||||
price: item.catalogAvailability.price?.value?.value,
|
||||
price: price,
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -145,6 +162,14 @@ export class PurchasingOptionsModalComponent {
|
||||
}
|
||||
}
|
||||
|
||||
changeCustomVat(vat: VATType) {
|
||||
this.purchasingOptionsModalStore.setCustomVat(vat);
|
||||
}
|
||||
|
||||
changeCustomPrice(price: number) {
|
||||
this.purchasingOptionsModalStore.setCustomPrice(price);
|
||||
}
|
||||
|
||||
backToSetOptions() {
|
||||
this.purchasingOptionsModalStore.setOption(undefined);
|
||||
}
|
||||
@@ -173,6 +198,8 @@ export class PurchasingOptionsModalComponent {
|
||||
const branch = await this.branch$.pipe(first()).toPromise();
|
||||
const shoppingCartItem = await this.purchasingOptionsModalStore.selectShoppingCartItem.pipe(first()).toPromise();
|
||||
const canAdd = await this.canAdd$.pipe(first()).toPromise();
|
||||
const customPrice = await this.purchasingOptionsModalStore.selectCustomPrice.pipe(first()).toPromise();
|
||||
const customVat = await this.purchasingOptionsModalStore.selectCustomVat.pipe(first()).toPromise();
|
||||
|
||||
if (canAdd || navigate === 'add-customer-data') {
|
||||
const newItem: AddToShoppingCartDTO = {
|
||||
@@ -187,6 +214,18 @@ export class PurchasingOptionsModalComponent {
|
||||
|
||||
newItem.product.catalogProductNumber = String(item.id);
|
||||
|
||||
if (!!customPrice && !!customVat) {
|
||||
newItem.availability.price = {
|
||||
value: {
|
||||
value: customPrice,
|
||||
currency: 'EUR',
|
||||
},
|
||||
vat: {
|
||||
vatType: customVat,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
switch (option) {
|
||||
case 'take-away':
|
||||
case 'pick-up':
|
||||
|
||||
@@ -21,6 +21,7 @@ import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
import { KeyNavigationModule } from '../../shared/key-navigation/key-navigation.module';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
import { PurchasingOptionsModalPriceInputModule } from './price-input/purchasing-options-modal-price-input.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -34,6 +35,7 @@ import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
UiSpinnerModule,
|
||||
KeyNavigationModule,
|
||||
RouterModule,
|
||||
PurchasingOptionsModalPriceInputModule,
|
||||
],
|
||||
exports: [PurchasingOptionsModalComponent],
|
||||
declarations: [
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
||||
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, ShoppingCartItemDTO, VATType } from '@swagger/checkout';
|
||||
import { isBoolean, isNullOrUndefined, isString } from '@utils/common';
|
||||
import { NEVER, Observable } from 'rxjs';
|
||||
import { delay, filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
@@ -32,6 +32,8 @@ interface PurchasingOptionsModalState {
|
||||
// TODO: FilterBranch in der UI Component sortieren und filtern
|
||||
filterResult?: BranchDTO[];
|
||||
availabilities: { [key: string]: AvailabilityDTO };
|
||||
customPrice?: number;
|
||||
customVat?: VATType;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -67,6 +69,10 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
|
||||
|
||||
readonly selectQuantity = this.select((s) => s.quantity);
|
||||
|
||||
readonly selectCustomPrice = this.select((s) => s.customPrice);
|
||||
|
||||
readonly selectCustomVat = this.select((s) => s.customVat);
|
||||
|
||||
readonly selectAvailabilities = this.select((s) => s.availabilities);
|
||||
|
||||
readonly selectAvailability = this.select((s) => s.availabilities[s.option]);
|
||||
@@ -266,6 +272,20 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
|
||||
};
|
||||
});
|
||||
|
||||
readonly setCustomPrice = this.updater((state, customPrice: number) => {
|
||||
return {
|
||||
...state,
|
||||
customPrice,
|
||||
};
|
||||
});
|
||||
|
||||
readonly setCustomVat = this.updater((state, customVat: VATType) => {
|
||||
return {
|
||||
...state,
|
||||
customVat,
|
||||
};
|
||||
});
|
||||
|
||||
readonly setAvailableOptions = this.updater((state, availableOptions: PurchasingOptions[]) => {
|
||||
let option = state.option;
|
||||
if (availableOptions?.length === 1 && availableOptions[0] === 'download') {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CheckoutDummyComponent } from './checkout-dummy/checkout-dummy.component';
|
||||
import { CheckoutReviewComponent } from './checkout-review/checkout-review.component';
|
||||
import { CheckoutSummaryComponent } from './checkout-summary/checkout-summary.component';
|
||||
import { PageCheckoutComponent } from './page-checkout.component';
|
||||
@@ -11,6 +12,7 @@ const routes: Routes = [
|
||||
children: [
|
||||
{ path: 'summary', component: CheckoutSummaryComponent },
|
||||
{ path: 'review', component: CheckoutReviewComponent },
|
||||
{ path: 'dummy', component: CheckoutDummyComponent },
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'review' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
|
||||
import { CheckoutDummyModule } from './checkout-dummy/checkout-dummy.module';
|
||||
import { CheckoutReviewModule } from './checkout-review/checkout-review.module';
|
||||
import { CheckoutSummaryModule } from './checkout-summary/checkout-summary.module';
|
||||
import { PageCheckoutModalsModule } from './page-checkout-modals.module';
|
||||
@@ -14,6 +15,7 @@ import { PageCheckoutComponent } from './page-checkout.component';
|
||||
CheckoutSummaryModule,
|
||||
PageCheckoutRoutingModule,
|
||||
CheckoutReviewModule,
|
||||
CheckoutDummyModule,
|
||||
ShellBreadcrumbModule,
|
||||
],
|
||||
declarations: [PageCheckoutComponent],
|
||||
|
||||
@@ -46,7 +46,9 @@
|
||||
<div class="orders">
|
||||
<ng-container *ngFor="let orderItem of order.items; let i = index">
|
||||
<ng-container
|
||||
*ngIf="i === 0 || order.items[i - 1].data.features.orderType !== orderItem.data.features.orderType"
|
||||
*ngIf="
|
||||
i === 0 || order.items[i - 1].data.features.orderType !== orderItem.data.features.orderType || !hasDummyItems(orderItem.data)
|
||||
"
|
||||
[ngSwitch]="orderItem.data.features.orderType"
|
||||
>
|
||||
<div *ngSwitchCase="'Rücklage'" class="order-category">
|
||||
@@ -54,8 +56,15 @@
|
||||
<h2>Rücklage</h2>
|
||||
</div>
|
||||
<div *ngSwitchCase="'Abholung'" class="order-category">
|
||||
<ui-icon class="icon" icon="box_out" size="18px"></ui-icon>
|
||||
<h2>Abholung</h2>
|
||||
<ng-container *ngIf="hasDummyItems(orderItem.data); else abholung">
|
||||
<div class="order-category-dummy">
|
||||
<h2>Manuelle Anlage / Dummy Bestellung</h2>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #abholung>
|
||||
<ui-icon class="icon" icon="box_out" size="18px"></ui-icon>
|
||||
<h2>Abholung</h2>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div #download *ngSwitchCase="'Download'" class="order-category">
|
||||
<ui-icon class="icon" icon="download" size="18px"></ui-icon>
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
@apply flex flex-col bg-white mx-10 mt-5 mb-5;
|
||||
}
|
||||
|
||||
.order-category-dummy {
|
||||
@apply flex flex-row items-center mt-5 ml-0;
|
||||
}
|
||||
|
||||
.order-category {
|
||||
@apply flex flex-row items-center mt-5;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
import { OrderDTO } from '@swagger/oms';
|
||||
import { EntityDTOContainerOfOrderItemDTO, OrderDTO, OrderItemDTO } from '@swagger/oms';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@@ -17,6 +17,7 @@ export class CustomerOrderDetailsComponent implements OnInit {
|
||||
customerId: number;
|
||||
orderId: number;
|
||||
order$: Observable<OrderDTO>;
|
||||
manuallyOrDummyOrderItems$: Observable<EntityDTOContainerOfOrderItemDTO[]>;
|
||||
branchName$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
@@ -47,4 +48,8 @@ export class CustomerOrderDetailsComponent implements OnInit {
|
||||
params: {},
|
||||
});
|
||||
}
|
||||
|
||||
hasDummyItems(orderItem: OrderItemDTO): boolean {
|
||||
return !!orderItem?.subsetItems?.find((subsetItem) => subsetItem?.data?.supplyChannel === 'MANUALLY');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,20 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="showUpdateComment$ | async">
|
||||
<hr />
|
||||
<div class="update-comment">
|
||||
<div class="update-comment-heading">
|
||||
Update-Notiz
|
||||
</div>
|
||||
|
||||
<ui-icon icon="refresh" size="19px"></ui-icon>
|
||||
<div class="grow">
|
||||
{{ info.updateComment }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="info-body">
|
||||
@@ -80,17 +94,6 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="showUpdateComment$ | async">
|
||||
<hr *ngIf="info?.attachments === 0 || info?.articles?.length === 0" />
|
||||
<div class="update-comment">
|
||||
<ui-icon icon="refresh" size="19px"></ui-icon>
|
||||
<div class="grow">
|
||||
{{ info.updateComment }}
|
||||
</div>
|
||||
</div>
|
||||
<hr *ngIf="!(showNotes$ | async)" />
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="showNotes$ | async">
|
||||
<hr />
|
||||
<div class="notes">
|
||||
|
||||
@@ -111,10 +111,14 @@ hr {
|
||||
}
|
||||
|
||||
.update-comment {
|
||||
@apply flex flex-row items-center text-lg;
|
||||
@apply flex flex-row items-center;
|
||||
|
||||
.update-comment-heading {
|
||||
@apply flex flex-row items-center text-lg font-bold;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply mr-3 text-cool-grey;
|
||||
@apply mx-3 text-cool-grey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,8 +113,10 @@ export class TaskInfoComponent implements OnChanges {
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
showUpdateComment$ = combineLatest([this.processingStatus$, this.info$]).pipe(
|
||||
map(([processingStatus, info]) => info.updateComment && processingStatus.includes('Removed') && !!info.successor?.id)
|
||||
showUpdateComment$ = combineLatest([this.info$, this.processingStatus$]).pipe(
|
||||
map(
|
||||
([info, processingStatus]) => !!info.updateComment && ((info.successor && processingStatus.includes('Removed')) || info.predecessor)
|
||||
)
|
||||
);
|
||||
|
||||
@HostBinding('class')
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DisplayInfoDTO } from '@swagger/eis';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { first, map, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-task-list',
|
||||
@@ -56,13 +56,21 @@ export class TaskListComponent {
|
||||
return false;
|
||||
});
|
||||
}),
|
||||
withLatestFrom(this.selected$),
|
||||
map(([list, date]) =>
|
||||
list.filter((item) =>
|
||||
!!item?.successor
|
||||
? this.dateAdapter.equals({ first: new Date(item.publicationDate || item.taskDate), second: date, precision: 'day' })
|
||||
: item
|
||||
)
|
||||
),
|
||||
map((list) => list.sort(this.moveRemovedToEnd.bind(this)))
|
||||
);
|
||||
|
||||
ongoingItems$ = combineLatest([this.items$, this.selected$]).pipe(
|
||||
map(([items, selectedDate]) => {
|
||||
map(([items, selectedDate]) =>
|
||||
// Filter Aufgaben die vor dem aktuellen Tag gestartet sind und nicht überfällig oder abgeschlossen sind
|
||||
const list = items.filter((item) => {
|
||||
items.filter((item) => {
|
||||
const type = this.domainTaskCalendarService.getInfoType(item);
|
||||
const processingStatus = this.domainTaskCalendarService.getProcessingStatusList(item);
|
||||
if (
|
||||
@@ -76,15 +84,34 @@ export class TaskListComponent {
|
||||
return new Date(item.publicationDate || item.taskDate) < this.today;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed']);
|
||||
})
|
||||
})
|
||||
),
|
||||
withLatestFrom(this.selected$),
|
||||
map(([list, date]) =>
|
||||
list.filter((item) =>
|
||||
!!item?.successor
|
||||
? this.dateAdapter.equals({ first: new Date(item.publicationDate || item.taskDate), second: date, precision: 'day' })
|
||||
: item
|
||||
)
|
||||
),
|
||||
map((list) => this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])),
|
||||
map((list) => list.sort(this.moveRemovedToEnd.bind(this)))
|
||||
);
|
||||
|
||||
selectedItems$ = combineLatest([this.items$, this.selected$]).pipe(
|
||||
map(([items, date]) =>
|
||||
items.filter((item) =>
|
||||
this.dateAdapter.equals({ first: this.domainTaskCalendarService.getDisplayInfoDate(item), second: date, precision: 'day' })
|
||||
this.dateAdapter.equals({
|
||||
first: this.domainTaskCalendarService.getDisplayInfoDate(item),
|
||||
second: date,
|
||||
precision: 'day',
|
||||
})
|
||||
)
|
||||
),
|
||||
withLatestFrom(this.selected$),
|
||||
map(([list, date]) =>
|
||||
list.filter((item) =>
|
||||
!!item?.successor ? this.dateAdapter.equals({ first: new Date(item.publicationDate), second: date, precision: 'day' }) : item
|
||||
)
|
||||
),
|
||||
// Sortierung der aufgaben nach Rot => Gelb => Grau => Grün
|
||||
|
||||
@@ -16,18 +16,12 @@
|
||||
<ui-icon icon="dashboard" size="26px"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
<!-- <a class="align-right nav-link" (click)="goToDashboard()" *ngIf="module === 0">
|
||||
<lib-icon *ngIf="router.url !== '/dashboard'" width="25px" height="26px" name="Infoboard_inactive"></lib-icon>
|
||||
<lib-icon *ngIf="router.url === '/dashboard'" width="25px" height="26px" name="Infoboard"></lib-icon>
|
||||
</a>
|
||||
<a class="align-right" *ngIf="module === 1">
|
||||
<lib-icon width="25px" height="26px" name="Infoboard-branch"></lib-icon>
|
||||
</a> -->
|
||||
<div class="align-center">
|
||||
<button
|
||||
class="header-icon notification"
|
||||
type="button"
|
||||
[class.active]="notificationCount$ | async"
|
||||
[disabled]="(notificationCount$ | async) === 0"
|
||||
(click)="openNotifications()"
|
||||
>
|
||||
<div class="notification-counter" *ngIf="notificationCount$ | async; let count">{{ count }}</div>
|
||||
|
||||
@@ -104,6 +104,9 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
||||
this._modal.open({
|
||||
content: ModalNotificationsComponent,
|
||||
data: notifications,
|
||||
config: {
|
||||
showScrollbarY: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
</div>
|
||||
<div class="actions">
|
||||
<ng-container *ngIf="!loading; else spinner">
|
||||
<app-button (action)="productNotFound()" [outline]="true" class="cta-not-found">
|
||||
Artikel nicht gefunden
|
||||
</app-button>
|
||||
<app-button [disabled]="quantityControl.invalid" (action)="addProduct()" primaryBorders="true" primary="true"
|
||||
>Exemplare remittieren</app-button
|
||||
>
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.cta-not-found {
|
||||
@apply mr-7;
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
font-family: 'Open Sans';
|
||||
line-height: 21px;
|
||||
|
||||
@@ -71,6 +71,15 @@ export class RemissionAddProductToShippingDocumentPartiallyDialogComponent imple
|
||||
});
|
||||
}
|
||||
|
||||
productNotFound() {
|
||||
this.loading = true;
|
||||
this.add.emit({
|
||||
quantity: 0,
|
||||
placementType: undefined,
|
||||
predefinedRemissionQuantity: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
placementUpdated(placementType: RemissionPlacementType) {
|
||||
this.placementType = placementType;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<div class="card-wrapper">
|
||||
<app-remission-list-card-header [product]="product"></app-remission-list-card-header>
|
||||
|
||||
<div class="restmenge" *ngIf="product.isResidual">Restmenge</div>
|
||||
<div class="restmenge" *ngIf="product.isResidual || product.impediment?.comment">
|
||||
{{ product.impediment?.comment ? product.impediment?.comment : 'Restmenge' }}
|
||||
<ng-container *ngIf="product.impediment?.attempts"> ({{ product.impediment?.attempts }}) </ng-container>
|
||||
</div>
|
||||
|
||||
<div class="card-details">
|
||||
<div class="icon">
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</div>
|
||||
<div class="image" *ngIf="pageStatus === 0">
|
||||
<ng-container *ngIf="emptyOrInvalidBarcode">
|
||||
<app-barcode-scanner *ngIf="isSafari" (scan)="handleScanResult($event)"></app-barcode-scanner>
|
||||
<lib-remission-container-scanner *ngIf="isNative" #scanner (scan)="handleScanResult($event)"></lib-remission-container-scanner>
|
||||
<!-- <app-barcode-scanner *ngIf="isSafari" (scan)="handleScanResult($event)"></app-barcode-scanner> -->
|
||||
<lib-remission-container-scanner *ngIf="isNative" (scan)="handleScanResult($event)"></lib-remission-container-scanner>
|
||||
<ui-searchbox *ngIf="isDesktop" placeholder="Wannennummer scannen" (search)="handleScanResult($event)"></ui-searchbox>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -36,6 +36,7 @@ import { UiSearchboxNextComponent } from '@ui/searchbox';
|
||||
import { isResponseArgs } from '@utils/object';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ResponseArgs } from '@swagger/cat';
|
||||
import { RemissionContainerScannerScanditComponent } from 'shared/public_api';
|
||||
|
||||
@Component({
|
||||
selector: 'app-remission-finish',
|
||||
@@ -107,6 +108,9 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(UiSearchboxNextComponent, { static: false })
|
||||
searchbox: UiSearchboxNextComponent;
|
||||
|
||||
@ViewChild(RemissionContainerScannerScanditComponent, { static: false })
|
||||
scanner: RemissionContainerScannerScanditComponent;
|
||||
|
||||
constructor(
|
||||
private store: Store,
|
||||
private router: Router,
|
||||
@@ -273,6 +277,10 @@ export class RemissionFinishComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
|
||||
if (this.scanner) {
|
||||
this.scanner.code = '';
|
||||
}
|
||||
|
||||
if (result.data) {
|
||||
this.completeScan(barcode);
|
||||
} else {
|
||||
|
||||
@@ -205,4 +205,5 @@
|
||||
<g id="dashboard" transform="matrix(1.31094,0,0,1.31094,-0.207962,-1.09593)">
|
||||
<path d="M23.017,15.072L19.649,15.072C19.649,15.072 19.649,1.924 19.649,1.924C19.649,1.315 19.188,0.836 18.638,0.836L1.71,0.836C1.161,0.836 0.7,1.315 0.7,1.924L0.7,21.301C0.702,23.487 2.316,25.244 4.288,25.246L20.83,25.246C21.684,25.245 22.503,24.87 23.106,24.199C23.698,23.539 24.03,22.647 24.027,21.717C24.027,21.718 24.027,16.159 24.027,16.159C24.027,15.55 23.566,15.072 23.017,15.072ZM4.289,23.055C3.419,23.054 2.729,22.262 2.72,21.299C2.72,21.299 2.72,3.011 2.72,3.011C2.72,3.011 17.628,3.011 17.628,3.011C17.628,3.011 17.628,21.718 17.628,21.718C17.628,22.179 17.711,22.633 17.871,23.055C17.871,23.055 4.289,23.055 4.289,23.055L4.289,23.055ZM22.007,21.71C21.973,22.414 21.464,22.979 20.828,22.979C20.194,22.979 19.685,22.417 19.649,21.716C19.649,21.71 19.649,17.246 19.649,17.246C19.649,17.246 22.007,17.246 22.007,17.246L22.007,21.71ZM15.174,17.136L15.174,17.135C15.178,16.848 15.076,16.572 14.894,16.368C14.702,16.151 14.438,16.032 14.164,16.032C14.164,16.032 5.311,16.032 5.311,16.032C4.762,16.032 4.301,16.511 4.301,17.12C4.301,17.728 4.762,18.207 5.311,18.207L14.164,18.207C14.708,18.207 15.166,17.737 15.174,17.136ZM15.174,12.013L15.174,12C15.174,11.718 15.071,11.448 14.893,11.248C14.7,11.033 14.437,10.914 14.164,10.914C14.164,10.914 5.311,10.914 5.311,10.914C4.762,10.914 4.301,11.393 4.301,12.002C4.301,12.61 4.762,13.089 5.311,13.089C5.311,13.089 14.164,13.089 14.164,13.089C14.71,13.089 15.169,12.616 15.174,12.013ZM14.833,12.268C14.83,12.276 14.827,12.284 14.824,12.292C14.842,12.297 14.857,12.299 14.865,12.301L14.833,12.268ZM14.835,12.261L14.835,12.262L14.842,12.269L14.835,12.261ZM15.174,6.884C15.174,6.275 14.713,5.797 14.164,5.797C14.164,5.797 5.311,5.797 5.311,5.797C4.762,5.797 4.301,6.275 4.301,6.884C4.301,7.493 4.762,7.971 5.311,7.971L14.164,7.971C14.713,7.971 15.174,7.493 15.174,6.884Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<path id="checked" d="M25.151,8.013C24.854,8.055 24.58,8.197 24.374,8.415C19.929,12.869 16.602,16.545 12.361,20.844L7.533,16.766C7.169,16.457 6.667,16.366 6.218,16.527C5.768,16.689 5.44,17.079 5.356,17.549C5.272,18.019 5.447,18.498 5.813,18.805L11.584,23.688C12.115,24.134 12.899,24.098 13.387,23.605C18.15,18.832 21.553,15.005 26.26,10.288C26.674,9.887 26.782,9.265 26.53,8.748C26.277,8.231 25.721,7.934 25.151,8.013Z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
15
apps/sales/src/assets/images/email_bookmark.svg
Normal file
15
apps/sales/src/assets/images/email_bookmark.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.00155,0,0,1.00155,0,0)">
|
||||
<g id="Tablet_ISA_Kunde_Warenausgabe_Detailseite_001_009_eingetroffen-Benachrichtigung_001" serif:id="Tablet_ISA_Kunde_Warenausgabe_Detailseite_001_009_eingetroffen+Benachrichtigung_001">
|
||||
<g id="Bookmark_Benachrichtigung_Mail-Copy">
|
||||
<g id="Group-3-Copy-2">
|
||||
<path id="Rectangle" d="M12.445,0L43.555,0C46.01,-0 48,1.99 48,4.445L48,4.474L8,4.474L8,4.445C8,1.99 9.99,0 12.445,0Z" style="fill:rgb(31,70,108);"/>
|
||||
<path id="Rectangle1" serif:id="Rectangle" d="M4.445,0L42.781,0L42.781,43.481C42.781,45.936 40.791,47.926 38.336,47.926C37.644,47.926 36.961,47.764 36.342,47.454L23.376,40.948C22.121,40.318 20.643,40.319 19.388,40.949L6.44,47.451C4.246,48.552 1.574,47.667 0.473,45.473C0.162,44.854 -0,44.171 0,43.478L0,4.445C-0,1.99 1.99,0 4.445,0Z" style="fill:rgb(85,117,150);"/>
|
||||
</g>
|
||||
<path id="Mail" d="M30.886,11L31.059,11.008C32.1,11.097 32.919,11.935 32.994,12.969L33,13.125L33,27.022L32.992,27.194C32.903,28.236 32.064,29.057 31.032,29.133L30.876,29.138L11.115,29.138L10.942,29.13C9.901,29.041 9.081,28.202 9.006,27.168L9,27.011L9,13.115L9.008,12.942C9.097,11.9 9.935,11.081 10.969,11.006L11.125,11L30.886,11ZM11.103,14.827L11.117,27.034L30.882,27.02L30.881,14.84L21.668,22.612L21.56,22.69C21.391,22.796 21.197,22.852 20.999,22.852C20.792,22.852 20.59,22.79 20.417,22.676L20.316,22.601L11.103,14.827ZM29.656,13.103L12.359,13.115L20.999,20.409L29.656,13.103Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
13
apps/sales/src/assets/images/sms_bookmark.svg
Normal file
13
apps/sales/src/assets/images/sms_bookmark.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Tablet_ISA_Kunde_Warenausgabe_Detailseite_001_009_eingetroffen-Benachrichtigung_003" serif:id="Tablet_ISA_Kunde_Warenausgabe_Detailseite_001_009_eingetroffen+Benachrichtigung_003">
|
||||
<g id="Bookmark_Benachrichtigung_SMS-Copy-2">
|
||||
<g id="Group-3-Copy-2">
|
||||
<path id="Rectangle" d="M12.445,0L43.555,0C46.01,-0 48,1.99 48,4.445L48,4.474L8,4.474L8,4.445C8,1.99 9.99,0 12.445,0Z" style="fill:rgb(31,70,108);"/>
|
||||
<path id="Rectangle1" serif:id="Rectangle" d="M4.445,0L42.781,0L42.781,43.481C42.781,45.936 40.791,47.926 38.336,47.926C37.644,47.926 36.961,47.764 36.342,47.454L23.376,40.948C22.121,40.318 20.643,40.319 19.388,40.949L6.44,47.451C4.246,48.552 1.574,47.667 0.473,45.473C0.162,44.854 -0,44.171 0,43.478L0,4.445C-0,1.99 1.99,0 4.445,0Z" style="fill:rgb(85,117,150);"/>
|
||||
</g>
|
||||
<path id="Shape" d="M29.607,10.8C31.527,10.8 33.096,12.307 33.195,14.202L33.2,14.393L33.2,23.771C33.2,25.692 31.693,27.26 29.798,27.36L29.607,27.365L19.651,27.364L14.438,31.541C14.306,31.658 14.147,31.739 13.978,31.779L13.849,31.801L13.718,31.807C13.55,31.805 13.384,31.767 13.228,31.693C12.89,31.521 12.659,31.196 12.606,30.823L12.594,30.681L12.594,27.364L12.393,27.365C10.537,27.365 9.009,25.957 8.82,24.15L8.805,23.962L8.8,23.771L8.8,14.393C8.8,12.473 10.307,10.904 12.202,10.805L12.393,10.8L29.607,10.8ZM29.607,13.051L12.393,13.051C11.696,13.051 11.122,13.583 11.057,14.264L11.051,14.393L11.051,23.771C11.051,24.469 11.583,25.042 12.264,25.107L12.393,25.114L13.72,25.114C14.3,25.114 14.778,25.553 14.839,26.116L14.845,26.239L14.845,28.323L18.561,25.373C18.722,25.245 18.912,25.161 19.117,25.128L19.272,25.114L29.607,25.114C30.304,25.114 30.878,24.581 30.943,23.9L30.949,23.771L30.949,14.393C30.949,13.652 30.348,13.051 29.607,13.051Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -16,7 +16,7 @@
|
||||
|
||||
button,
|
||||
a {
|
||||
@apply outline-none cursor-pointer;
|
||||
@apply text-black outline-none cursor-pointer;
|
||||
}
|
||||
|
||||
button:disabled,
|
||||
|
||||
@@ -94,6 +94,10 @@
|
||||
<div class="value">{{ orderItem?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Benachrichtigung</div>
|
||||
<div class="value">{{ (notificationsChannel$ | async | notificationsChannel) || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
@apply flex flex-row my-1;
|
||||
|
||||
.label {
|
||||
width: 130px;
|
||||
width: 145px;
|
||||
}
|
||||
|
||||
.value {
|
||||
|
||||
@@ -4,8 +4,8 @@ import { DomainOmsService } from '@domain/oms';
|
||||
import { KeyValueDTOOfStringAndString, 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 { BehaviorSubject, combineLatest, of } from 'rxjs';
|
||||
import { catchError, filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { SharedGoodsInOutOrderDetailsComponent } from '../goods-in-out-order-details.component';
|
||||
|
||||
@Component({
|
||||
@@ -26,6 +26,19 @@ export class SharedGoodsInOutOrderDetailsHeaderComponent {
|
||||
|
||||
orderItem$ = this.host.orderItems$.pipe(map((orderItems) => orderItems[0]));
|
||||
|
||||
notificationsChannel$ = this.orderItem$.pipe(
|
||||
switchMap((oi) => {
|
||||
if (oi?.orderId) {
|
||||
return this.omsService.getNotifications(oi?.orderId).pipe(
|
||||
map((res) => res.selected),
|
||||
catchError(() => of(0))
|
||||
);
|
||||
}
|
||||
|
||||
return of({ selected: 0 });
|
||||
})
|
||||
);
|
||||
|
||||
changeDateLoader$ = new BehaviorSubject<boolean>(false);
|
||||
changeStatusLoader$ = new BehaviorSubject<boolean>(false);
|
||||
changeStatusDisabled$ = this.host.changeActionDisabled$;
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
<ng-container *ngIf="orderItem$ | async; let orderItem">
|
||||
<div class="goods-in-out-order-details-features">
|
||||
<img *ngIf="orderItem?.features?.prebooked" src="/assets/images/tag_icon_preorder.svg" [alt]="orderItem?.features?.prebooked" />
|
||||
<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 />
|
||||
{{ notificationsSent?.NOTIFICATION_EMAIL | date: 'dd.MM.yyyy | HH:mm' }} Uhr
|
||||
</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 />
|
||||
{{ notificationsSent?.NOTIFICATION_SMS | date: 'dd.MM.yyyy | HH:mm' }} Uhr
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="goods-in-out-order-details-item">
|
||||
<div class="goods-in-out-order-details-item-thumbnail">
|
||||
@@ -60,6 +76,10 @@
|
||||
<div class="label">Meldenummer</div>
|
||||
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Vormerker</div>
|
||||
<div class="value">{{ orderItem.isPrebooked ? 'Ja' : 'Nein' }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">MwSt</div>
|
||||
<div class="value">{{ orderItem.retailPrice?.vat?.inPercent }}%</div>
|
||||
|
||||
@@ -8,6 +8,12 @@ button {
|
||||
|
||||
.goods-in-out-order-details-features {
|
||||
@apply absolute grid grid-flow-col gap-2 -top-1 right-6;
|
||||
|
||||
img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-in-out-order-details-item {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { DomainOmsService, DomainReceiptService } from '@domain/oms';
|
||||
import { HistoryComponent } from '@modal/history';
|
||||
@@ -6,8 +6,8 @@ import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { OrderItemListItemDTO, ReceiptDTO, ReceiptType } from '@swagger/oms';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { combineLatest, NEVER } from 'rxjs';
|
||||
import { catchError, filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { SharedGoodsInOutOrderDetailsStore } from '../goods-in-out-order-details.store';
|
||||
|
||||
export interface SharedGoodsInOutOrderDetailsItemComponentState {
|
||||
@@ -25,7 +25,7 @@ export interface SharedGoodsInOutOrderDetailsItemComponentState {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<SharedGoodsInOutOrderDetailsItemComponentState>
|
||||
implements OnDestroy {
|
||||
implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
get orderItem() {
|
||||
return this.get((s) => s.orderItem);
|
||||
@@ -52,6 +52,15 @@ export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<Sh
|
||||
|
||||
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 })
|
||||
.pipe(catchError(() => NEVER))
|
||||
)
|
||||
);
|
||||
|
||||
canChangeQuantity$ = combineLatest([this.orderItem$, this._host.fetchPartial$]).pipe(
|
||||
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1)
|
||||
);
|
||||
@@ -123,6 +132,8 @@ export class SharedGoodsInOutOrderDetailsItemComponent extends ComponentStore<Sh
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
ngOnDestroy() {
|
||||
// Remove Prev OrderItem from selected list
|
||||
this._host.selectOrderItem(this.orderItem, false);
|
||||
|
||||
@@ -3,13 +3,13 @@ import { Component, ChangeDetectionStrategy, ContentChildren, QueryList } from '
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { CommandService } from '@core/command';
|
||||
import { OrderItemsContext } from '@domain/oms';
|
||||
import { SharedGoodsInOutOrderDetailsTagsComponent } from '@shared/goods-in-out';
|
||||
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { BehaviorSubject, combineLatest, merge, of, Subscription } from 'rxjs';
|
||||
import { first, switchMap } from 'rxjs/operators';
|
||||
import { SharedGoodsInOutOrderDetailsCoversComponent } from './goods-in-out-order-details-covers';
|
||||
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';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -17,6 +17,7 @@ import { UiCommonModule } from '@ui/common';
|
||||
import { UiSliderModule } from '@ui/slider';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -33,6 +34,7 @@ import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
UiSliderModule,
|
||||
UiSelectBulletModule,
|
||||
UiSpinnerModule,
|
||||
UiTooltipModule,
|
||||
],
|
||||
exports: [
|
||||
SharedGoodsInOutOrderDetailsComponent,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<ui-form-control label="Vorgang-ID" variant="inline" statusLabel="Nicht Änderbar">
|
||||
<input uiInput formControlName="orderId" />
|
||||
<input uiInput formControlName="orderNumber" />
|
||||
</ui-form-control>
|
||||
|
||||
<ui-form-control label="Bestelldatum" variant="inline" statusLabel="Nicht Änderbar">
|
||||
@@ -18,6 +18,8 @@
|
||||
<input uiInput formControlName="clientChannel" />
|
||||
</ui-form-control>
|
||||
|
||||
<shared-notification-channel-control formGroupName="notificationChannel"></shared-notification-channel-control>
|
||||
|
||||
<ui-form-control label="Kundennummer" variant="inline" statusLabel="Nicht Änderbar">
|
||||
<input uiInput formControlName="buyerNumber" />
|
||||
</ui-form-control>
|
||||
@@ -127,6 +129,10 @@
|
||||
<input class="ssc-text" uiInput formControlName="sscText" />
|
||||
</div>
|
||||
|
||||
<ui-form-control label="Vormerker" variant="inline" statusLabel="Nicht Änderbar">
|
||||
<input uiInput formControlName="isPrebooked" />
|
||||
</ui-form-control>
|
||||
|
||||
<ui-form-control label="MwSt" variant="inline">
|
||||
<ui-select formControlName="vat">
|
||||
<ui-select-option *ngFor="let vat of vats$ | async" [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
|
||||
|
||||
@@ -10,10 +10,12 @@ import {
|
||||
Output,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
import { OrderItemListItemDTO, StockStatusCodeDTO, VATDTO } from '@swagger/oms';
|
||||
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/notification-channel-control';
|
||||
import { NotificationChannel, OrderDTO, OrderItemListItemDTO, StockStatusCodeDTO, VATDTO } from '@swagger/oms';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { UiSelectOptionComponent } from '@ui/select';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { first, shareReplay } from 'rxjs/operators';
|
||||
@@ -36,12 +38,21 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
@Input()
|
||||
items: OrderItemListItemDTO[];
|
||||
|
||||
@Input()
|
||||
order: OrderDTO;
|
||||
|
||||
expanded: boolean[];
|
||||
|
||||
showTagsComponent: boolean[];
|
||||
|
||||
control: FormGroup;
|
||||
|
||||
notificationsGroup = new FormGroup({
|
||||
selected: new FormControl(1),
|
||||
email: new FormControl('', emailNotificationValidator),
|
||||
mobile: new FormControl('', mobileNotificationValidator),
|
||||
});
|
||||
|
||||
minDate = this.dateAdapter.addCalendarDays(new Date(), -1);
|
||||
|
||||
vats$: Observable<VATDTO[]> = this.omsService.getVATs().pipe(shareReplay());
|
||||
@@ -67,7 +78,8 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
private datePipe: DatePipe,
|
||||
private omsService: DomainOmsService,
|
||||
private dateAdapter: DateAdapter,
|
||||
private cdr: ChangeDetectorRef
|
||||
private cdr: ChangeDetectorRef,
|
||||
private _modal: UiModalService
|
||||
) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -75,24 +87,30 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
}
|
||||
|
||||
ngOnChanges({ items }: SimpleChanges) {
|
||||
if (items.currentValue) {
|
||||
if (items?.currentValue) {
|
||||
this.expanded = new Array(items.currentValue.length);
|
||||
this.expanded[0] = true;
|
||||
|
||||
this.showTagsComponent = items.currentValue?.map((item) => !!item.compartmentCode);
|
||||
this.initForm(items.currentValue);
|
||||
this.updateNotificationsGroup();
|
||||
}
|
||||
}
|
||||
|
||||
initForm(items: OrderItemListItemDTO[]) {
|
||||
const fb = this.fb;
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fb = this.fb;
|
||||
this.control = fb.group({
|
||||
orderId: fb.control({ value: items[0].orderId, disabled: true }),
|
||||
orderNumber: fb.control({ value: items[0].orderNumber, disabled: true }),
|
||||
orderDate: fb.control({ value: this.datePipe.transform(items[0].orderDate), disabled: true }),
|
||||
clientChannel: fb.control({ value: this.environmentChannelPipe.transform(items[0].clientChannel), disabled: true }),
|
||||
buyerNumber: fb.control({ value: items[0].buyerNumber, disabled: true }),
|
||||
items: fb.array([]),
|
||||
notificationChannel: this.notificationsGroup,
|
||||
});
|
||||
|
||||
items.forEach(async (item, index) => {
|
||||
@@ -129,6 +147,7 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
supplier: fb.control({ value: item.supplier, disabled: true }),
|
||||
ssc: fb.control(item.ssc, [Validators.required, validateSsc(statusCodes)]),
|
||||
sscText: fb.control({ value: '', disabled: true }),
|
||||
isPrebooked: fb.control({ value: item.isPrebooked ? 'Ja' : 'Nein', disabled: true }),
|
||||
vat: fb.control(item.vatType),
|
||||
specialComment: fb.control(item.specialComment),
|
||||
});
|
||||
@@ -148,6 +167,21 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
});
|
||||
}
|
||||
|
||||
async updateNotificationsGroup() {
|
||||
const control = this.control?.getRawValue();
|
||||
const orderId = control?.orderId;
|
||||
|
||||
try {
|
||||
if (orderId) {
|
||||
const notifications = await this.omsService.getNotifications(+orderId).toPromise();
|
||||
|
||||
this.notificationsGroup.reset(notifications);
|
||||
}
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim abrufen der Benachrichtigung' });
|
||||
}
|
||||
}
|
||||
|
||||
changeEstimatedDeliveryDate(date: Date, item: OrderItemListItemDTO) {
|
||||
if (!date) {
|
||||
return;
|
||||
@@ -170,44 +204,64 @@ export class SharedGoodsInOutOrderEditComponent implements OnChanges, OnDestroy
|
||||
try {
|
||||
const control = this.control.getRawValue();
|
||||
const orderId = control.orderId;
|
||||
|
||||
if (this.notificationsGroup.dirty) {
|
||||
try {
|
||||
await this.omsService.updateNotifications(orderId, this.notificationsGroup.getRawValue()).toPromise();
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim aktualisieren der Benachrichtigung' });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
for (const itemCtrl of control.items) {
|
||||
const orderItemId = itemCtrl.orderItemId;
|
||||
const orderItemSubsetId = itemCtrl.orderItemSubsetId;
|
||||
await this.omsService
|
||||
.patchOrderItem({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItem: {
|
||||
product: { ean: itemCtrl.ean },
|
||||
grossPrice: {
|
||||
vat: { vatType: itemCtrl.vat },
|
||||
value: { value: Number(String(itemCtrl.price).replace(',', '.')) },
|
||||
try {
|
||||
await this.omsService
|
||||
.patchOrderItem({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItem: {
|
||||
product: { ean: itemCtrl.ean },
|
||||
grossPrice: {
|
||||
vat: { vatType: itemCtrl.vat },
|
||||
value: { value: Number(String(itemCtrl.price).replace(',', '.')) },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim aktualisieren des Bestellpostens' });
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.omsService
|
||||
.patchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
compartmentCode:
|
||||
itemCtrl.compartmentInfo && itemCtrl.compartmentCode
|
||||
? itemCtrl.compartmentCode.replace('_' + itemCtrl.compartmentInfo, '')
|
||||
: itemCtrl.compartmentCode,
|
||||
compartmentInfo: itemCtrl.compartmentInfo || '',
|
||||
estimatedShippingDate: itemCtrl.estimatedShippingDate || null,
|
||||
compartmentStop: itemCtrl.pickUpDeadline || null,
|
||||
specialComment: itemCtrl.specialComment || '',
|
||||
ssc: itemCtrl.ssc,
|
||||
sscText: itemCtrl.sscText !== '' ? itemCtrl.sscText.substring(3) : '',
|
||||
},
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
try {
|
||||
await this.omsService
|
||||
.patchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
compartmentCode:
|
||||
itemCtrl.compartmentInfo && itemCtrl.compartmentCode
|
||||
? itemCtrl.compartmentCode.replace('_' + itemCtrl.compartmentInfo, '')
|
||||
: itemCtrl.compartmentCode,
|
||||
compartmentInfo: itemCtrl.compartmentInfo || '',
|
||||
estimatedShippingDate: itemCtrl.estimatedShippingDate || null,
|
||||
compartmentStop: itemCtrl.pickUpDeadline || null,
|
||||
specialComment: itemCtrl.specialComment || '',
|
||||
ssc: itemCtrl.ssc,
|
||||
sscText: itemCtrl.sscText !== '' ? itemCtrl.sscText.substring(3) : '',
|
||||
},
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
} catch (error) {
|
||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim aktualisieren des Bestellpostens' });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
this.navigateBack();
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SharedGoodsInOutOrderDetailsModule } from '@shared/goods-in-out';
|
||||
import { NotificationChannelControlModule } from '@shared/notification-channel-control';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiDatepickerModule } from '@ui/datepicker';
|
||||
import { UiDropdownModule } from '@ui/dropdown';
|
||||
@@ -10,6 +10,7 @@ import { UiIconModule } from '@ui/icon';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { UiSelectModule } from '@ui/select';
|
||||
import { ProductImageModule } from 'apps/cdn/product-image/src/public-api';
|
||||
import { SharedGoodsInOutOrderDetailsModule } from '../goods-in-out-order-details';
|
||||
import { PipesModule } from '../pipes/pipes.module';
|
||||
import { SharedGoodsInOutOrderEditComponent } from './goods-in-out-order-edit.component';
|
||||
|
||||
@@ -28,6 +29,7 @@ import { SharedGoodsInOutOrderEditComponent } from './goods-in-out-order-edit.co
|
||||
UiDatepickerModule,
|
||||
UiDropdownModule,
|
||||
SharedGoodsInOutOrderDetailsModule,
|
||||
NotificationChannelControlModule,
|
||||
],
|
||||
exports: [SharedGoodsInOutOrderEditComponent],
|
||||
declarations: [SharedGoodsInOutOrderEditComponent],
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { NotificationChannel } from '@swagger/oms';
|
||||
|
||||
@Pipe({
|
||||
name: 'notificationsChannel',
|
||||
})
|
||||
export class NotificationsChannelPipe implements PipeTransform {
|
||||
static channels = new Map<NotificationChannel, string>([
|
||||
[1, 'E-Mail'],
|
||||
[2, 'SMS'],
|
||||
[4, 'Telefon'],
|
||||
[8, 'Fax'],
|
||||
[16, 'Brief'],
|
||||
]);
|
||||
|
||||
transform(value: NotificationChannel = 0): any {
|
||||
const result: string[] = [];
|
||||
|
||||
const channelKeys = Array.from(NotificationsChannelPipe.channels.keys());
|
||||
|
||||
channelKeys.forEach((key) => {
|
||||
if (value & key) {
|
||||
result.push(NotificationsChannelPipe.channels.get(key));
|
||||
}
|
||||
});
|
||||
|
||||
return result.join(' | ');
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { TitlePipe } from './title.pipe';
|
||||
import { ShowTagsPipe } from './show-tags.pipe';
|
||||
import { ShelfEditActionInProgressPipe } from './shelf-edit-action-in-progress.pipe';
|
||||
import { ShowCoverCompartmentCodePipe } from './show-cover-compartment-code.pipe';
|
||||
import { NotificationsChannelPipe } from './notifications-channel.pipe';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
@@ -22,6 +23,7 @@ import { ShowCoverCompartmentCodePipe } from './show-cover-compartment-code.pipe
|
||||
ShelfEditActionInProgressPipe,
|
||||
ShowCoverCompartmentCodePipe,
|
||||
ProcessingStatusOptionsPipe,
|
||||
NotificationsChannelPipe,
|
||||
],
|
||||
declarations: [
|
||||
ProcessingStatusPipe,
|
||||
@@ -34,6 +36,7 @@ import { ShowCoverCompartmentCodePipe } from './show-cover-compartment-code.pipe
|
||||
ShelfEditActionInProgressPipe,
|
||||
ShowCoverCompartmentCodePipe,
|
||||
ProcessingStatusOptionsPipe,
|
||||
NotificationsChannelPipe,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
|
||||
25
apps/shared/notification-channel-control/README.md
Normal file
25
apps/shared/notification-channel-control/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# NotificationChannelControl
|
||||
|
||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.4.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name --project notification-channel-control` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project notification-channel-control`.
|
||||
|
||||
> Note: Don't forget to add `--project notification-channel-control` or else it will be added to the default project in your `angular.json` file.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build notification-channel-control` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Publishing
|
||||
|
||||
After building your library with `ng build notification-channel-control`, go to the dist folder `cd dist/notification-channel-control` and run `npm publish`.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test notification-channel-control` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
32
apps/shared/notification-channel-control/karma.conf.js
Normal file
32
apps/shared/notification-channel-control/karma.conf.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../../../coverage/shared/notification-channel-control'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true,
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
7
apps/shared/notification-channel-control/ng-package.json
Normal file
7
apps/shared/notification-channel-control/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../../dist/shared/notification-channel-control",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
11
apps/shared/notification-channel-control/package.json
Normal file
11
apps/shared/notification-channel-control/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@shared/notification-channel-control",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^10.2.4",
|
||||
"@angular/core": "^10.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// start:ng42.barrel
|
||||
export * from './notification-channel-control.component';
|
||||
export * from './notification-channel-control.module';
|
||||
export * from './validators';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,40 @@
|
||||
<div class="nc-heading">
|
||||
<label for="notificationChannel">
|
||||
Benachrichtigung
|
||||
</label>
|
||||
<ui-checkbox-group
|
||||
[ngModel]="notificationChannels$ | async"
|
||||
(ngModelChange)="setNotificationChannels($event)"
|
||||
[disabled]="notificationChannelControl?.disabled"
|
||||
>
|
||||
<ui-checkbox [value]="1" design="filled">E-Mail</ui-checkbox>
|
||||
<ui-checkbox [value]="2" design="filled">SMS</ui-checkbox>
|
||||
</ui-checkbox-group>
|
||||
<div class="expand"></div>
|
||||
<button *ngIf="displayToggle" type="button" class="more-toggle" (click)="toggle()" [class.open]="open$ | async">
|
||||
<ui-icon icon="arrow_head" size="16px"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="nc-content" [class.open]="open$ | async">
|
||||
<div class="nc-control-wrapper" *ngIf="displayEmail">
|
||||
<label for="email">E-Mail</label>
|
||||
<div class="input-wrapper" [class.has-error]="emailControl.touched && emailControl?.errors">
|
||||
<input type="email" name="email" id="email" [formControl]="emailControl" placeholder="E-Mail*" />
|
||||
<ng-container *ngIf="emailControl.touched && emailControl?.errors; let errors">
|
||||
<span class="error" *ngIf="errors.required">Das Fehld E-Mail ist ein Pflichtfeld</span>
|
||||
<span class="error" *ngIf="errors.pattern">Keine gültige E-Mail Adresse</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nc-control-wrapper" *ngIf="displayMobile">
|
||||
<label for="mobile">SMS</label>
|
||||
<div class="input-wrapper" [class.has-error]="mobileControl.touched && mobileControl?.errors">
|
||||
<input type="tel" name="mobile" id="mobile" [formControl]="mobileControl" placeholder="SMS*" />
|
||||
<ng-container *ngIf="mobileControl.touched && mobileControl?.errors; let errors">
|
||||
<span class="error" *ngIf="errors.required">Das Fehld SMS ist ein Pflichtfeld</span>
|
||||
<span class="error" *ngIf="errors.pattern">Keine gültige Mobilnummer</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,90 @@
|
||||
:host {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
.nc-heading {
|
||||
@apply flex flex-row items-center px-5 py-6 bg-white;
|
||||
|
||||
label {
|
||||
width: 154px;
|
||||
}
|
||||
|
||||
.expand {
|
||||
@apply flex-grow;
|
||||
}
|
||||
|
||||
.more-toggle {
|
||||
@apply bg-transparent outline-none border-none;
|
||||
|
||||
ui-icon {
|
||||
@apply transition-all transform rotate-90;
|
||||
}
|
||||
|
||||
&.open {
|
||||
ui-icon {
|
||||
@apply -rotate-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nc-content {
|
||||
@apply h-0 overflow-hidden transition-all bg-white px-5 py-0;
|
||||
|
||||
&.open {
|
||||
@apply h-auto overflow-auto;
|
||||
}
|
||||
}
|
||||
|
||||
.nc-control-wrapper {
|
||||
@apply flex flex-row items-start pb-6;
|
||||
|
||||
label {
|
||||
@apply mt-1;
|
||||
width: 154px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
@apply flex flex-col flex-grow pb-1;
|
||||
|
||||
input {
|
||||
@apply flex-grow outline-none text-base font-bold border-0 border-b-2 border-solid pb-px-2;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
@apply bg-white;
|
||||
}
|
||||
|
||||
&.has-error input {
|
||||
@apply border-brand;
|
||||
}
|
||||
|
||||
.error {
|
||||
@apply text-right text-sm font-bold text-brand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .customer shared-notification-channel-control {
|
||||
.nc-control-wrapper {
|
||||
.input-wrapper {
|
||||
input {
|
||||
@apply border-glitter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .branch shared-notification-channel-control {
|
||||
.nc-control-wrapper {
|
||||
.input-wrapper {
|
||||
input {
|
||||
@apply border-munsell;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply text-white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { ControlContainer, FormGroup } from '@angular/forms';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { NotificationChannel } from '@swagger/oms';
|
||||
import { NEVER, Observable } from 'rxjs';
|
||||
import { map, startWith, tap } from 'rxjs/operators';
|
||||
|
||||
export interface NotificationChannelControlComponentState {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'shared-notification-channel-control',
|
||||
templateUrl: './notification-channel-control.component.html',
|
||||
styleUrls: ['./notification-channel-control.component.scss'],
|
||||
})
|
||||
export class NotificationChannelControlComponent extends ComponentStore<NotificationChannelControlComponentState> implements OnInit {
|
||||
notificationGroup: FormGroup;
|
||||
|
||||
get notificationChannelControl() {
|
||||
return this.notificationGroup.get('selected');
|
||||
}
|
||||
|
||||
get emailControl() {
|
||||
return this.notificationGroup.get('email');
|
||||
}
|
||||
|
||||
get displayEmail() {
|
||||
return !!(this.notificationChannelControl.value & 1) && this.emailControl;
|
||||
}
|
||||
|
||||
get mobileControl() {
|
||||
return this.notificationGroup.get('mobile');
|
||||
}
|
||||
|
||||
get displayMobile() {
|
||||
return !!(this.notificationChannelControl.value & 2) && this.mobileControl;
|
||||
}
|
||||
|
||||
get displayToggle() {
|
||||
return this.displayEmail || this.displayMobile;
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
return !!(this.mobileControl?.errors || this.emailControl?.errors);
|
||||
}
|
||||
|
||||
readonly open$ = this.select((s) => s.open);
|
||||
|
||||
readonly options: NotificationChannel[] = [1, 2];
|
||||
|
||||
get notificationChannels() {
|
||||
const value = this.notificationChannelControl?.value;
|
||||
|
||||
let values: NotificationChannel[] = [];
|
||||
|
||||
this.options?.forEach((option) => {
|
||||
if (value & option) {
|
||||
values.push(option);
|
||||
}
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
notificationChannels$: Observable<NotificationChannel[]>;
|
||||
|
||||
constructor(private _notificationsGroup: ControlContainer, private _cdr: ChangeDetectorRef) {
|
||||
super({
|
||||
open: false,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this._notificationsGroup.control instanceof FormGroup) {
|
||||
this.notificationGroup = this._notificationsGroup.control;
|
||||
}
|
||||
this.initNotificationChannels$();
|
||||
}
|
||||
|
||||
initNotificationChannels$() {
|
||||
if (this.notificationGroup) {
|
||||
this.notificationChannels$ = this.notificationChannelControl.valueChanges.pipe(startWith(this.notificationChannelControl.value)).pipe(
|
||||
map((value) => {
|
||||
let values = [];
|
||||
|
||||
this.options?.forEach((option) => {
|
||||
if (value & option) {
|
||||
values.push(option);
|
||||
}
|
||||
});
|
||||
return values;
|
||||
}),
|
||||
tap(() => {
|
||||
this.updateValidity();
|
||||
|
||||
if (this.hasError) {
|
||||
this.toggle(true);
|
||||
}
|
||||
|
||||
this._cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.notificationChannels$ = NEVER;
|
||||
}
|
||||
}
|
||||
|
||||
setNotificationChannels(notificationChannels: NotificationChannel[]) {
|
||||
const notificationChannel = notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel;
|
||||
this.notificationChannelControl.setValue(notificationChannel);
|
||||
this.notificationChannelControl.markAsDirty();
|
||||
}
|
||||
|
||||
toggle(value?: boolean) {
|
||||
this.patchState({ open: value ?? !this.get((s) => s.open) });
|
||||
}
|
||||
|
||||
updateValidity() {
|
||||
this.emailControl?.updateValueAndValidity();
|
||||
this.mobileControl?.updateValueAndValidity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiCheckboxModule } from '@ui/checkbox';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { NotificationChannelControlComponent } from './notification-channel-control.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [NotificationChannelControlComponent],
|
||||
imports: [CommonModule, UiCheckboxModule, FormsModule, ReactiveFormsModule, UiIconModule],
|
||||
exports: [NotificationChannelControlComponent],
|
||||
})
|
||||
export class NotificationChannelControlModule {}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { AbstractControl, ValidatorFn, Validators } from '@angular/forms';
|
||||
import { UiValidators } from '@ui/validators';
|
||||
|
||||
// RFC 5322 Official Standard
|
||||
const emailRegexPattern = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
|
||||
|
||||
export const emailNotificationValidator: ValidatorFn = (control: AbstractControl) => {
|
||||
if (control.parent) {
|
||||
const notificationChannel = control.parent.get('selected').value;
|
||||
if (notificationChannel & 1) {
|
||||
return Validators.required(control) ?? Validators.pattern(emailRegexPattern)(control);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const mobileNotificationValidator: ValidatorFn = (control: AbstractControl) => {
|
||||
if (control.parent) {
|
||||
const notificationChannel = control.parent.get('selected').value;
|
||||
if (notificationChannel & 2) {
|
||||
return Validators.required(control) ?? UiValidators.phone(control);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Public API Surface of notification-channel-control
|
||||
*/
|
||||
|
||||
export * from './lib';
|
||||
24
apps/shared/notification-channel-control/src/test.ts
Normal file
24
apps/shared/notification-channel-control/src/test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(
|
||||
path: string,
|
||||
deep?: boolean,
|
||||
filter?: RegExp
|
||||
): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
25
apps/shared/notification-channel-control/tsconfig.lib.json
Normal file
25
apps/shared/notification-channel-control/tsconfig.lib.json
Normal file
@@ -0,0 +1,25 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2018"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true,
|
||||
"strictMetadataEmit": true,
|
||||
"enableResourceInlining": true
|
||||
},
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"declarationMap": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": false
|
||||
}
|
||||
}
|
||||
17
apps/shared/notification-channel-control/tsconfig.spec.json
Normal file
17
apps/shared/notification-channel-control/tsconfig.spec.json
Normal file
@@ -0,0 +1,17 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
17
apps/shared/notification-channel-control/tslint.json
Normal file
17
apps/shared/notification-channel-control/tslint.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../../tslint.json",
|
||||
"rules": {
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"shared",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"shared",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export { TenantDTO } from './models/tenant-dto';
|
||||
export { ReadOnlyEntityDTOOfTenantDTOAndIReadOnlyTenant } from './models/read-only-entity-dtoof-tenant-dtoand-iread-only-tenant';
|
||||
export { EntityDTO } from './models/entity-dto';
|
||||
export { EntityStatus } from './models/entity-status';
|
||||
export { TouchedBase } from './models/touched-base';
|
||||
export { EntityDTOReferenceContainer } from './models/entity-dtoreference-container';
|
||||
export { ExternalReferenceDTO } from './models/external-reference-dto';
|
||||
export { ReadOnlyEntityDTOOfLabelDTOAndIReadOnlyLabel } from './models/read-only-entity-dtoof-label-dtoand-iread-only-label';
|
||||
|
||||
@@ -20,4 +20,5 @@ export interface AvailabilityDTO {
|
||||
supplierProductNumber?: string;
|
||||
supplierSSC?: string;
|
||||
supplierSSCText?: string;
|
||||
supplyChannel?: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* tslint:disable */
|
||||
import { TouchedBase } from './touched-base';
|
||||
import { EntityStatus } from './entity-status';
|
||||
export interface EntityDTO {
|
||||
export interface EntityDTO extends TouchedBase{
|
||||
changed?: string;
|
||||
created?: string;
|
||||
id?: number;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* tslint:disable */
|
||||
import { TouchedBase } from './touched-base';
|
||||
import { ExternalReferenceDTO } from './external-reference-dto';
|
||||
export interface EntityDTOReferenceContainer {
|
||||
export interface EntityDTOReferenceContainer extends TouchedBase{
|
||||
displayLabel?: string;
|
||||
enabled?: boolean;
|
||||
externalReference?: ExternalReferenceDTO;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* tslint:disable */
|
||||
import { TouchedBase } from './touched-base';
|
||||
import { EntityDTOReferenceContainer } from './entity-dtoreference-container';
|
||||
export interface EntityReferenceDTO {
|
||||
export interface EntityReferenceDTO extends TouchedBase{
|
||||
pId?: string;
|
||||
reference?: EntityDTOReferenceContainer;
|
||||
source?: number;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* tslint:disable */
|
||||
import { TouchedBase } from './touched-base';
|
||||
import { EntityStatus } from './entity-status';
|
||||
export interface ExternalReferenceDTO {
|
||||
export interface ExternalReferenceDTO extends TouchedBase{
|
||||
externalChanged?: string;
|
||||
externalCreated?: string;
|
||||
externalNumber?: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* tslint:disable */
|
||||
export interface GeoLocation {
|
||||
import { TouchedBase } from './touched-base';
|
||||
export interface GeoLocation extends TouchedBase{
|
||||
altitude?: number;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
|
||||
3
apps/swagger/checkout/src/lib/models/touched-base.ts
Normal file
3
apps/swagger/checkout/src/lib/models/touched-base.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
/* tslint:disable */
|
||||
export interface TouchedBase {
|
||||
}
|
||||
@@ -209,6 +209,13 @@ export { HistoryDTO } from './models/history-dto';
|
||||
export { DiffDTO } from './models/diff-dto';
|
||||
export { ResponseArgsOfOrderItemSubsetDTO } from './models/response-args-of-order-item-subset-dto';
|
||||
export { StatusValues } from './models/status-values';
|
||||
export { ListResponseArgsOfOrderItemSubsetTaskListItemDTO } from './models/list-response-args-of-order-item-subset-task-list-item-dto';
|
||||
export { ResponseArgsOfIEnumerableOfOrderItemSubsetTaskListItemDTO } from './models/response-args-of-ienumerable-of-order-item-subset-task-list-item-dto';
|
||||
export { OrderItemSubsetTaskListItemDTO } from './models/order-item-subset-task-list-item-dto';
|
||||
export { EntityDTOContainerOfOrderItemSubsetTransitionDTO } from './models/entity-dtocontainer-of-order-item-subset-transition-dto';
|
||||
export { OrderItemSubsetTransitionDTO } from './models/order-item-subset-transition-dto';
|
||||
export { ReadOnlyEntityDTOOfOrderItemSubsetTransitionDTOAndIOrderItemStatusTransition } from './models/read-only-entity-dtoof-order-item-subset-transition-dtoand-iorder-item-status-transition';
|
||||
export { OrderItemStatusValuesDTO } from './models/order-item-status-values-dto';
|
||||
export { ResponseArgsOfPayerDTO } from './models/response-args-of-payer-dto';
|
||||
export { ResponseArgsOfShippingAddressDTO } from './models/response-args-of-shipping-address-dto';
|
||||
export { ResponseArgsOfIEnumerableOfBranchDTO } from './models/response-args-of-ienumerable-of-branch-dto';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user