mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merge branch 'develop' into release/3.0
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AutocompleteTokenDTO, OrderService, QueryTokenDTO } from '@swagger/oms';
|
||||
import { memorize } from '@utils/common';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainCustomerOrderService {
|
||||
@@ -23,7 +25,11 @@ export class DomainCustomerOrderService {
|
||||
});
|
||||
}
|
||||
|
||||
@memorize()
|
||||
settings() {
|
||||
return this._orderService.OrderKundenbestellungenSettings();
|
||||
return this._orderService.OrderKundenbestellungenSettings().pipe(
|
||||
map((res) => res?.result),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
search = this.effect((options$: Observable<{ clear?: boolean }>) =>
|
||||
options$.pipe(
|
||||
tap((options) => {
|
||||
this.searchStarted.next();
|
||||
this.patchState({
|
||||
searchState: 'fetching',
|
||||
items: options.clear ? [] : this.items,
|
||||
|
||||
@@ -30,6 +30,10 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get showFilterClose$() {
|
||||
return this._environment.matchDesktop$.pipe(map((state) => !(state?.matches && this.leftOutlet === 'search')));
|
||||
}
|
||||
@@ -69,6 +73,21 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
ngOnInit() {
|
||||
this.fetching$ = this.articleSearch.fetching$;
|
||||
this.filter$ = this.articleSearch.filter$.pipe(map((filter) => Filter.create(filter)));
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this.articleSearch.searchStarted.pipe(takeUntil(this._onDestroy$)).subscribe(async (_) => {
|
||||
let queryParams = {
|
||||
...this.articleSearch.filter.getQueryParams(),
|
||||
...this.cleanupQueryParams(this.uiFilterComponent?.uiFilter?.getQueryParams()),
|
||||
};
|
||||
|
||||
// Always override query if not in tablet mode
|
||||
if (!!this.articleSearch.filter.getQueryParams()?.main_qs && !this.isTablet) {
|
||||
queryParams = { ...queryParams, main_qs: this.articleSearch.filter.getQueryParams()?.main_qs };
|
||||
}
|
||||
|
||||
await this.articleSearch.setDefaultFilter(queryParams);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -78,7 +97,6 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
|
||||
applyFilter(value: Filter) {
|
||||
this.uiFilterComponent?.cancelAutocomplete();
|
||||
this.articleSearch.setFilter(value);
|
||||
this.articleSearch.search({ clear: true });
|
||||
this.articleSearch.searchCompleted
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
@@ -110,4 +128,18 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
const queryParams = { main_qs: value?.getQueryParams()?.main_qs || '' };
|
||||
this.articleSearch.setDefaultFilter(queryParams);
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||
if (clean[key] == undefined) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,17 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this.subscriptions.add(
|
||||
this.searchService.searchStarted.subscribe(async (_) => {
|
||||
const queryParams = {
|
||||
...this.cleanupQueryParams(this.searchService.filter.getQueryParams()),
|
||||
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
|
||||
};
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { UiModalRef } from '@ui/modal';
|
||||
|
||||
@Component({
|
||||
@@ -10,13 +12,21 @@ import { UiModalRef } from '@ui/modal';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddedToCartModalComponent {
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
constructor(
|
||||
public ref: UiModalRef<any, any>,
|
||||
private readonly _router: Router,
|
||||
private readonly _applicationService: ApplicationService
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _navigation: ProductCatalogNavigationService,
|
||||
private readonly _environment: EnvironmentService
|
||||
) {}
|
||||
continue() {
|
||||
this._router.navigate([`/kunde/${this._applicationService.activatedProcessId}/product/search`]);
|
||||
async continue() {
|
||||
if (this.isTablet) {
|
||||
await this._navigation.navigateToProductSearch({ processId: this._applicationService.activatedProcessId });
|
||||
}
|
||||
this.ref.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -154,9 +154,10 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
const isSelected = this._articleSearchService.selectedItemIds.includes(this.item?.id);
|
||||
this._articleSearchService.setSelected({ selected: !isSelected, itemId: this.item?.id });
|
||||
|
||||
if (!this.isTablet) {
|
||||
this.selectedChange.emit(this.item);
|
||||
}
|
||||
// #4135 Code auskommentiert bis zur Klärung
|
||||
// if (!this.isTablet) {
|
||||
// this.selectedChange.emit(this.item);
|
||||
// }
|
||||
}
|
||||
|
||||
@HostBinding('style') get class() {
|
||||
|
||||
@@ -47,36 +47,40 @@
|
||||
</shared-order-by-filter>
|
||||
</div>
|
||||
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="product-list"
|
||||
[itemSize]="(mainOutletActive$ | async) ? 98 : 187"
|
||||
minBufferPx="935"
|
||||
maxBufferPx="1200"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
[class.page-search-results__result-item-main]="mainOutletActive$ | async"
|
||||
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[mainOutletActive]="mainOutletActive$ | async"
|
||||
></search-result-item>
|
||||
<page-search-result-item-loading
|
||||
[mainOutletActive]="mainOutletActive$ | async"
|
||||
*ngIf="fetching$ | async"
|
||||
></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<div *ngIf="isTablet" class="actions z-fixed">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
<div class="h-full relative">
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="product-list h-full"
|
||||
[itemSize]="(mainOutletActive$ | async) ? 98 : 187"
|
||||
minBufferPx="935"
|
||||
maxBufferPx="1200"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
[class.page-search-results__result-item-main]="mainOutletActive$ | async"
|
||||
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[mainOutletActive]="mainOutletActive$ | async"
|
||||
></search-result-item>
|
||||
<page-search-result-item-loading
|
||||
[mainOutletActive]="mainOutletActive$ | async"
|
||||
*ngIf="fetching$ | async"
|
||||
></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #4135 Code auskommentiert bis zur Klärung -->
|
||||
<!-- <div *ngIf="isTablet" class="actions z-fixed"> -->
|
||||
|
||||
@@ -30,9 +30,7 @@
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply fixed bottom-16 inline-grid grid-flow-col gap-7;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
@apply flex sticky bottom-10 items-center justify-center;
|
||||
|
||||
.cta-cart {
|
||||
@apply border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none self-end whitespace-nowrap no-underline;
|
||||
|
||||
@@ -91,6 +91,10 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
return this._environment.matchTablet$.pipe(map((state) => this._navigationService.mainOutletActive(this.route, state?.matches)));
|
||||
}
|
||||
|
||||
get rightOutletLocation() {
|
||||
return this._navigationService.getOutletLocations(this.route).right;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public searchService: ArticleSearchService,
|
||||
private route: ActivatedRoute,
|
||||
@@ -140,12 +144,15 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
if (this.rightOutletLocation !== 'filter') {
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
}
|
||||
const data = this.getCachedData(processId, queryParams, selectedBranch?.id);
|
||||
this.searchService.setItems(data.items);
|
||||
this.searchService.setHits(data.hits);
|
||||
|
||||
if (data.items?.length === 0) {
|
||||
if (data.items?.length > 0) {
|
||||
this.searchService.setItems(data.items);
|
||||
this.searchService.setHits(data.hits);
|
||||
}
|
||||
if (data.items?.length === 0 && this.rightOutletLocation !== 'filter') {
|
||||
this.search();
|
||||
} else {
|
||||
if (!this.isDesktop || this._navigationService.mainOutletActive(this.route)) {
|
||||
@@ -195,6 +202,17 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this.subscriptions.add(
|
||||
this.searchService.searchStarted.subscribe(async (_) => {
|
||||
const queryParams = {
|
||||
...this.cleanupQueryParams(this.searchService.filter.getQueryParams()),
|
||||
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
|
||||
};
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
@@ -226,7 +244,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
search(filter?: Filter) {
|
||||
if (!!filter) {
|
||||
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||
this.searchService.setFilter(filter);
|
||||
}
|
||||
|
||||
this.searchService.search({ clear: true });
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
<ng-container *ngIf="orderItem$ | async; let orderItem">
|
||||
<div class="grid grid-flow-row gap-px-2">
|
||||
<div class="bg-[#F5F7FA] flex flex-row justify-between items-center p-4 rounded-t">
|
||||
<div class="grid grid-flow-col gap-[0.4375rem] items-center" *ngIf="features$ | async; let features; else: featureLoading">
|
||||
<shared-icon *ngIf="features?.length > 0" [size]="24" icon="person"></shared-icon>
|
||||
<div class="grid grid-flow-col gap-2 items-center font-bold text-p2" *ngFor="let feature of features">
|
||||
{{ feature?.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
[disabled]="editButtonDisabled$ | async"
|
||||
class="page-customer-order-details-header__edit-cta bg-transparent text-brand font-bold border-none text-p1"
|
||||
*ngIf="editClick.observers.length"
|
||||
(click)="editClick.emit(orderItem)"
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-header__details bg-white px-4 pt-4 pb-5">
|
||||
<h2
|
||||
class="page-customer-order-details-header__details-header items-center"
|
||||
[class.mb-8]="!orderItem?.features?.paid && !isKulturpass"
|
||||
>
|
||||
<div class="text-h2">
|
||||
{{ orderItem?.organisation }}
|
||||
<ng-container *ngIf="!!orderItem?.organisation && (!!orderItem?.firstName || !!orderItem?.lastName)"> - </ng-container>
|
||||
{{ orderItem?.lastName }}
|
||||
{{ orderItem?.firstName }}
|
||||
</div>
|
||||
<div class="page-customer-order-details-header__header-compartment text-h3">
|
||||
{{ orderItem?.compartmentCode }}{{ orderItem?.compartmentInfo && '_' + orderItem?.compartmentInfo }}
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<div class="page-customer-order-details-header__paid-marker mt-[0.375rem]" *ngIf="orderItem?.features?.paid && !isKulturpass">
|
||||
<div class="font-bold w-fit desktop-small:text-p2 px-3 py-[0.125rem] rounded text-white bg-[#26830C]">
|
||||
{{ orderItem?.features?.paid }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-customer-order-details-header__paid-marker mt-[0.375rem] text-[#26830C]" *ngIf="isKulturpass">
|
||||
<svg class="fill-current mr-2" xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22">
|
||||
<path
|
||||
d="M880-740v520q0 24-18 42t-42 18H140q-24 0-42-18t-18-42v-520q0-24 18-42t42-18h680q24 0 42 18t18 42ZM140-631h680v-109H140v109Zm0 129v282h680v-282H140Zm0 282v-520 520Z"
|
||||
/>
|
||||
</svg>
|
||||
<strong> Bezahlt über KulturPass </strong>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-header__details-wrapper -mt-3">
|
||||
<div class="flex flex-row page-customer-order-details-header__buyer-number" data-detail-id="Kundennummer">
|
||||
<div class="w-[9rem]">Kundennummer</div>
|
||||
<div class="flex flex-row font-bold">{{ orderItem?.buyerNumber }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row page-customer-order-details-header__order-number" data-detail-id="VorgangId">
|
||||
<div class="w-[9rem]">Vorgang-ID</div>
|
||||
<div class="flex flex-row font-bold">{{ orderItem?.orderNumber }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row page-customer-order-details-header__order-date" data-detail-id="Bestelldatum">
|
||||
<div class="w-[9rem]">Bestelldatum</div>
|
||||
<div class="flex flex-row font-bold">{{ orderItem?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="flex flex-row page-customer-order-details-header__processing-status justify-between" data-detail-id="Status">
|
||||
<div class="w-[9rem]">Status</div>
|
||||
<div *ngIf="!(changeStatusLoader$ | async)" class="flex flex-row font-bold -mr-[0.125rem]">
|
||||
<shared-icon
|
||||
class="mr-2 text-black flex items-center justify-center"
|
||||
[size]="16"
|
||||
*ngIf="orderItem.processingStatus | processingStatus: 'icon'; let icon"
|
||||
[icon]="icon"
|
||||
></shared-icon>
|
||||
|
||||
<span *ngIf="!(canEditStatus$ | async)">
|
||||
{{ orderItem?.processingStatus | processingStatus }}
|
||||
</span>
|
||||
<ng-container *ngIf="canEditStatus$ | async">
|
||||
<button
|
||||
class="cta-status-dropdown"
|
||||
[uiOverlayTrigger]="statusDropdown"
|
||||
[disabled]="changeStatusDisabled$ | async"
|
||||
#dropdown="uiOverlayTrigger"
|
||||
>
|
||||
<div class="mr-[0.375rem]">
|
||||
{{ orderItem?.processingStatus | processingStatus }}
|
||||
</div>
|
||||
<shared-icon
|
||||
[size]="24"
|
||||
[class.rotate-0]="!dropdown.opened"
|
||||
[class.-rotate-180]="dropdown.opened"
|
||||
icon="arrow-drop-down"
|
||||
></shared-icon>
|
||||
</button>
|
||||
<ui-dropdown #statusDropdown yPosition="below" xPosition="after" [xOffset]="8">
|
||||
<button uiDropdownItem *ngFor="let action of statusActions$ | async" (click)="handleActionClick(action)">
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</ui-dropdown>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ui-spinner *ngIf="changeStatusLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"></ui-spinner>
|
||||
</div>
|
||||
<div class="flex flex-row page-customer-order-details-header__order-source" data-detail-id="Bestellkanal">
|
||||
<div class="w-[9rem]">Bestellkanal</div>
|
||||
<div class="flex flex-row font-bold">{{ order?.features?.orderSource }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-row page-customer-order-details-header__change-date justify-between"
|
||||
[ngSwitch]="orderItem.processingStatus"
|
||||
data-detail-id="Geaendert"
|
||||
>
|
||||
<!-- orderType 1 === Abholung / Rücklage; orderType 2 === Versand / B2B-Versand; orderType 4 === Download (ist manchmal auch orderType 2) -->
|
||||
<ng-container *ngIf="orderItem.orderType === 1; else changeDate">
|
||||
<ng-container *ngSwitchCase="16">
|
||||
<ng-container *ngTemplateOutlet="vslLieferdatum"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="8192">
|
||||
<ng-container *ngTemplateOutlet="vslLieferdatum"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="128">
|
||||
<ng-container *ngTemplateOutlet="abholfrist"></ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #changeDate>
|
||||
<div class="w-[9rem]">Geändert</div>
|
||||
<div class="flex flex-row font-bold">{{ orderItem?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row page-customer-order-details-header__pick-up justify-between"
|
||||
data-detail-id="Wunschdatum"
|
||||
*ngIf="orderItem.orderType === 1 && (orderItem.processingStatus === 16 || orderItem.processingStatus === 8192)"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="preferredPickUpDate"></ng-container>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col page-customer-order-details-header__dig-and-notification">
|
||||
<div
|
||||
*ngIf="orderItem.orderType === 1"
|
||||
class="flex flex-row page-customer-order-details-header__notification"
|
||||
data-detail-id="Benachrichtigung"
|
||||
>
|
||||
<div class="w-[9rem]">Benachrichtigung</div>
|
||||
<div class="flex flex-row font-bold">{{ (notificationsChannel | notificationsChannel) || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="!!digOrderNumber && orderItem.orderType !== 1"
|
||||
class="flex flex-row page-customer-order-details-header__dig-number"
|
||||
data-detail-id="Dig-Bestellnummer"
|
||||
>
|
||||
<div class="w-[9rem]">Dig-Bestell Nr.</div>
|
||||
<div class="flex flex-row font-bold">{{ digOrderNumber }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center relative bg-[#F5F7FA] p-4 rounded-t">
|
||||
<div *ngIf="showFeature" class="flex flex-row items-center mr-3">
|
||||
<ng-container [ngSwitch]="order.features.orderType">
|
||||
<ng-container *ngSwitchCase="'Versand'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-truck"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">Versand</p>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'DIG-Versand'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-truck"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">Versand</p>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'B2B-Versand'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-b2b-truck"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">B2B-Versand</p>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Abholung'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-box-out"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1 mr-3">Abholung</p>
|
||||
{{ orderItem.targetBranch }}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Rücklage'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-shopping-bag"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">Rücklage</p>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Download'">
|
||||
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
|
||||
<shared-icon [size]="24" icon="isa-download"></shared-icon>
|
||||
</div>
|
||||
<p class="font-bold text-p1">Download</p>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-header__additional-addresses" *ngIf="showAddresses">
|
||||
<button (click)="openAddresses = !openAddresses" class="text-[#0556B4]">
|
||||
Lieferadresse / Rechnungsadresse {{ openAddresses ? 'ausblenden' : 'anzeigen' }}
|
||||
</button>
|
||||
|
||||
<div class="page-customer-order-details-header__addresses-popover" *ngIf="openAddresses">
|
||||
<button (click)="openAddresses = !openAddresses" class="close">
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
|
||||
<div class="page-customer-order-details-header__addresses-popover-data">
|
||||
<div *ngIf="order.shipping" class="page-customer-order-details-header__addresses-popover-delivery">
|
||||
<p>Lieferadresse</p>
|
||||
<div class="page-customer-order-details-header__addresses-popover-delivery-data">
|
||||
<ng-container *ngIf="order.shipping?.data?.organisation">
|
||||
<p>{{ order.shipping?.data?.organisation?.name }}</p>
|
||||
<p>{{ order.shipping?.data?.organisation?.department }}</p>
|
||||
</ng-container>
|
||||
<p>{{ order.shipping?.data?.firstName }} {{ order.shipping?.data?.lastName }}</p>
|
||||
<p>{{ order.shipping?.data?.address?.street }} {{ order.shipping?.data?.address?.streetNumber }}</p>
|
||||
<p>{{ order.shipping?.data?.address?.zipCode }} {{ order.shipping?.data?.address?.city }}</p>
|
||||
<p>{{ order.shipping?.data?.address?.info }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="order.billing" class="page-customer-order-details-header__addresses-popover-billing">
|
||||
<p>Rechnungsadresse</p>
|
||||
<div class="page-customer-order-details-header__addresses-popover-billing-data">
|
||||
<ng-container *ngIf="order.billing?.data?.organisation">
|
||||
<p>{{ order.billing?.data?.organisation?.name }}</p>
|
||||
<p>{{ order.billing?.data?.organisation?.department }}</p>
|
||||
</ng-container>
|
||||
<p>{{ order.billing?.data?.firstName }} {{ order.billing?.data?.lastName }}</p>
|
||||
<p>{{ order.billing?.data?.address?.street }} {{ order.billing?.data?.address?.streetNumber }}</p>
|
||||
<p>{{ order.billing?.data?.address?.zipCode }} {{ order.billing?.data?.address?.city }}</p>
|
||||
<p>{{ order.billing?.data?.address?.info }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-details-header__select grow" *ngIf="showMultiselect$ | async">
|
||||
<button class="cta-select-all" (click)="selectAll()">Alle auswählen</button>
|
||||
{{ selectedOrderItemCount$ | async }} von {{ orderItemCount$ | async }} Titeln
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #featureLoading>
|
||||
<div class="fetch-wrapper">
|
||||
<div class="fetching"></div>
|
||||
<div class="fetching"></div>
|
||||
<div class="fetching"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #abholfrist>
|
||||
<div class="w-[9rem]">Abholfrist</div>
|
||||
<div *ngIf="!(changeDateLoader$ | async)" class="flex flex-row font-bold">
|
||||
<button
|
||||
[uiOverlayTrigger]="deadlineDatepicker"
|
||||
#deadlineDatepickerTrigger="uiOverlayTrigger"
|
||||
[disabled]="!isKulturpass && (!!orderItem?.features?.paid || (changeDateDisabled$ | async))"
|
||||
class="cta-pickup-deadline"
|
||||
>
|
||||
<strong class="border-r border-[#AEB7C1] pr-4">
|
||||
{{ orderItem?.pickUpDeadline | date: 'dd.MM.yy' }}
|
||||
</strong>
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
#deadlineDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[xOffset]="8"
|
||||
[min]="minDateDatepicker"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="orderItem?.pickUpDeadline"
|
||||
(save)="updatePickupDeadline($event)"
|
||||
>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
<ui-spinner *ngIf="changeDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"></ui-spinner>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #preferredPickUpDate>
|
||||
<div class="w-[9rem]">Zurücklegen bis</div>
|
||||
<div *ngIf="!(changePreferredDateLoader$ | async)" class="flex flex-row font-bold">
|
||||
<button
|
||||
[uiOverlayTrigger]="preferredPickUpDatePicker"
|
||||
#preferredPickUpDatePickerTrigger="uiOverlayTrigger"
|
||||
[disabled]="(!isKulturpass && !!orderItem?.features?.paid) || (changeDateDisabled$ | async)"
|
||||
class="cta-pickup-preferred"
|
||||
>
|
||||
<strong *ngIf="preferredPickUpDate$ | async; let pickUpDate; else: selectTemplate">
|
||||
{{ pickUpDate | date: 'dd.MM.yy' }}
|
||||
</strong>
|
||||
<ng-template #selectTemplate>
|
||||
<strong class="border-r border-[#AEB7C1] pr-4">Auswählen</strong>
|
||||
</ng-template>
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
#preferredPickUpDatePicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[xOffset]="8"
|
||||
[min]="minDateDatepicker"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="(preferredPickUpDate$ | async) || today"
|
||||
(save)="updatePreferredPickUpDate($event)"
|
||||
>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
<ui-spinner *ngIf="changePreferredDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"> </ui-spinner>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #vslLieferdatum>
|
||||
<div class="w-[9rem]">vsl. Lieferdatum</div>
|
||||
<div *ngIf="!(changeDateLoader$ | async)" class="flex flex-row font-bold">
|
||||
<button
|
||||
class="cta-datepicker"
|
||||
[disabled]="changeDateDisabled$ | async"
|
||||
[uiOverlayTrigger]="uiDatepicker"
|
||||
#datepicker="uiOverlayTrigger"
|
||||
>
|
||||
<span class="border-r border-[#AEB7C1] pr-4">
|
||||
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
|
||||
</span>
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
#uiDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[xOffset]="8"
|
||||
[min]="minDateDatepicker"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="orderItem?.estimatedShippingDate"
|
||||
(save)="updateEstimatedShippingDate($event)"
|
||||
>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
<ui-spinner *ngIf="changeDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"></ui-spinner>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,140 @@
|
||||
:host {
|
||||
@apply block mb-[0.125rem];
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__edit-cta {
|
||||
&:disabled {
|
||||
@apply text-inactive-customer;
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__paid-marker {
|
||||
@apply font-bold flex items-center justify-end;
|
||||
|
||||
shared-icon {
|
||||
@apply mr-2 text-white bg-green-600 w-px-25 h-px-25 flex items-center justify-center rounded-full;
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__details {
|
||||
@apply grid grid-flow-row pt-4;
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__details-header {
|
||||
@apply flex flex-row justify-between font-bold;
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__details-wrapper {
|
||||
@apply grid grid-flow-row gap-x-6 gap-y-[0.375rem];
|
||||
grid-template-columns: 50% auto;
|
||||
grid-template-areas:
|
||||
'buyernumber .'
|
||||
'ordernumber .'
|
||||
'orderdate processingstatus'
|
||||
'ordersource changedate'
|
||||
'dignotification pickup';
|
||||
|
||||
.detail {
|
||||
shared-icon {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__buyer-number {
|
||||
grid-area: buyernumber;
|
||||
}
|
||||
.page-customer-order-details-header__order-number {
|
||||
grid-area: ordernumber;
|
||||
}
|
||||
.page-customer-order-details-header__order-date {
|
||||
grid-area: orderdate;
|
||||
}
|
||||
.page-customer-order-details-header__processing-status {
|
||||
grid-area: processingstatus;
|
||||
}
|
||||
.page-customer-order-details-header__order-source {
|
||||
grid-area: ordersource;
|
||||
}
|
||||
.page-customer-order-details-header__change-date {
|
||||
grid-area: changedate;
|
||||
}
|
||||
.page-customer-order-details-header__pick-up {
|
||||
grid-area: pickup;
|
||||
}
|
||||
.page-customer-order-details-header__dig-and-notification {
|
||||
grid-area: dignotification;
|
||||
}
|
||||
|
||||
.cta-status-dropdown,
|
||||
.cta-datepicker,
|
||||
.cta-pickup-deadline,
|
||||
.cta-pickup-preferred {
|
||||
@apply flex flex-row border-none outline-none text-p2 font-bold bg-transparent items-center px-0 mx-0;
|
||||
|
||||
&:disabled {
|
||||
@apply text-disabled-customer cursor-not-allowed;
|
||||
|
||||
shared-icon {
|
||||
@apply text-disabled-customer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__select {
|
||||
@apply flex flex-col items-end;
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__additional-addresses {
|
||||
.page-customer-order-details-header__addresses-popover {
|
||||
@apply absolute inset-x-0 top-16 bottom-0 z-popover;
|
||||
|
||||
.close {
|
||||
@apply bg-white absolute right-0 p-6;
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-data {
|
||||
@apply flex flex-col bg-white p-6 z-popover min-h-[200px];
|
||||
box-shadow: 0px 6px 24px rgba(206, 212, 219, 0.8);
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-delivery {
|
||||
@apply grid mb-6;
|
||||
grid-template-columns: 153px auto;
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-delivery-data {
|
||||
p {
|
||||
@apply font-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-billing {
|
||||
@apply grid;
|
||||
grid-template-columns: 153px auto;
|
||||
|
||||
.page-customer-order-details-header__addresses-popover-billing-data {
|
||||
p {
|
||||
@apply font-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cta-select-all {
|
||||
@apply text-brand bg-transparent text-p2 font-bold outline-none border-none;
|
||||
}
|
||||
|
||||
.fetch-wrapper {
|
||||
@apply grid grid-flow-col gap-4;
|
||||
}
|
||||
|
||||
.fetching {
|
||||
@apply w-24 h-px-20 bg-customer;
|
||||
animation: load 0.75s linear infinite;
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Output,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
import { NotificationChannel } from '@swagger/checkout';
|
||||
import { KeyValueDTOOfStringAndString, OrderDTO, OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { CustomerOrderDetailsStore } from '../customer-order-details.store';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details-header',
|
||||
templateUrl: 'customer-order-details-header.component.html',
|
||||
styleUrls: ['customer-order-details-header.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderDetailsHeaderComponent implements OnChanges {
|
||||
@Output()
|
||||
editClick = new EventEmitter<OrderItemListItemDTO>();
|
||||
|
||||
@Output()
|
||||
handleAction = new EventEmitter<KeyValueDTOOfStringAndString>();
|
||||
|
||||
@Input()
|
||||
order: OrderDTO;
|
||||
|
||||
get isKulturpass() {
|
||||
return this.order?.features?.orderSource === 'KulturPass';
|
||||
}
|
||||
|
||||
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);
|
||||
today = this.dateAdapter.today();
|
||||
|
||||
selectedOrderItemCount$ = this._store.selectedeOrderItemSubsetIds$.pipe(map((ids) => ids?.length ?? 0));
|
||||
|
||||
orderItemCount$ = this._store.items$.pipe(map((items) => items?.length ?? 0));
|
||||
|
||||
orderItem$ = this._store.items$.pipe(map((orderItems) => orderItems?.find((_) => true)));
|
||||
|
||||
preferredPickUpDate$ = new BehaviorSubject<Date>(undefined);
|
||||
|
||||
notificationsChannel: NotificationChannel = 0;
|
||||
|
||||
changeDateLoader$ = new BehaviorSubject<boolean>(false);
|
||||
changePreferredDateLoader$ = new BehaviorSubject<boolean>(false);
|
||||
changeStatusLoader$ = new BehaviorSubject<boolean>(false);
|
||||
changeStatusDisabled$ = this._store.changeActionDisabled$;
|
||||
changeDateDisabled$ = this.changeStatusDisabled$;
|
||||
|
||||
features$ = this.orderItem$.pipe(
|
||||
filter((orderItem) => !!orderItem),
|
||||
switchMap((orderItem) =>
|
||||
this.customerService.getCustomers(orderItem.buyerNumber).pipe(
|
||||
map((res) => res.result.find((c) => c.customerNumber === orderItem.buyerNumber)),
|
||||
map((customer) => customer?.features || []),
|
||||
map((features) => features.filter((f) => f.enabled && !!f.description))
|
||||
)
|
||||
),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
statusActions$ = this.orderItem$.pipe(map((orderItem) => orderItem?.actions?.filter((action) => action.enabled === false)));
|
||||
|
||||
showMultiselect$ = combineLatest([this._store.items$, this._store.fetchPartial$, this._store.itemsSelectable$]).pipe(
|
||||
map(([orderItems, fetchPartial, multiSelect]) => multiSelect && fetchPartial && orderItems?.length > 1)
|
||||
);
|
||||
|
||||
crudaUpdate$ = this.orderItem$.pipe(map((orederItem) => !!(orederItem?.cruda & 4)));
|
||||
|
||||
editButtonDisabled$ = combineLatest([this.changeStatusLoader$, this.crudaUpdate$]).pipe(
|
||||
map(([changeStatusLoader, crudaUpdate]) => changeStatusLoader || !crudaUpdate)
|
||||
);
|
||||
|
||||
canEditStatus$ = combineLatest([this.statusActions$, this.crudaUpdate$]).pipe(
|
||||
map(([statusActions, crudaUpdate]) => statusActions?.length > 0 && crudaUpdate)
|
||||
);
|
||||
|
||||
openAddresses: boolean = false;
|
||||
|
||||
get digOrderNumber(): string {
|
||||
return this.order?.linkedRecords?.find((_) => true)?.number;
|
||||
}
|
||||
|
||||
get showAddresses(): boolean {
|
||||
return (this.order?.orderType === 2 || this.order?.orderType === 4) && (!!this.order?.shipping || !!this.order?.billing);
|
||||
}
|
||||
|
||||
get showFeature(): boolean {
|
||||
return !!this.order?.features && !!this.order?.features?.orderType;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _store: CustomerOrderDetailsStore,
|
||||
private customerService: CrmCustomerService,
|
||||
private dateAdapter: DateAdapter,
|
||||
private omsService: DomainOmsService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.order) {
|
||||
this.findLatestPreferredPickUpDate();
|
||||
this.computeNotificationChannel();
|
||||
}
|
||||
}
|
||||
|
||||
computeNotificationChannel() {
|
||||
const order = this.order;
|
||||
this.notificationsChannel = order?.notificationChannels ?? 0;
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
async updatePickupDeadline(deadline: Date) {
|
||||
this.changeDateLoader$.next(true);
|
||||
this.changeStatusDisabled$.next(true);
|
||||
const orderItems = cloneDeep(this._store.items);
|
||||
for (const item of orderItems) {
|
||||
if (this.dateAdapter.compareDate(deadline, new Date(item.pickUpDeadline)) !== 0) {
|
||||
try {
|
||||
const res = await this.omsService
|
||||
.setPickUpDeadline(item.orderId, item.orderItemId, item.orderItemSubsetId, deadline?.toISOString())
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
item.pickUpDeadline = deadline.toISOString();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.changeDateLoader$.next(false);
|
||||
this.changeStatusDisabled$.next(false);
|
||||
this._store.updateOrderItems(orderItems);
|
||||
}
|
||||
|
||||
async updateEstimatedShippingDate(estimatedShippingDate: Date) {
|
||||
this.changeDateLoader$.next(true);
|
||||
this.changeStatusDisabled$.next(true);
|
||||
const orderItems = cloneDeep(this._store.items);
|
||||
for (const item of orderItems) {
|
||||
if (this.dateAdapter.compareDate(estimatedShippingDate, new Date(item.pickUpDeadline)) !== 0) {
|
||||
try {
|
||||
const res = await this.omsService
|
||||
.setEstimatedShippingDate(item.orderId, item.orderItemId, item.orderItemSubsetId, estimatedShippingDate?.toISOString())
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
item.estimatedShippingDate = res.estimatedShippingDate;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.changeDateLoader$.next(false);
|
||||
this.changeStatusDisabled$.next(false);
|
||||
this._store.updateOrderItems(orderItems);
|
||||
}
|
||||
|
||||
async handleActionClick(action: KeyValueDTOOfStringAndString) {
|
||||
this.changeStatusLoader$.next(true);
|
||||
this.handleAction.emit(action);
|
||||
setTimeout(() => this.changeStatusLoader$.next(false), 1000);
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
this._store.selectAllOrderItems();
|
||||
}
|
||||
|
||||
async updatePreferredPickUpDate(date: Date) {
|
||||
this.changePreferredDateLoader$.next(true);
|
||||
this.changeStatusDisabled$.next(true);
|
||||
const orderItems = cloneDeep(this._store.items);
|
||||
|
||||
const data: Record<string, string> = {};
|
||||
orderItems.forEach((item) => {
|
||||
data[`${item.orderItemSubsetId}`] = date?.toISOString();
|
||||
});
|
||||
|
||||
try {
|
||||
await this.omsService.setPreferredPickUpDate({ data }).toPromise();
|
||||
this.order.items.forEach((item) => {
|
||||
item.data.subsetItems.forEach((subsetItem) => (subsetItem.data.preferredPickUpDate = date.toISOString()));
|
||||
});
|
||||
this.findLatestPreferredPickUpDate();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
this.changePreferredDateLoader$.next(false);
|
||||
this.changeStatusDisabled$.next(false);
|
||||
}
|
||||
|
||||
findLatestPreferredPickUpDate() {
|
||||
let latestDate;
|
||||
const subsetItems = this.order?.items
|
||||
?.reduce((acc, item) => {
|
||||
return [...acc, ...item.data.subsetItems];
|
||||
}, [])
|
||||
?.filter((a) => !!a.data.preferredPickUpDate);
|
||||
if (subsetItems?.length > 0) {
|
||||
latestDate = new Date(
|
||||
subsetItems?.reduce((a, b) => {
|
||||
return new Date(a.data.preferredPickUpDate) > new Date(b.data.preferredPickUpDate) ? a : b;
|
||||
})?.data?.preferredPickUpDate
|
||||
);
|
||||
}
|
||||
this.preferredPickUpDate$.next(latestDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './customer-order-details-header.component';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,232 @@
|
||||
<ng-container *ngIf="orderItem$ | async; let orderItem">
|
||||
<div #features class="page-customer-order-details-item__features">
|
||||
<ng-container *ngIf="orderItem?.features?.prebooked">
|
||||
<img [uiOverlayTrigger]="prebookedTooltip" src="/assets/images/tag_icon_preorder.svg" [alt]="orderItem?.features?.prebooked" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #prebookedTooltip [closeable]="true">
|
||||
Artikel wird für Sie vorgemerkt.
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="notificationsSent$ | async; let notificationsSent">
|
||||
<ng-container *ngIf="notificationsSent?.NOTIFICATION_EMAIL">
|
||||
<img [uiOverlayTrigger]="emailTooltip" src="/assets/images/email_bookmark.svg" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #emailTooltip [closeable]="true">
|
||||
Per E-Mail benachrichtigt <br />
|
||||
<ng-container *ngFor="let notification of notificationsSent?.NOTIFICATION_EMAIL">
|
||||
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
|
||||
</ng-container>
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="notificationsSent?.NOTIFICATION_SMS">
|
||||
<img [uiOverlayTrigger]="smsTooltip" src="/assets/images/sms_bookmark.svg" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #smsTooltip [closeable]="true">
|
||||
Per SMS benachrichtigt <br />
|
||||
<ng-container *ngFor="let notification of notificationsSent?.NOTIFICATION_SMS">
|
||||
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
|
||||
</ng-container>
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="page-customer-order-details-item__item-container">
|
||||
<div class="page-customer-order-details-item__thumbnail">
|
||||
<img [src]="orderItem.product?.ean | productImage" [alt]="orderItem.product?.name" />
|
||||
</div>
|
||||
<div class="page-customer-order-details-item__details">
|
||||
<div class="flex flex-row justify-between items-start mb-[1.3125rem]">
|
||||
<h3
|
||||
[uiElementDistance]="features"
|
||||
#elementDistance="uiElementDistance"
|
||||
[style.max-width.px]="elementDistance.distanceChange | async"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div class="font-normal mb-[0.375rem]">{{ orderItem.product?.contributors }}</div>
|
||||
<div>{{ orderItem.product?.name }}</div>
|
||||
</h3>
|
||||
<div class="history-wrapper flex flex-col items-end justify-center">
|
||||
<button class="cta-history text-p1" (click)="historyClick.emit(orderItem)">
|
||||
Historie
|
||||
</button>
|
||||
|
||||
<input
|
||||
*ngIf="selectable$ | async"
|
||||
[ngModel]="selected$ | async"
|
||||
(ngModelChange)="setSelected($event)"
|
||||
class="isa-select-bullet mt-4"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Menge</div>
|
||||
<div class="value">
|
||||
<ng-container *ngIf="!(canChangeQuantity$ | async)"> {{ orderItem?.quantity }}x </ng-container>
|
||||
<ui-quantity-dropdown
|
||||
*ngIf="canChangeQuantity$ | async"
|
||||
[showTrash]="false"
|
||||
[range]="orderItem?.quantity"
|
||||
[(ngModel)]="quantity"
|
||||
[showSpinner]="false"
|
||||
>
|
||||
</ui-quantity-dropdown>
|
||||
<span class="overall-quantity">(von {{ orderItem?.overallQuantity }})</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.product?.formatDetail">
|
||||
<div class="label">Format</div>
|
||||
<div class="value">
|
||||
<img
|
||||
*ngIf="orderItem?.product?.format && orderItem?.product?.format !== 'UNKNOWN'"
|
||||
class="format-icon"
|
||||
[src]="'/assets/images/Icon_' + orderItem.product?.format + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
<span>{{ orderItem.product?.formatDetail }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.product?.ean">
|
||||
<div class="label">ISBN/EAN</div>
|
||||
<div class="value">{{ orderItem.product?.ean }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.price">
|
||||
<div class="label">Preis</div>
|
||||
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.retailPrice?.vat?.inPercent">
|
||||
<div class="label">MwSt</div>
|
||||
<div class="value">{{ orderItem.retailPrice?.vat?.inPercent }}%</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
|
||||
<div class="detail" *ngIf="orderItem.supplier">
|
||||
<div class="label">Lieferant</div>
|
||||
<div class="value">{{ orderItem.supplier }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.ssc || !!orderItem.sscText">
|
||||
<div class="label">Meldenummer</div>
|
||||
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.targetBranch">
|
||||
<div class="label">Zielfiliale</div>
|
||||
<div class="value">{{ orderItem.targetBranch }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
orderItemFeature(orderItem) === 'Versand' ||
|
||||
orderItemFeature(orderItem) === 'B2B-Versand' ||
|
||||
orderItemFeature(orderItem) === 'DIG-Versand'
|
||||
"
|
||||
>{{ orderItem?.estimatedDelivery ? 'Lieferung zwischen' : 'Lieferung ab' }}</ng-container
|
||||
>
|
||||
<ng-container *ngIf="orderItemFeature(orderItem) === 'Abholung' || orderItemFeature(orderItem) === 'Rücklage'">
|
||||
Abholung ab
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!!orderItem?.estimatedDelivery || !!orderItem?.estimatedShippingDate">
|
||||
<div class="value bg-[#D8DFE5] rounded w-max px-2">
|
||||
<ng-container *ngIf="!!orderItem?.estimatedDelivery; else estimatedShippingDate">
|
||||
{{ orderItem?.estimatedDelivery?.start | date: 'dd.MM.yy' }} und
|
||||
{{ orderItem?.estimatedDelivery?.stop | date: 'dd.MM.yy' }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #estimatedShippingDate>
|
||||
<ng-container *ngIf="!!orderItem?.estimatedShippingDate">
|
||||
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
|
||||
<div class="detail" *ngIf="!!orderItem?.compartmentCode">
|
||||
<div class="label">Abholfachnr.</div>
|
||||
<div class="value">{{ orderItem?.compartmentCode }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Vormerker</div>
|
||||
<div class="value">{{ orderItem.isPrebooked ? 'Ja' : 'Nein' }}</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
|
||||
<div class="detail" *ngIf="!!orderItem.paymentProcessing">
|
||||
<div class="label">Zahlungsweg</div>
|
||||
<div class="value">{{ orderItem.paymentProcessing || '-' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.paymentType">
|
||||
<div class="label">Zahlungsart</div>
|
||||
<div class="value">{{ orderItem.paymentType | paymentType }}</div>
|
||||
</div>
|
||||
|
||||
<h4 class="receipt-header" *ngIf="receiptCount$ | async; let count">
|
||||
{{ count > 1 ? 'Belege' : 'Beleg' }}
|
||||
</h4>
|
||||
<ng-container *ngFor="let receipt of receipts$ | async">
|
||||
<div class="detail" *ngIf="!!receipt?.receiptNumber">
|
||||
<div class="label">Belegnummer</div>
|
||||
<div class="value">{{ receipt?.receiptNumber }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!receipt?.printedDate">
|
||||
<div class="label">Erstellt am</div>
|
||||
<div class="value">{{ receipt?.printedDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!receipt?.receiptText">
|
||||
<div class="label">Rechnungstext</div>
|
||||
<div class="value">{{ receipt?.receiptText || '-' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!receipt?.receiptType">
|
||||
<div class="label">Belegart</div>
|
||||
<div class="value">
|
||||
{{ receipt?.receiptType === 1 ? 'Lieferschein' : receipt?.receiptType === 64 ? 'Zahlungsbeleg' : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="page-customer-order-details-item__comment flex flex-col items-start mt-[1.625rem]">
|
||||
<div class="label mb-[0.375rem]">Anmerkung</div>
|
||||
|
||||
<div class="flex flex-row w-full">
|
||||
<textarea
|
||||
matInput
|
||||
cdkTextareaAutosize
|
||||
#autosize="cdkTextareaAutosize"
|
||||
cdkAutosizeMinRows="1"
|
||||
cdkAutosizeMaxRows="5"
|
||||
#specialCommentInput
|
||||
(keydown.delete)="triggerResize()"
|
||||
(keydown.backspace)="triggerResize()"
|
||||
type="text"
|
||||
name="comment"
|
||||
placeholder="Eine Anmerkung hinzufügen"
|
||||
[formControl]="specialCommentControl"
|
||||
[class.inactive]="!specialCommentControl.dirty"
|
||||
></textarea>
|
||||
|
||||
<div class="comment-actions">
|
||||
<button
|
||||
type="reset"
|
||||
class="clear"
|
||||
*ngIf="!!specialCommentControl.value?.length"
|
||||
(click)="specialCommentControl.setValue(''); saveSpecialComment(); triggerResize()"
|
||||
>
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
<button
|
||||
class="cta-save"
|
||||
type="submit"
|
||||
*ngIf="specialCommentControl?.enabled && specialCommentControl.dirty"
|
||||
(click)="saveSpecialComment()"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,123 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-0.5 relative;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply px-1;
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__features {
|
||||
@apply absolute grid grid-flow-col gap-2 -top-1 right-24;
|
||||
|
||||
img {
|
||||
@apply w-12 h-12;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__item-container {
|
||||
@apply grid gap-8 bg-white p-4;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__thumbnail {
|
||||
img {
|
||||
@apply rounded shadow-cta w-[3.625rem] h-[5.9375rem];
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__details {
|
||||
@apply relative;
|
||||
|
||||
h3 {
|
||||
@apply mt-0 font-bold;
|
||||
}
|
||||
|
||||
.detail {
|
||||
@apply grid gap-x-7 my-1;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
|
||||
.label {
|
||||
@apply w-[8.125rem];
|
||||
}
|
||||
|
||||
.value {
|
||||
@apply flex flex-row items-center font-bold;
|
||||
|
||||
.format-icon {
|
||||
@apply mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.overall-quantity {
|
||||
@apply ml-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-customer-order-details-item__comment {
|
||||
textarea {
|
||||
@apply w-full flex-grow rounded bg-[#EDEFF0] border-[#AEB7C1] border border-solid outline-none text-p2 p-4;
|
||||
resize: none;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
textarea.inactive {
|
||||
@apply text-warning font-bold;
|
||||
@apply w-full flex-grow rounded bg-[#EDEFF0] border-[#AEB7C1] border border-solid outline-none text-p2 p-4 text-warning font-bold;
|
||||
// ipad color fix
|
||||
-webkit-text-fill-color: rgb(190, 129, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
textarea::placeholder,
|
||||
textarea.inactive::placeholder {
|
||||
@apply text-[#89949E] font-normal;
|
||||
-webkit-text-fill-color: #89949e;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply flex-grow bg-transparent border-none outline-none text-p2 mx-4;
|
||||
}
|
||||
|
||||
input.inactive {
|
||||
@apply text-warning font-bold;
|
||||
@apply flex-grow bg-transparent border-none outline-none text-p2 mx-4 text-warning font-bold;
|
||||
// ipad color fix
|
||||
-webkit-text-fill-color: rgb(190, 129, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply bg-transparent text-brand font-bold text-p2 outline-none border-none;
|
||||
}
|
||||
|
||||
button.clear {
|
||||
@apply text-black;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
}
|
||||
|
||||
.history-wrapper {
|
||||
@apply text-right;
|
||||
}
|
||||
|
||||
.cta-history {
|
||||
@apply font-bold text-brand outline-none border-none bg-transparent -mr-1;
|
||||
}
|
||||
|
||||
.cta-edit,
|
||||
.cta-save {
|
||||
@apply -mr-1;
|
||||
}
|
||||
|
||||
ui-select-bullet {
|
||||
@apply absolute top-20 right-0 p-5;
|
||||
}
|
||||
|
||||
.receipt-header {
|
||||
@apply mb-1;
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ChangeDetectorRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { DomainOmsService, DomainReceiptService } from '@domain/oms';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { OrderDTO, OrderItemListItemDTO, ReceiptDTO, ReceiptType } from '@swagger/oms';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest, NEVER, Subject, Observable } from 'rxjs';
|
||||
import { catchError, filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { CustomerOrderDetailsStore } from '../customer-order-details.store';
|
||||
|
||||
export interface CustomerOrderDetailsItemComponentState {
|
||||
orderItem?: OrderItemListItemDTO;
|
||||
order?: OrderDTO;
|
||||
quantity?: number;
|
||||
receipts?: ReceiptDTO[];
|
||||
selected?: boolean;
|
||||
more: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details-item',
|
||||
templateUrl: 'customer-order-details-item.component.html',
|
||||
styleUrls: ['customer-order-details-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOrderDetailsItemComponentState> implements OnInit, OnDestroy {
|
||||
@ViewChild('autosize') autosize: CdkTextareaAutosize;
|
||||
|
||||
@Output()
|
||||
historyClick = new EventEmitter<OrderItemListItemDTO>();
|
||||
|
||||
@Output()
|
||||
specialCommentChanged = new EventEmitter<void>();
|
||||
|
||||
@Input()
|
||||
get orderItem() {
|
||||
return this.get((s) => s.orderItem);
|
||||
}
|
||||
set orderItem(orderItem: OrderItemListItemDTO) {
|
||||
if (!isEqual(this.orderItem, orderItem)) {
|
||||
// Remove Prev OrderItem from selected list
|
||||
this._store.selectOrderItem(this.orderItem, false);
|
||||
|
||||
this.patchState({ orderItem, quantity: orderItem?.quantity, receipts: [], more: false });
|
||||
this.specialCommentControl.reset(orderItem?.specialComment);
|
||||
|
||||
// Add New OrderItem to selected list if selected was set to true by its input
|
||||
if (this.get((s) => s.selected)) {
|
||||
this._store.selectOrderItem(this.orderItem, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
get order() {
|
||||
return this.get((s) => s.order);
|
||||
}
|
||||
set order(order: OrderDTO) {
|
||||
this.patchState({ order });
|
||||
}
|
||||
|
||||
readonly orderItem$ = this.select((s) => s.orderItem);
|
||||
|
||||
notificationsSent$ = this.orderItem$.pipe(
|
||||
filter((oi) => !!oi),
|
||||
switchMap((oi) =>
|
||||
this._omsService
|
||||
.getCompletedTasks({ orderId: oi.orderId, orderItemId: oi.orderItemId, orderItemSubsetId: oi.orderItemSubsetId, take: 4, skip: 0 })
|
||||
.pipe(catchError(() => NEVER))
|
||||
)
|
||||
);
|
||||
|
||||
canChangeQuantity$ = combineLatest([this.orderItem$, this._store.fetchPartial$]).pipe(
|
||||
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1)
|
||||
);
|
||||
|
||||
get quantity() {
|
||||
return this.get((s) => s.quantity);
|
||||
}
|
||||
set quantity(quantity: number) {
|
||||
if (this.quantity !== quantity) {
|
||||
this.patchState({ quantity });
|
||||
}
|
||||
}
|
||||
|
||||
readonly quantity$ = this.select((s) => s.quantity);
|
||||
|
||||
@Input()
|
||||
get selected() {
|
||||
return this._store.selectedeOrderItemSubsetIds.includes(this.orderItem?.orderItemSubsetId);
|
||||
}
|
||||
set selected(selected: boolean) {
|
||||
if (this.selected !== selected) {
|
||||
this.patchState({ selected });
|
||||
this._store.selectOrderItem(this.orderItem, selected);
|
||||
}
|
||||
}
|
||||
|
||||
readonly selected$ = combineLatest([this.orderItem$, this._store.selectedeOrderItemSubsetIds$]).pipe(
|
||||
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId))
|
||||
);
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<boolean>();
|
||||
|
||||
get selectable() {
|
||||
return this._store.itemsSelectable && this._store.items.length > 1 && this._store.fetchPartial;
|
||||
}
|
||||
|
||||
readonly selectable$ = combineLatest([this._store.items$, this._store.itemsSelectable$, this._store.fetchPartial$]).pipe(
|
||||
map(([orderItems, selectable, fetchPartial]) => orderItems.length > 1 && selectable && fetchPartial)
|
||||
);
|
||||
|
||||
get receipts() {
|
||||
return this.get((s) => s.receipts);
|
||||
}
|
||||
set receipts(receipts: ReceiptDTO[]) {
|
||||
if (!isEqual(this.receipts, receipts)) {
|
||||
this.patchState({ receipts });
|
||||
this._store.updateReceipts(receipts);
|
||||
}
|
||||
}
|
||||
|
||||
readonly receipts$ = this.select((s) => s.receipts);
|
||||
|
||||
readonly receiptCount$ = this.select((s) => s.receipts?.length);
|
||||
|
||||
specialCommentControl = new UntypedFormControl();
|
||||
|
||||
more$ = this.select((s) => s.more);
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
constructor(
|
||||
private _store: CustomerOrderDetailsStore,
|
||||
private _domainReceiptService: DomainReceiptService,
|
||||
private _omsService: DomainOmsService,
|
||||
private _cdr: ChangeDetectorRef
|
||||
) {
|
||||
super({
|
||||
more: false,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
ngOnDestroy() {
|
||||
// Remove Prev OrderItem from selected list
|
||||
this._store.selectOrderItem(this.orderItem, false);
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
loadReceipts = this.effect((done$: Observable<(receipts: ReceiptDTO[]) => void | undefined>) =>
|
||||
done$.pipe(
|
||||
withLatestFrom(this.orderItem$),
|
||||
filter(([_, orderItem]) => !!orderItem),
|
||||
switchMap(([done, orderItem]) =>
|
||||
this._domainReceiptService
|
||||
.getReceipts({
|
||||
receiptType: 65 as ReceiptType,
|
||||
ids: [orderItem.orderItemSubsetId],
|
||||
eagerLoading: 1,
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
const receipts = res.result.map((r) => r.item3?.data).filter((f) => !!f);
|
||||
this.receipts = receipts;
|
||||
|
||||
if (typeof done === 'function') {
|
||||
done?.(receipts);
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
if (typeof done === 'function') {
|
||||
done?.([]);
|
||||
}
|
||||
},
|
||||
() => {}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
async saveSpecialComment() {
|
||||
const { orderId, orderItemId, orderItemSubsetId } = this.orderItem;
|
||||
|
||||
try {
|
||||
this.specialCommentControl.reset(this.specialCommentControl.value);
|
||||
const res = await this._omsService
|
||||
.patchComment({ orderId, orderItemId, orderItemSubsetId, specialComment: this.specialCommentControl.value ?? '' })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
this.orderItem = { ...this.orderItem, specialComment: this.specialCommentControl.value ?? '' };
|
||||
this._store.updateOrderItems([this.orderItem]);
|
||||
this.specialCommentChanged.emit();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
setMore(more: boolean) {
|
||||
this.patchState({ more });
|
||||
if (more && this.receipts.length === 0) {
|
||||
this.loadReceipts(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
this.selected = selected;
|
||||
this.selectedChange.emit(selected);
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
orderItemFeature(orderItemListItem: OrderItemListItemDTO) {
|
||||
const orderItems = this.order?.items;
|
||||
return orderItems?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)?.data?.features?.orderType;
|
||||
}
|
||||
|
||||
triggerResize() {
|
||||
this.autosize.reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './customer-order-details-item.component';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="page-customer-order-details-tags__wrapper">
|
||||
<button
|
||||
class="page-customer-order-details-tags__tag"
|
||||
type="button"
|
||||
[class.selected]="tag === (selected$ | async) && !inputFocus.focused"
|
||||
*ngFor="let tag of defaultTags"
|
||||
(click)="setCompartmentInfo(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</button>
|
||||
<button
|
||||
(click)="inputFocus.focus()"
|
||||
type="button"
|
||||
class="page-customer-order-details-tags__tag"
|
||||
[class.selected]="(inputValue$ | async) === (selected$ | async) && (inputValue$ | async)"
|
||||
>
|
||||
<input
|
||||
#inputFocus="uiFocus"
|
||||
uiFocus
|
||||
type="text"
|
||||
[ngModel]="inputValue$ | async"
|
||||
(ngModelChange)="inputValueSubject.next($event); setCompartmentInfo(inputValue)"
|
||||
placeholder="..."
|
||||
[size]="controlSize$ | async"
|
||||
maxlength="15"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,40 @@
|
||||
:host {
|
||||
@apply block bg-white p-4;
|
||||
}
|
||||
|
||||
.page-customer-order-details-tags__wrapper {
|
||||
@apply grid grid-flow-col justify-center gap-2 w-auto mx-auto;
|
||||
}
|
||||
|
||||
.page-customer-order-details-tags__tag {
|
||||
@apply w-auto text-p2 border border-solid bg-white border-inactive-customer py-px-10 px-5 font-bold text-inactive-customer rounded-full;
|
||||
|
||||
&:focus:not(.selected) {
|
||||
@apply bg-white border-inactive-customer text-inactive-customer;
|
||||
}
|
||||
|
||||
&.selected,
|
||||
&:focus-within {
|
||||
@apply bg-inactive-customer text-white;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply border-none outline-none text-p2 font-bold w-auto text-center text-inactive-customer bg-transparent p-0 m-0;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
@apply text-inactive-customer;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
input:focus::placeholder {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
&.selected input:not(:focus) {
|
||||
@apply text-white;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, forwardRef } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details-tags',
|
||||
templateUrl: 'customer-order-details-tags.component.html',
|
||||
styleUrls: ['customer-order-details-tags.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomerOrderDetailsTagsComponent), multi: true }],
|
||||
})
|
||||
export class CustomerOrderDetailsTagsComponent implements OnInit, OnDestroy, ControlValueAccessor {
|
||||
selected$ = new BehaviorSubject<string>('');
|
||||
|
||||
readonly defaultTags = ['Maxi', 'Mini', 'Kleinkram', 'Nonbook', 'Kalender'];
|
||||
|
||||
inputValueSubject = new BehaviorSubject<string>('');
|
||||
|
||||
inputValue$ = this.inputValueSubject.asObservable();
|
||||
|
||||
disabled = false;
|
||||
|
||||
private onChange = (value: string) => {};
|
||||
|
||||
private onTouched = () => {};
|
||||
|
||||
get inputValue() {
|
||||
return this.inputValueSubject.value;
|
||||
}
|
||||
|
||||
controlSize$ = this.inputValue$.pipe(map((value) => (value ? Math.min(value?.length, 15) : 3)));
|
||||
|
||||
private subscription = new Subscription();
|
||||
|
||||
constructor(private _cdr: ChangeDetectorRef) {}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.selected$.next(obj);
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscription.add(
|
||||
this.selected$.pipe(map((selected) => (this.defaultTags.includes(selected) ? '' : selected))).subscribe(this.inputValueSubject)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.inputValueSubject.unsubscribe();
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
async setCompartmentInfo(compartmentInfo: string) {
|
||||
const currentCompartmentInfo = await this.selected$.pipe(first()).toPromise();
|
||||
if (currentCompartmentInfo === compartmentInfo) {
|
||||
this.onChange(undefined);
|
||||
this.selected$.next(undefined);
|
||||
} else {
|
||||
this.onChange(compartmentInfo);
|
||||
this.selected$.next(compartmentInfo);
|
||||
}
|
||||
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './customer-order-details-tags.component';
|
||||
// end:ng42.barrel
|
||||
@@ -1,11 +1,42 @@
|
||||
<shared-goods-in-out-order-details (actionHandled)="actionHandled($event)" [itemsSelectable]="hasActions$ | async" [fetchPartial]="true">
|
||||
<shared-goods-in-out-order-details-header [order]="order$ | async" (editClick)="navigateToEditPage($event)">
|
||||
</shared-goods-in-out-order-details-header>
|
||||
<shared-goods-in-out-order-details-item
|
||||
<div class="mb-4">
|
||||
<page-customer-order-details-header
|
||||
(editClick)="navigateToEditPage($event)"
|
||||
(handleAction)="handleAction($event)"
|
||||
[order]="order$ | async"
|
||||
></page-customer-order-details-header>
|
||||
<page-customer-order-details-item
|
||||
class="mb-px-2"
|
||||
*ngFor="let item of items$ | async"
|
||||
[orderItem]="item"
|
||||
[order]="order$ | async"
|
||||
[selected]="true"
|
||||
></shared-goods-in-out-order-details-item>
|
||||
<shared-goods-in-out-order-details-tags></shared-goods-in-out-order-details-tags>
|
||||
</shared-goods-in-out-order-details>
|
||||
(historyClick)="navigateToHistoryPage($event)"
|
||||
(specialCommentChanged)="updateCustomerOrderResults()"
|
||||
></page-customer-order-details-item>
|
||||
<page-customer-order-details-tags *ngIf="showTagsComponent$ | async"></page-customer-order-details-tags>
|
||||
</div>
|
||||
<div class="page-customer-order-details__action-wrapper">
|
||||
<button
|
||||
[disabled]="addToPreviousCompartmentActionDisabled$ | async"
|
||||
*ngIf="addToPreviousCompartmentAction$ | async; let action"
|
||||
class="cta-action shadow-action"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
[class.cta-action-secondary]="!action.selected"
|
||||
(click)="handleAction(action, { compartmentCode: latestCompartmentCode, compartmentInfo: latestCompartmentInfo })"
|
||||
>
|
||||
<ui-spinner [show]="(changeActionLoader$ | async) === action.command"
|
||||
>{{ latestCompartmentCode$ | async | addToPreviousCompartmentCodeLabelPipe }} zubuchen</ui-spinner
|
||||
>
|
||||
</button>
|
||||
|
||||
<button
|
||||
[disabled]="actionsDisabled$ | async"
|
||||
class="cta-action shadow-action"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
[class.cta-action-secondary]="!action.selected"
|
||||
*ngFor="let action of mainActions$ | async"
|
||||
(click)="handleAction(action)"
|
||||
>
|
||||
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,29 @@
|
||||
:host {
|
||||
@apply box-border block overflow-y-scroll h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
// max-height: calc(100vh - 305px);
|
||||
@apply box-border grid overflow-y-scroll h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
}
|
||||
|
||||
.page-customer-order-details__action-wrapper {
|
||||
@apply sticky bottom-4 mt-4 text-center;
|
||||
|
||||
.cta-action {
|
||||
@apply border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none self-end whitespace-nowrap m-2;
|
||||
}
|
||||
|
||||
.fetching {
|
||||
@apply bg-inactive-branch;
|
||||
}
|
||||
|
||||
.cta-action-primary {
|
||||
@apply bg-brand text-white;
|
||||
}
|
||||
|
||||
.cta-action-secondary {
|
||||
@apply bg-white text-brand;
|
||||
}
|
||||
|
||||
button {
|
||||
&:disabled {
|
||||
@apply bg-inactive-customer border-inactive-customer text-white cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,55 @@
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, ContentChildren, OnDestroy, OnInit, QueryList } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainCustomerOrderService, DomainGoodsService, DomainOmsService, OrderItemsContext } from '@domain/oms';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { OrderItemsContext } from '@domain/oms';
|
||||
import { provideComponentStore } from '@ngrx/component-store';
|
||||
import { CustomerOrdersNavigationService } from '@shared/services';
|
||||
import { ListResponseArgsOfOrderItemListItemDTO, OrderItemListItemDTO, OrderItemProcessingStatusValue } from '@swagger/oms';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { combineLatest, Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, filter, first, map, shareReplay, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
export interface CustomerOrderDetailsComponentState {
|
||||
fetching: boolean;
|
||||
orderNumber?: string;
|
||||
buyerNumber?: string;
|
||||
processingStatus?: OrderItemProcessingStatusValue;
|
||||
compartmentCode?: string;
|
||||
items?: OrderItemListItemDTO[];
|
||||
orderId?: number;
|
||||
archive?: boolean;
|
||||
}
|
||||
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO, OrderItemProcessingStatusValue, ReceiptDTO } from '@swagger/oms';
|
||||
import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { BehaviorSubject, Subscription, combineLatest } from 'rxjs';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { CustomerOrderDetailsStore } from './customer-order-details.store';
|
||||
import { CustomerOrderDetailsItemComponent } from './customer-order-details-item';
|
||||
import { unionBy } from 'lodash';
|
||||
import { CommandService } from '@core/command';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-details',
|
||||
templateUrl: 'customer-order-details.component.html',
|
||||
styleUrls: ['customer-order-details.component.scss'],
|
||||
providers: [provideComponentStore(CustomerOrderDetailsStore)],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderDetailsComponentState> implements OnInit, OnDestroy {
|
||||
orderNumber$ = this.select((s) => decodeURIComponent(s.orderNumber ?? '') || undefined);
|
||||
export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
|
||||
@ContentChildren(CustomerOrderDetailsItemComponent)
|
||||
customerOrderDetailsItemComponents: QueryList<CustomerOrderDetailsItemComponent>;
|
||||
|
||||
buyerNumber$ = this.select((s) => s.buyerNumber);
|
||||
buyerNumber$ = this._store.buyerNumber$;
|
||||
|
||||
compartmentCode$ = this.select((s) => decodeURIComponent(s.compartmentCode ?? '') || undefined);
|
||||
compartmentCode$ = this._store.compartmentCode$;
|
||||
|
||||
processingStatus$ = this.select((s) => s.processingStatus);
|
||||
latestCompartmentCode$ = this._store.latestCompartmentCode$;
|
||||
|
||||
archive$ = this.select((s) => s.archive);
|
||||
get archive() {
|
||||
return this.get((s) => s.archive);
|
||||
}
|
||||
latestCompartmentCode = this._store.latestCompartmentCode;
|
||||
|
||||
items$ = this.select((s) => s.items ?? []);
|
||||
latestCompartmentInfo = this._store.latestCompartmentInfo;
|
||||
|
||||
orderId$ = this.select((s) => s.orderId);
|
||||
compartmentInfo$ = this._store.compartmentInfo$;
|
||||
|
||||
get items() {
|
||||
return this.get((s) => s.items);
|
||||
}
|
||||
compartmentInfo = this._store.compartmentInfo;
|
||||
|
||||
order$ = this.orderId$.pipe(
|
||||
filter((orderId) => !!orderId),
|
||||
switchMap((orderId) => this._omsService.getOrder(orderId)),
|
||||
shareReplay()
|
||||
);
|
||||
addToPreviousCompartmentAction$ = this._store.addToPreviousCompartmentAction$;
|
||||
|
||||
mainActions$ = this._store.mainActions$;
|
||||
|
||||
processingStatus$ = this._store.processingStatus$;
|
||||
|
||||
items$ = this._store.items$;
|
||||
|
||||
order$ = this._store.order$;
|
||||
|
||||
fetching$ = this._store.fetching$;
|
||||
|
||||
get processId() {
|
||||
return +this._activatedRoute.snapshot.parent.data.processId;
|
||||
@@ -61,52 +57,103 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
|
||||
processId$ = this._activatedRoute.parent.data.pipe(map((params) => +params.processId));
|
||||
|
||||
hasActions$ = this.items$.pipe(map((items) => items?.some((item) => !!item.actions && item.actions.length > 0)));
|
||||
private subscriptions = new Subscription();
|
||||
|
||||
private _onDestroy$ = new Subject();
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
|
||||
changeActionLoader$ = new BehaviorSubject<string>(undefined);
|
||||
changeActionDisabled$ = this._store.changeActionDisabled$;
|
||||
|
||||
showTagsComponent$ = combineLatest([this.items$, this.fetching$]).pipe(
|
||||
map(([items, fetching]) => {
|
||||
const first = items?.find((_) => true);
|
||||
const hasArrivedAction = first?.actions?.some((a) => a.command?.includes('ARRIVED'));
|
||||
return hasArrivedAction && !fetching;
|
||||
})
|
||||
);
|
||||
|
||||
actionsDisabled$ = combineLatest([
|
||||
this.changeActionDisabled$,
|
||||
this._store.select((s) => (s.fetchPartial ? s.selectedeOrderItemSubsetIds.length === 0 : false)),
|
||||
this.items$,
|
||||
]).pipe(map(([disabled, partial, orderItems]) => disabled || (partial && orderItems.length > 1)));
|
||||
|
||||
addToPreviousCompartmentActionDisabled$ = combineLatest([this.compartmentInfo$, this.changeActionDisabled$, this.fetching$]).pipe(
|
||||
map(([compartmentInfo, changeActionDisabled, fetching]) => (!!compartmentInfo || changeActionDisabled) && fetching)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _domainGoodsInService: DomainCustomerOrderService,
|
||||
private _store: CustomerOrderDetailsStore,
|
||||
private _navigationService: CustomerOrdersNavigationService,
|
||||
private _omsService: DomainOmsService,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _router: Router,
|
||||
private _uiModal: UiModalService,
|
||||
private _environment: EnvironmentService
|
||||
) {
|
||||
super({
|
||||
fetching: false,
|
||||
});
|
||||
}
|
||||
private _environment: EnvironmentService,
|
||||
private _commandService: CommandService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((params) => {
|
||||
const buyerNumber: string = decodeURIComponent(params.buyerNumber ?? '');
|
||||
const archive = params?.archive || false;
|
||||
this.patchState({ buyerNumber, archive });
|
||||
});
|
||||
this.subscriptions.add(
|
||||
this._activatedRoute.queryParams.subscribe((params) => {
|
||||
const buyerNumber: string = decodeURIComponent(params.buyerNumber ?? '');
|
||||
this._store.patchState({ buyerNumber });
|
||||
})
|
||||
);
|
||||
|
||||
this._activatedRoute.params.pipe(takeUntil(this._onDestroy$)).subscribe(async (params) => {
|
||||
const orderNumber: string = params?.orderNumber;
|
||||
const compartmentCode = params?.compartmentCode;
|
||||
const processingStatus = +params?.processingStatus as OrderItemProcessingStatusValue;
|
||||
this.subscriptions.add(
|
||||
this._activatedRoute.params.subscribe(async (params) => {
|
||||
const orderNumber: string = params?.orderNumber;
|
||||
const compartmentCode = params?.compartmentCode;
|
||||
const processingStatus = +params?.processingStatus as OrderItemProcessingStatusValue;
|
||||
|
||||
this.patchState({ orderNumber, compartmentCode, processingStatus });
|
||||
await this.removeDetailsCrumbs();
|
||||
this.loadItems();
|
||||
});
|
||||
if (
|
||||
this._store.orderNumber !== orderNumber ||
|
||||
this._store.compartmentCode !== compartmentCode ||
|
||||
this._store.processingStatus !== processingStatus
|
||||
) {
|
||||
this._store.patchState({
|
||||
orderNumber: !!orderNumber ? decodeURIComponent(orderNumber) : undefined,
|
||||
compartmentCode: !!compartmentCode ? decodeURIComponent(compartmentCode) : undefined,
|
||||
processingStatus,
|
||||
});
|
||||
this._store.loadItems();
|
||||
}
|
||||
|
||||
await this.removeDetailsCrumbs();
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
this._store.searchCompleted.subscribe((state) => {
|
||||
const items = state?.items;
|
||||
this.openModalIfItemsHaveDifferentCustomers(items);
|
||||
this.itemsSelectable(items);
|
||||
|
||||
const firstItem = items?.find((_) => true);
|
||||
this.updateBreadcrumb(firstItem);
|
||||
|
||||
const orderId = firstItem?.orderId;
|
||||
if (!!orderId && this._store?.order?.id !== orderId) {
|
||||
this._store.patchState({ orderId });
|
||||
this._store.loadOrder();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.removeBreadcrumbs();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
this.subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
itemsSelectable(items: OrderItemListItemDTO[]) {
|
||||
this._store.patchState({
|
||||
itemsSelectable: items?.some((item) => !!item?.actions && item?.actions?.length > 0),
|
||||
});
|
||||
}
|
||||
|
||||
async updateBreadcrumb(item: OrderItemListItemDTO) {
|
||||
@@ -116,8 +163,8 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
name: item?.compartmentCode || item?.orderNumber,
|
||||
path: this.getDetailsPath(item),
|
||||
params: {
|
||||
buyerNumber: item.buyerNumber,
|
||||
...this._activatedRoute.snapshot.queryParams,
|
||||
buyerNumber: item.buyerNumber,
|
||||
},
|
||||
section: 'customer',
|
||||
tags: ['customer-order', 'details', item?.compartmentCode || item?.orderNumber],
|
||||
@@ -131,9 +178,17 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
const historyCrumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.processId, ['customer-order', 'history'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
editCrumbs.forEach((crumb) => {
|
||||
this._breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
historyCrumbs.forEach((crumb) => {
|
||||
this._breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
}
|
||||
|
||||
async removeDetailsCrumbs() {
|
||||
@@ -147,42 +202,8 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: member-ordering
|
||||
loadItems = this.effect(($) =>
|
||||
$.pipe(
|
||||
tap(() => this.patchState({ fetching: false })),
|
||||
debounceTime(500),
|
||||
withLatestFrom(this.orderNumber$, this.compartmentCode$, this.archive$),
|
||||
switchMap(([_, orderNumber, compartmentCode, archive]) => {
|
||||
let request$: Observable<ListResponseArgsOfOrderItemListItemDTO>;
|
||||
request$ = this._domainGoodsInService.getOrderItemsByOrderNumber(orderNumber ?? compartmentCode);
|
||||
return combineLatest([request$, this.processingStatus$, this.buyerNumber$]).pipe(
|
||||
tapResponse(
|
||||
([res, processingStatus, buyerNumber]) => {
|
||||
const items = res.result.filter((item) => {
|
||||
return item.processingStatus === processingStatus && (!!buyerNumber ? item.buyerNumber === buyerNumber : true);
|
||||
});
|
||||
|
||||
this.openModalIfItemsHaveDifferentCustomers(items);
|
||||
|
||||
this.patchState({
|
||||
items,
|
||||
orderId: items[0].orderId,
|
||||
});
|
||||
this.updateBreadcrumb(items[0]);
|
||||
},
|
||||
(err) => {},
|
||||
() => {
|
||||
this.patchState({ fetching: false });
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
openModalIfItemsHaveDifferentCustomers(items: OrderItemListItemDTO[]) {
|
||||
const buyerNumbers = new Set(items.map((item) => item.buyerNumber));
|
||||
const buyerNumbers = new Set(items?.map((item) => item?.buyerNumber));
|
||||
if (buyerNumbers.size > 1) {
|
||||
this._uiModal.open({
|
||||
content: UiMessageModalComponent,
|
||||
@@ -196,21 +217,18 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
|
||||
navigateToEditPage(orderItem: OrderItemListItemDTO) {
|
||||
this._router.navigate(this.getEditPath(orderItem), {
|
||||
queryParams: { buyerNumber: orderItem.buyerNumber, archive: this.archive },
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, buyerNumber: orderItem?.buyerNumber },
|
||||
});
|
||||
}
|
||||
|
||||
async actionHandled(handler: { orderItemsContext: OrderItemsContext; command: string; navigation: 'details' | 'main' | 'reservation' }) {
|
||||
if (handler.navigation === 'main') {
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId: this.processId });
|
||||
} else {
|
||||
const item: OrderItemListItemDTO = handler.orderItemsContext.items.find((_) => true);
|
||||
await this._router.navigate(this.getDetailsPath(item), {
|
||||
queryParams: { buyerNumber: item.buyerNumber, archive: this.archive },
|
||||
});
|
||||
await this.removeDetailsCrumbs();
|
||||
this.loadItems();
|
||||
}
|
||||
navigateToHistoryPage(orderItem: OrderItemListItemDTO) {
|
||||
this._router.navigate(this.getHistoryPath(orderItem), {
|
||||
queryParams: {
|
||||
...this._activatedRoute.snapshot.queryParams,
|
||||
buyerNumber: orderItem?.buyerNumber,
|
||||
orderItemSubsetId: orderItem?.orderItemSubsetId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getDetailsPath(item: OrderItemListItemDTO) {
|
||||
@@ -230,4 +248,133 @@ export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderD
|
||||
orderNumber: item?.orderNumber ? encodeURIComponent(item.orderNumber) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
getHistoryPath(item: OrderItemListItemDTO) {
|
||||
return this._navigationService.getCustomerOrdersHistoryPath({
|
||||
processId: this.processId,
|
||||
processingStatus: item?.processingStatus,
|
||||
compartmentCode: item?.compartmentCode ? encodeURIComponent(item.compartmentCode) : undefined,
|
||||
orderNumber: item?.orderNumber ? encodeURIComponent(item.orderNumber) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
getItemQuantityMap(): Map<number, number> {
|
||||
return new Map(
|
||||
this.customerOrderDetailsItemComponents.toArray().map((component) => [component.orderItem.orderItemSubsetId, component.quantity])
|
||||
);
|
||||
}
|
||||
|
||||
getitemsToUpdate(): OrderItemListItemDTO[] {
|
||||
if (this._store.fetchPartial && this._store.itemsSelectable && this._store.items.length > 1) {
|
||||
return this._store.items.filter((orderItem) => this._store.selectedeOrderItemSubsetIds.includes(orderItem.orderItemSubsetId));
|
||||
}
|
||||
|
||||
return this._store.items;
|
||||
}
|
||||
|
||||
async handleAction(
|
||||
action: KeyValueDTOOfStringAndString,
|
||||
{ compartmentCode, compartmentInfo }: { compartmentCode?: string; compartmentInfo?: string } = {}
|
||||
) {
|
||||
if (action.command.includes('FETCHED_PARTIAL')) {
|
||||
this._store.patchState({ fetchPartial: true });
|
||||
return;
|
||||
}
|
||||
let receipts: ReceiptDTO[] = [];
|
||||
if (action.command.includes('PRINT_SHIPPINGNOTE')) {
|
||||
const receiptsPromise = this.customerOrderDetailsItemComponents.toArray().map(
|
||||
(itemComponent) =>
|
||||
new Promise<ReceiptDTO[]>((resolve) => {
|
||||
itemComponent.loadReceipts((r) => resolve(r));
|
||||
})
|
||||
);
|
||||
|
||||
receipts = await Promise.all(receiptsPromise).then((r) => r.reduce((acc, val) => acc.concat(val), []));
|
||||
|
||||
receipts = unionBy(receipts, 'id');
|
||||
}
|
||||
|
||||
// #2737 Bei Zubuchen kein Abholfachzettel ausdrucken
|
||||
let command = action.command;
|
||||
if (compartmentCode) {
|
||||
command = action.command.replace('|PRINT_COMPARTMENTLABEL', '');
|
||||
}
|
||||
|
||||
this.changeActionLoader$.next(action.command);
|
||||
this.changeActionDisabled$.next(true);
|
||||
|
||||
let commandData: OrderItemsContext = {
|
||||
items: this.getitemsToUpdate(),
|
||||
compartmentInfo: compartmentInfo || this.compartmentInfo,
|
||||
compartmentCode:
|
||||
action.command.includes('PRINT_PRICEDIFFQRCODELABEL') && !compartmentCode
|
||||
? this._store.items?.find((_) => true)?.compartmentCode
|
||||
: compartmentCode,
|
||||
itemQuantity: this.getItemQuantityMap(),
|
||||
receipts,
|
||||
};
|
||||
try {
|
||||
commandData = await this._commandService.handleCommand(command, commandData);
|
||||
let navigateTo: 'details' | 'main' | 'reservation' = 'details';
|
||||
if (action.command.includes('ARRIVED')) {
|
||||
navigateTo = await this.arrivedActionNavigation();
|
||||
}
|
||||
if (action.command.includes('PRINT_PRICEDIFFQRCODELABEL')) {
|
||||
navigateTo = 'main';
|
||||
}
|
||||
|
||||
await this.actionHandled({ orderItemsContext: commandData, command: action.command, navigation: navigateTo });
|
||||
this._store.updateOrderItems(commandData.items);
|
||||
} catch (error) {
|
||||
this._uiModal.open({
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (action.command.includes('FETCHED_PARTIAL')) {
|
||||
this._store.patchState({ fetchPartial: false });
|
||||
}
|
||||
this._store.selectedeOrderItemSubsetIds = [];
|
||||
|
||||
setTimeout(() => {
|
||||
this.changeActionDisabled$.next(false);
|
||||
this.changeActionLoader$.next(undefined);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async actionHandled(handler: { orderItemsContext: OrderItemsContext; command: string; navigation: 'details' | 'main' | 'reservation' }) {
|
||||
if (handler.navigation === 'main') {
|
||||
await this._navigationService.navigateToCustomerOrdersSearch({ processId: this.processId });
|
||||
} else {
|
||||
const item: OrderItemListItemDTO = handler.orderItemsContext.items.find((_) => true);
|
||||
await this._router.navigate(this.getDetailsPath(item), {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, buyerNumber: item.buyerNumber },
|
||||
});
|
||||
await this.updateCustomerOrderResults();
|
||||
await this.removeDetailsCrumbs();
|
||||
this._store.loadItems();
|
||||
}
|
||||
}
|
||||
|
||||
async updateCustomerOrderResults() {
|
||||
if (this.isDesktop) {
|
||||
await this._router.navigate([], {
|
||||
queryParams: { ...this._activatedRoute.snapshot.queryParams, updateResults: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async arrivedActionNavigation(): Promise<'main'> {
|
||||
const detailsCrumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$('customer-order', ['customer-order', 'details'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
detailsCrumbs.forEach((crumb) => {
|
||||
this._breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
|
||||
return 'main';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,51 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedGoodsInOutOrderDetailsModule } from '@shared/components/goods-in-out';
|
||||
|
||||
import { CustomerOrderDetailsComponent } from './customer-order-details.component';
|
||||
import { CustomerOrderDetailsItemComponent } from './customer-order-details-item';
|
||||
import { CustomerOrderDetailsHeaderComponent } from './customer-order-details-header';
|
||||
import { CustomerOrderDetailsTagsComponent } from './customer-order-details-tags';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
import { CustomerOrderPipesModule } from '../pipes';
|
||||
import { ProductImageModule } from '@cdn/product-image';
|
||||
import { CustomerOrderDetailsStore } from './customer-order-details.store';
|
||||
import { UiDatepickerModule } from '@ui/datepicker';
|
||||
import { UiDropdownModule } from '@ui/dropdown';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, SharedGoodsInOutOrderDetailsModule],
|
||||
exports: [CustomerOrderDetailsComponent],
|
||||
declarations: [CustomerOrderDetailsComponent],
|
||||
providers: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
UiCommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
UiSpinnerModule,
|
||||
IconModule,
|
||||
UiTooltipModule,
|
||||
UiDatepickerModule,
|
||||
UiQuantityDropdownModule,
|
||||
UiDropdownModule,
|
||||
TextFieldModule,
|
||||
CustomerOrderPipesModule,
|
||||
ProductImageModule,
|
||||
],
|
||||
exports: [
|
||||
CustomerOrderDetailsComponent,
|
||||
CustomerOrderDetailsItemComponent,
|
||||
CustomerOrderDetailsHeaderComponent,
|
||||
CustomerOrderDetailsTagsComponent,
|
||||
],
|
||||
declarations: [
|
||||
CustomerOrderDetailsComponent,
|
||||
CustomerOrderDetailsItemComponent,
|
||||
CustomerOrderDetailsHeaderComponent,
|
||||
CustomerOrderDetailsTagsComponent,
|
||||
],
|
||||
providers: [CustomerOrderDetailsStore],
|
||||
})
|
||||
export class CustomerOrderDetailsModule {}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { DomainCustomerOrderService, DomainOmsService, OrderItemsContext } from '@domain/oms';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import {
|
||||
ListResponseArgsOfOrderItemListItemDTO,
|
||||
OrderDTO,
|
||||
OrderItemListItemDTO,
|
||||
OrderItemProcessingStatusValue,
|
||||
ReceiptDTO,
|
||||
} from '@swagger/oms';
|
||||
import { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
export interface CustomerOrderDetailsComponentState {
|
||||
order?: OrderDTO;
|
||||
orderNumber?: string;
|
||||
orderId?: number;
|
||||
buyerNumber?: string;
|
||||
items?: OrderItemListItemDTO[];
|
||||
fetching?: boolean;
|
||||
processingStatus?: OrderItemProcessingStatusValue;
|
||||
receipts: ReceiptDTO[];
|
||||
compartmentCode?: string;
|
||||
compartmentInfo?: string;
|
||||
latestCompartmentCode?: string;
|
||||
latestCompartmentInfo?: string;
|
||||
fetchPartial?: boolean;
|
||||
itemsSelectable?: boolean;
|
||||
selectedeOrderItemSubsetIds: number[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CustomerOrderDetailsStore extends ComponentStore<CustomerOrderDetailsComponentState> implements OnDestroy {
|
||||
get items() {
|
||||
return this.get((s) => s.items);
|
||||
}
|
||||
set items(items: OrderItemListItemDTO[]) {
|
||||
if (!isEqual(this.items, items)) {
|
||||
this.patchState({ items });
|
||||
}
|
||||
}
|
||||
|
||||
items$ = this.select((s) => s.items);
|
||||
|
||||
get orderNumber() {
|
||||
return this.get((s) => s.orderNumber);
|
||||
}
|
||||
set orderNumber(orderNumber: string) {
|
||||
this.patchState({ orderNumber });
|
||||
}
|
||||
|
||||
orderNumber$ = this.select((s) => s.orderNumber);
|
||||
|
||||
get orderId() {
|
||||
return this.get((s) => s.orderId);
|
||||
}
|
||||
set orderId(orderId: number) {
|
||||
this.patchState({ orderId });
|
||||
}
|
||||
|
||||
orderId$ = this.select((s) => s.orderId);
|
||||
|
||||
get order() {
|
||||
return this.get((s) => s.order);
|
||||
}
|
||||
set order(order: OrderDTO) {
|
||||
this.patchState({ order });
|
||||
}
|
||||
|
||||
order$ = this.select((s) => s.order);
|
||||
|
||||
get processingStatus() {
|
||||
return this.get((s) => s.processingStatus);
|
||||
}
|
||||
set processingStatus(processingStatus: OrderItemProcessingStatusValue) {
|
||||
this.patchState({ processingStatus });
|
||||
}
|
||||
|
||||
processingStatus$ = this.select((s) => s.processingStatus);
|
||||
|
||||
get itemsSelectable() {
|
||||
return this.get((s) => s.itemsSelectable);
|
||||
}
|
||||
set itemsSelectable(itemsSelectable: boolean) {
|
||||
this.patchState({ itemsSelectable });
|
||||
}
|
||||
|
||||
itemsSelectable$ = this.select((s) => s.itemsSelectable);
|
||||
|
||||
get fetching() {
|
||||
return this.get((s) => s.fetching);
|
||||
}
|
||||
set fetching(fetching: boolean) {
|
||||
this.patchState({ fetching });
|
||||
}
|
||||
|
||||
fetching$ = this.select((s) => s.fetching);
|
||||
|
||||
get compartmentCode() {
|
||||
return this.get((s) => s.compartmentCode);
|
||||
}
|
||||
set compartmentCode(compartmentCode: string) {
|
||||
this.patchState({ compartmentCode });
|
||||
}
|
||||
|
||||
compartmentCode$ = this.select((s) => s.compartmentCode);
|
||||
|
||||
get buyerNumber() {
|
||||
return this.get((s) => s.buyerNumber);
|
||||
}
|
||||
set buyerNumber(buyerNumber: string) {
|
||||
this.patchState({ buyerNumber });
|
||||
}
|
||||
|
||||
buyerNumber$ = this.select((s) => s.buyerNumber);
|
||||
|
||||
get selectedeOrderItemSubsetIds() {
|
||||
return this.get((s) => s.selectedeOrderItemSubsetIds);
|
||||
}
|
||||
set selectedeOrderItemSubsetIds(selectedeOrderItemSubsetIds: number[]) {
|
||||
if (!isEqual(this.selectedeOrderItemSubsetIds, selectedeOrderItemSubsetIds)) {
|
||||
this.patchState({ selectedeOrderItemSubsetIds });
|
||||
}
|
||||
}
|
||||
|
||||
selectedeOrderItemSubsetIds$ = this.select((s) => s.selectedeOrderItemSubsetIds);
|
||||
|
||||
get compartmentInfo() {
|
||||
return this.get((s) => s.compartmentInfo);
|
||||
}
|
||||
set compartmentInfo(compartmentInfo: string) {
|
||||
if (this.compartmentInfo !== compartmentInfo) {
|
||||
this.patchState({ compartmentInfo });
|
||||
}
|
||||
}
|
||||
|
||||
compartmentInfo$ = this.select((s) => s.compartmentInfo);
|
||||
|
||||
get fetchPartial() {
|
||||
return this.get((s) => s.fetchPartial);
|
||||
}
|
||||
|
||||
fetchPartial$ = this.select((s) => s.fetchPartial);
|
||||
|
||||
mainActions$ = combineLatest([this.items$, this.fetchPartial$]).pipe(
|
||||
map(([items, fetchPartial]) =>
|
||||
items
|
||||
?.find((_) => true)
|
||||
?.actions?.filter((action) => typeof action?.enabled !== 'boolean')
|
||||
?.filter((action) => (fetchPartial ? !action.command.includes('FETCHED_PARTIAL') : true))
|
||||
?.sort((a, b) => (a.selected === b.selected ? 0 : a.selected ? -1 : 1))
|
||||
)
|
||||
);
|
||||
|
||||
showTagsComponent$ = this.items$.pipe(
|
||||
map((orderItems) => {
|
||||
const first = orderItems?.find((_) => true);
|
||||
return first?.actions?.some((a) => a.command?.includes('ARRIVED')) || false;
|
||||
})
|
||||
);
|
||||
|
||||
get latestCompartmentCode() {
|
||||
return this.get((s) => s.latestCompartmentCode);
|
||||
}
|
||||
|
||||
set latestCompartmentCode(latestCompartmentCode: string) {
|
||||
if (this.latestCompartmentCode !== latestCompartmentCode) {
|
||||
this.patchState({ latestCompartmentCode });
|
||||
}
|
||||
}
|
||||
|
||||
readonly latestCompartmentCode$ = combineLatest([
|
||||
this.select((s) => s.latestCompartmentCode),
|
||||
this.select((s) => s.latestCompartmentInfo),
|
||||
]).pipe(
|
||||
map(([code, info]) => {
|
||||
if (!!info) {
|
||||
return `${code}_${info}`;
|
||||
}
|
||||
return code;
|
||||
})
|
||||
);
|
||||
|
||||
get latestCompartmentInfo() {
|
||||
return this.get((s) => s.latestCompartmentInfo);
|
||||
}
|
||||
|
||||
set latestCompartmentInfo(latestCompartmentInfo: string) {
|
||||
if (this.latestCompartmentInfo !== latestCompartmentInfo) {
|
||||
this.patchState({ latestCompartmentInfo });
|
||||
}
|
||||
}
|
||||
|
||||
readonly latestCompartmentInfo$ = this.select((s) => s.latestCompartmentInfo);
|
||||
|
||||
addToPreviousCompartmentAction$ = combineLatest([this.items$, this.latestCompartmentCode$]).pipe(
|
||||
map(([orderItems, latestCompartmentCode]) => {
|
||||
const orderItem = orderItems?.find((_) => true);
|
||||
|
||||
if ([16, 8192].includes(orderItem?.processingStatus) && latestCompartmentCode) {
|
||||
// Zubuchen von Bezahlte und unbezahlte Bestellungen nicht möglich
|
||||
// Zubuchen bei Pay&Collect nur innerhalb der gleichen Bestellung möglich
|
||||
|
||||
return orderItem.actions.find((a) => a.key === '128');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
|
||||
searchCompleted = new Subject<CustomerOrderDetailsComponentState>();
|
||||
|
||||
actionHandled = new Subject<{
|
||||
orderItemsContext: OrderItemsContext;
|
||||
command: string;
|
||||
navigation: 'details' | 'main' | 'reservation';
|
||||
}>();
|
||||
|
||||
changeActionDisabled$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
constructor(private _domainCustomerOrderService: DomainCustomerOrderService, private _domainOmsService: DomainOmsService) {
|
||||
super({
|
||||
items: [],
|
||||
selectedeOrderItemSubsetIds: [],
|
||||
receipts: [],
|
||||
fetchPartial: true,
|
||||
});
|
||||
}
|
||||
|
||||
loadItems = this.effect(($) =>
|
||||
$.pipe(
|
||||
tap(() => this.patchState({ fetching: true })),
|
||||
debounceTime(500),
|
||||
withLatestFrom(this.orderNumber$, this.compartmentCode$),
|
||||
switchMap(([_, orderNumber, compartmentCode]) => {
|
||||
return this._domainCustomerOrderService.getOrderItemsByOrderNumber(orderNumber ?? compartmentCode).pipe(
|
||||
withLatestFrom(this.processingStatus$, this.buyerNumber$),
|
||||
tapResponse(
|
||||
([res, processingStatus, buyerNumber]) => {
|
||||
const items = res?.result?.filter((item) => {
|
||||
return item.processingStatus === processingStatus && (!!buyerNumber ? item.buyerNumber === buyerNumber : true);
|
||||
});
|
||||
this.patchState({
|
||||
items,
|
||||
fetching: false,
|
||||
orderId: items?.find((_) => true)?.orderId,
|
||||
});
|
||||
this.searchCompleted.next(this.get());
|
||||
},
|
||||
(err) => {
|
||||
console.error(err);
|
||||
this.patchState({ fetching: false, items: [] });
|
||||
this.searchCompleted.next(this.get());
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
loadOrder = this.effect(($) =>
|
||||
$.pipe(
|
||||
withLatestFrom(this.orderId$),
|
||||
switchMap(([_, orderId]) => {
|
||||
return this._domainOmsService.getOrder(orderId).pipe(
|
||||
tapResponse(
|
||||
(response) => {
|
||||
this.patchState({ order: response });
|
||||
},
|
||||
(err) => {
|
||||
this.patchState({ order: undefined });
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
selectAllOrderItems() {
|
||||
this.patchState({ selectedeOrderItemSubsetIds: this.items?.map((item) => item.orderItemSubsetId) });
|
||||
}
|
||||
|
||||
selectOrderItem(item: OrderItemListItemDTO, selected: boolean) {
|
||||
const included = this.selectedeOrderItemSubsetIds.includes(item?.orderItemSubsetId);
|
||||
|
||||
if (!included && selected) {
|
||||
this.patchState({ selectedeOrderItemSubsetIds: [...this.selectedeOrderItemSubsetIds, item.orderItemSubsetId] });
|
||||
} else if (included && !selected) {
|
||||
this.patchState({ selectedeOrderItemSubsetIds: this.selectedeOrderItemSubsetIds.filter((id) => id !== item?.orderItemSubsetId) });
|
||||
}
|
||||
}
|
||||
|
||||
updateOrderItems(orderItems: OrderItemListItemDTO[]) {
|
||||
this.patchState({
|
||||
items: this.items.map((item) => {
|
||||
const newItem = orderItems.find((i) => i.orderItemSubsetId === item.orderItemSubsetId);
|
||||
return newItem ? newItem : item;
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
updateReceipts(receipts: ReceiptDTO[]) {
|
||||
this.patchState({
|
||||
receipts,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,5 @@
|
||||
export * from './customer-order-details.component';
|
||||
export * from './customer-order-details.module';
|
||||
export * from './customer-order-details-header';
|
||||
export * from './customer-order-details-item';
|
||||
export * from './customer-order-details-tags';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
div {
|
||||
@apply overflow-y-scroll;
|
||||
height: calc(100vh - 305px);
|
||||
@apply box-border grid overflow-y-scroll h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainCustomerOrderService, DomainGoodsService, DomainOmsService } from '@domain/oms';
|
||||
@@ -38,7 +38,7 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
|
||||
items$ = combineLatest([this.orderNumber$, this.compartmentCode$]).pipe(
|
||||
switchMap(([orderNumber, compartmentCode]) => {
|
||||
return this._domainGoodsInService.getOrderItemsByOrderNumber(compartmentCode ?? orderNumber);
|
||||
return this._domainCustomerOrderService.getOrderItemsByOrderNumber(compartmentCode ?? orderNumber);
|
||||
}),
|
||||
withLatestFrom(this.processingStatus$, this.buyerNumber$),
|
||||
map(([response, processingStatus, buyerNumber]) => {
|
||||
@@ -57,9 +57,8 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
constructor(
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _domainGoodsInService: DomainCustomerOrderService,
|
||||
private _domainCustomerOrderService: DomainCustomerOrderService,
|
||||
private _navigation: CustomerOrdersNavigationService,
|
||||
private _router: Router,
|
||||
private _uiModal: UiModalService,
|
||||
private _environment: EnvironmentService
|
||||
) {}
|
||||
@@ -72,19 +71,17 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
const orderNumber = this._activatedRoute.snapshot.params.orderNumber;
|
||||
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
|
||||
const processingStatus = this._activatedRoute.snapshot.params.processingStatus;
|
||||
const buyerNumber = this._activatedRoute.snapshot.queryParams.buyerNumber;
|
||||
const archive = this._activatedRoute.snapshot.queryParams.archive;
|
||||
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this.processId,
|
||||
name: 'Bearbeiten',
|
||||
path: this._navigation.getCustomerOrdersEditPath({
|
||||
processId: this.processId,
|
||||
processingStatus,
|
||||
compartmentCode: compartmentCode ? encodeURIComponent(compartmentCode) : undefined,
|
||||
orderNumber: orderNumber ? encodeURIComponent(orderNumber) : undefined,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderNumber: orderNumber ?? undefined,
|
||||
}),
|
||||
section: 'customer',
|
||||
params: { buyerNumber, archive },
|
||||
params: this._activatedRoute.snapshot.queryParams,
|
||||
tags: ['customer-order', 'edit', compartmentCode || orderNumber],
|
||||
});
|
||||
}
|
||||
@@ -93,18 +90,13 @@ export class CustomerOrderEditComponent implements OnInit {
|
||||
const orderNumber = this._activatedRoute.snapshot.params.orderNumber;
|
||||
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
|
||||
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
|
||||
const buyerNumber = this._activatedRoute.snapshot.queryParams.buyerNumber;
|
||||
const archive = this._activatedRoute.snapshot.queryParams.archive;
|
||||
|
||||
await this._navigation.navigateToDetails({
|
||||
processId: this.processId,
|
||||
processingStatus: processingStatus,
|
||||
compartmentCode: compartmentCode ? encodeURIComponent(compartmentCode) : undefined,
|
||||
orderNumber: orderNumber ? encodeURIComponent(orderNumber) : undefined,
|
||||
queryParams: {
|
||||
buyerNumber,
|
||||
archive,
|
||||
},
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderNumber: orderNumber ?? undefined,
|
||||
queryParams: this._activatedRoute.snapshot.queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="bg-[#f5f7fa]">
|
||||
<div class="bg-white px-7 flex flex-col py-5">
|
||||
<button class="self-end" type="button" (click)="navigateToDetailsPage({})">
|
||||
<shared-icon icon="close" [size]="26"></shared-icon>
|
||||
</button>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<div class="flex flex-row flex-grow">
|
||||
<span class="mr-3">Kundenname</span>
|
||||
<strong>{{ customerName$ | async }}</strong>
|
||||
</div>
|
||||
<div class="flex flex-row flex-grow">
|
||||
<span class="mr-3">Kundennummer</span>
|
||||
<strong>{{ customerNumber$ | async }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<shared-history-list [history]="history$ | async"> </shared-history-list>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply box-border grid overflow-y-scroll h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainCustomerOrderService, DomainOmsService } from '@domain/oms';
|
||||
import { CustomerOrdersNavigationService } from '@shared/services';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { map, switchMap, shareReplay, take } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-order-history',
|
||||
templateUrl: 'customer-order-history.component.html',
|
||||
styleUrls: ['customer-order-history.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerOrderHistoryComponent implements OnInit {
|
||||
get processId() {
|
||||
return +this._activatedRoute.snapshot.parent.data.processId;
|
||||
}
|
||||
|
||||
processId$ = this._activatedRoute.parent.data.pipe(map((params) => +params.processId));
|
||||
|
||||
orderNumber$: Observable<string> = this._activatedRoute.params.pipe(
|
||||
map((params) => decodeURIComponent(params.orderNumber ?? '') || undefined)
|
||||
);
|
||||
|
||||
compartmentCode$: Observable<string> = this._activatedRoute.params.pipe(
|
||||
map((params) => decodeURIComponent(params?.compartmentCode ?? '') || undefined)
|
||||
);
|
||||
|
||||
get orderItemSubsetId() {
|
||||
return +this._activatedRoute.snapshot.queryParams.orderItemSubsetId;
|
||||
}
|
||||
|
||||
historyItem$ = combineLatest([this.orderNumber$, this.compartmentCode$]).pipe(
|
||||
switchMap(([orderNumber, compartmentCode]) =>
|
||||
this._domainCustomerOrderService.getOrderItemsByOrderNumber(compartmentCode ?? orderNumber)
|
||||
),
|
||||
map((response) => response?.result?.find((listItem) => listItem?.orderItemSubsetId === this.orderItemSubsetId)),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
customerName$ = this.historyItem$.pipe(
|
||||
map((historyItem) =>
|
||||
[historyItem?.organisation ?? historyItem.organisation, historyItem.lastName, historyItem.firstName].filter((i) => !!i).join(', ')
|
||||
)
|
||||
);
|
||||
|
||||
customerNumber$ = this.historyItem$.pipe(map((historyItem) => historyItem.buyerNumber));
|
||||
|
||||
history$ = this.historyItem$.pipe(switchMap((historyItem) => this._omsService.getHistory(historyItem.orderItemSubsetId).pipe(take(1))));
|
||||
|
||||
constructor(
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _navigation: CustomerOrdersNavigationService,
|
||||
private _domainCustomerOrderService: DomainCustomerOrderService,
|
||||
private _omsService: DomainOmsService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.updateBreadcrumb();
|
||||
}
|
||||
|
||||
async updateBreadcrumb() {
|
||||
const orderNumber = this._activatedRoute.snapshot.params.orderNumber;
|
||||
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
|
||||
const processingStatus = this._activatedRoute.snapshot.params.processingStatus;
|
||||
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this.processId,
|
||||
name: 'Historie',
|
||||
path: this._navigation.getCustomerOrdersHistoryPath({
|
||||
processId: this.processId,
|
||||
processingStatus,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderNumber: orderNumber ?? undefined,
|
||||
}),
|
||||
section: 'customer',
|
||||
params: this._activatedRoute.snapshot.queryParams,
|
||||
tags: ['customer-order', 'history', compartmentCode || orderNumber],
|
||||
});
|
||||
}
|
||||
|
||||
async navigateToDetailsPage({ options }: { options?: { processingStatus?: number } }) {
|
||||
const orderNumber = this._activatedRoute.snapshot.params.orderNumber;
|
||||
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
|
||||
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
|
||||
|
||||
await this._navigation.navigateToDetails({
|
||||
processId: this.processId,
|
||||
processingStatus: processingStatus,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderNumber: orderNumber ?? undefined,
|
||||
queryParams: this._activatedRoute.snapshot.queryParams,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { CustomerOrderHistoryComponent } from './customer-order-history.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedHistoryListModule } from '@shared/components/history';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, SharedHistoryListModule, IconModule],
|
||||
exports: [CustomerOrderHistoryComponent],
|
||||
declarations: [CustomerOrderHistoryComponent],
|
||||
providers: [],
|
||||
})
|
||||
export class CustomerOrderHistoryModule {}
|
||||
@@ -0,0 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './customer-order-history.component';
|
||||
export * from './customer-order-history.module';
|
||||
// end:ng42.barrel
|
||||
@@ -6,6 +6,7 @@ import { CustomerOrderSearchComponent, CustomerOrderSearchFilterComponent, Custo
|
||||
import { CustomerOrderSearchMainComponent, CustomerOrderSearchMainModule } from './customer-order-search/search-main';
|
||||
import { CustomerOrderSearchResultsComponent, CustomerOrderSearchResultsModule } from './customer-order-search/search-results';
|
||||
import { CustomerOrderComponent } from './customer-order.component';
|
||||
import { CustomerOrderHistoryComponent } from './customer-order-history';
|
||||
|
||||
const auxiliaryRoutes = [
|
||||
{
|
||||
@@ -25,12 +26,12 @@ const auxiliaryRoutes = [
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'filter/:compartmentCode/:processingStatus',
|
||||
path: 'filter/compartment/:compartmentCode/:processingStatus',
|
||||
component: CustomerOrderSearchFilterComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'filter/:orderNumber/:processingStatus',
|
||||
path: 'filter/order/:orderNumber/:processingStatus',
|
||||
component: CustomerOrderSearchFilterComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
@@ -74,6 +75,16 @@ const auxiliaryRoutes = [
|
||||
component: CustomerOrderEditComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'details/compartment/:compartmentCode/:processingStatus/history',
|
||||
component: CustomerOrderHistoryComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
{
|
||||
path: 'details/order/:orderNumber/:processingStatus/history',
|
||||
component: CustomerOrderHistoryComponent,
|
||||
outlet: 'right',
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -107,6 +118,14 @@ const routes: Routes = [
|
||||
path: 'details/order/:orderNumber/:processingStatus/edit',
|
||||
component: CustomerOrderEditComponent,
|
||||
},
|
||||
{
|
||||
path: 'details/compartment/:compartmentCode/:processingStatus/history',
|
||||
component: CustomerOrderHistoryComponent,
|
||||
},
|
||||
{
|
||||
path: 'details/order/:orderNumber/:processingStatus/history',
|
||||
component: CustomerOrderHistoryComponent,
|
||||
},
|
||||
...auxiliaryRoutes,
|
||||
],
|
||||
},
|
||||
|
||||
@@ -29,6 +29,10 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
|
||||
filter$: Observable<Filter>;
|
||||
|
||||
get isTablet() {
|
||||
return this._environment.matchTablet();
|
||||
}
|
||||
|
||||
get isDesktop() {
|
||||
return this._environment.matchDesktop();
|
||||
}
|
||||
@@ -59,8 +63,8 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
return this._navigationService.getCustomerOrdersDetailsPath({
|
||||
processId: this.processId,
|
||||
processingStatus,
|
||||
compartmentCode: compartmentCode ? encodeURIComponent(compartmentCode) : undefined,
|
||||
orderNumber: orderNumber ? encodeURIComponent(orderNumber) : undefined,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderNumber: orderNumber ?? undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -79,6 +83,22 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
ngOnInit() {
|
||||
this._initSettings();
|
||||
this._initLoading$();
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this._customerOrdersSearchStore.searchStarted.pipe(takeUntil(this._onDestroy$)).subscribe(async (_) => {
|
||||
let queryParams = {
|
||||
...this._customerOrdersSearchStore.filter.getQueryParams(),
|
||||
...this.cleanupQueryParams(this.uiFilter?.uiFilter?.getQueryParams()),
|
||||
};
|
||||
|
||||
// Always override query if not in tablet mode
|
||||
if (!!this._customerOrdersSearchStore.filter.getQueryParams()?.main_qs && !this.isTablet) {
|
||||
queryParams = { ...queryParams, main_qs: this._customerOrdersSearchStore.filter.getQueryParams()?.main_qs };
|
||||
}
|
||||
|
||||
this._customerOrdersSearchStore.setQueryParams(queryParams);
|
||||
await this._customerOrdersSearchStore.loadDefaultSettings();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -103,7 +123,6 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
|
||||
async applyFilter(filter: Filter) {
|
||||
this.uiFilter?.cancelAutocomplete();
|
||||
this._customerOrdersSearchStore.setFilter(filter);
|
||||
this._customerOrdersSearchStore.setMessage('');
|
||||
|
||||
await this.updateQueryParams();
|
||||
@@ -155,6 +174,20 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
|
||||
this.updateBreadcrumb();
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||
if (clean[key] == undefined) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
clearFilter(value: Filter) {
|
||||
value.unselectAll();
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
|
||||
readonly searchResultClearedSubject = new Subject<void>();
|
||||
|
||||
searchStarted = new Subject<{ clear?: boolean; silentReload?: boolean }>();
|
||||
|
||||
constructor(private _domainGoodsInService: DomainCustomerOrderService, private _cache: CacheService) {
|
||||
super({
|
||||
fetching: false,
|
||||
@@ -93,10 +95,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
}
|
||||
|
||||
async loadDefaultSettings() {
|
||||
const defaultSettings = await this._domainGoodsInService
|
||||
.settings()
|
||||
.pipe(map((res) => res?.result))
|
||||
.toPromise();
|
||||
const defaultSettings = await this._domainGoodsInService.settings().toPromise();
|
||||
|
||||
const filter = Filter.create(defaultSettings);
|
||||
if (this.queryParams) {
|
||||
@@ -142,6 +141,9 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
|
||||
|
||||
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
|
||||
options$.pipe(
|
||||
tap((_) => {
|
||||
this.searchStarted.next();
|
||||
}),
|
||||
withLatestFrom(this.results$, this.filter$, this.selectedBranch$),
|
||||
tap(([options, _, filter, __]) => {
|
||||
if (!options?.siletReload) {
|
||||
|
||||
@@ -116,12 +116,39 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this._subscriptions.add(
|
||||
this._customerOrderSearchStore.searchStarted.subscribe(async (_) => {
|
||||
const queryParams = {
|
||||
...this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()),
|
||||
main_qs: this.filterInputGroup?.uiInput?.value,
|
||||
};
|
||||
|
||||
this._customerOrderSearchStore.setQueryParams(queryParams);
|
||||
await this._customerOrderSearchStore.loadDefaultSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||
if (clean[key] == undefined) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
async removeBreadcrumbs(processId: number) {
|
||||
const resultCrumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'results'])
|
||||
|
||||
@@ -88,24 +88,29 @@
|
||||
class="page-customer-order-item__item-buyer-number desktop-small:text-h3 justify-self-end font-bold"
|
||||
[class.page-customer-order-item__item-buyer-number-main]="!mainOutletActive"
|
||||
>
|
||||
{{ item?.buyerNumber }}
|
||||
<ng-container *ngIf="item?.compartmentCode; else buyerNumber"
|
||||
>{{ item?.compartmentCode }}{{ item?.compartmentInfo && '_' + item?.compartmentInfo }}</ng-container
|
||||
>
|
||||
<ng-template #buyerNumber>{{ item?.buyerNumber }}</ng-template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="page-customer-order-item__item-processing-status flex flex-row font-bold desktop-small:text-p2 justify-self-end self-center"
|
||||
class="page-customer-order-item__item-processing-paid-status flex flex-col font-bold desktop-small:text-p2 justify-self-end self-center"
|
||||
>
|
||||
<shared-icon
|
||||
class="flex items-center justify-center mr-1"
|
||||
[size]="16"
|
||||
*ngIf="item.processingStatus | processingStatus: 'icon'; let icon"
|
||||
[icon]="icon"
|
||||
></shared-icon>
|
||||
{{ item.processingStatus | processingStatus }}
|
||||
</div>
|
||||
<div class="page-customer-order-item__item-processing-status flex flex-row mb-[0.375rem]">
|
||||
<shared-icon
|
||||
class="flex items-center justify-center mr-1"
|
||||
[size]="16"
|
||||
*ngIf="item.processingStatus | processingStatus: 'icon'; let icon"
|
||||
[icon]="icon"
|
||||
></shared-icon>
|
||||
{{ item.processingStatus | processingStatus }}
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-item__item-paid flex flex-row justify-self-end self-center">
|
||||
<div class="font-bold w-fit desktop-small:text-p2 px-3 py-[0.125rem] rounded text-white bg-[#26830C]" *ngIf="item.features?.paid">
|
||||
{{ item.features?.paid }}
|
||||
<div class="page-customer-order-item__item-paid flex flex-row self-end">
|
||||
<div class="font-bold w-fit desktop-small:text-p2 px-3 py-[0.125rem] rounded text-white bg-[#26830C]" *ngIf="item.features?.paid">
|
||||
{{ item.features?.paid }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
grid-template-columns: 3.125rem auto;
|
||||
grid-template-rows: 2rem 2rem 10px auto;
|
||||
grid-template-areas:
|
||||
'buyernumber buyernumber processingstatus processingstatus'
|
||||
'buyernumber buyernumber paid paid'
|
||||
'buyernumber buyernumber status status'
|
||||
'buyernumber buyernumber status status'
|
||||
'separator separator separator separator'
|
||||
'thumbnail title title title'
|
||||
'thumbnail title title title'
|
||||
@@ -27,8 +27,8 @@
|
||||
grid-template-columns: 3.125rem 30% 17.5% 27.5% auto;
|
||||
grid-template-areas:
|
||||
'thumbnail title format date buyernumber'
|
||||
'thumbnail title ean quantity processingstatus'
|
||||
'thumbnail comment price target paid';
|
||||
'thumbnail title ean quantity status'
|
||||
'thumbnail comment price target status';
|
||||
}
|
||||
|
||||
.page-customer-order-item__item-thumbnail {
|
||||
@@ -79,12 +79,8 @@
|
||||
@apply justify-self-start self-center;
|
||||
}
|
||||
|
||||
.page-customer-order-item__item-processing-status {
|
||||
grid-area: processingstatus;
|
||||
}
|
||||
|
||||
.page-customer-order-item__item-paid {
|
||||
grid-area: paid;
|
||||
.page-customer-order-item__item-processing-paid-status {
|
||||
grid-area: status;
|
||||
}
|
||||
|
||||
.page-customer-order-item__item-select-bullet {
|
||||
|
||||
@@ -78,7 +78,6 @@ export class CustomerOrderItemComponent extends ComponentStore<CustomerOrderItem
|
||||
const orderNumber = this.item?.orderNumber;
|
||||
const processingStatus = this.item?.processingStatus;
|
||||
const compartmentCode = this.item?.compartmentCode;
|
||||
|
||||
return this._navigationService.getCustomerOrdersDetailsPath({
|
||||
processId: this._applicationService.activatedProcessId,
|
||||
processingStatus,
|
||||
|
||||
@@ -129,8 +129,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
return this._navigationService.getCustomerOrdersResultsAndFilterPath({
|
||||
processId: this._application.activatedProcessId,
|
||||
processingStatus,
|
||||
compartmentCode: compartmentCode ? encodeURIComponent(compartmentCode) : undefined,
|
||||
orderNumber: orderNumber ? encodeURIComponent(orderNumber) : undefined,
|
||||
compartmentCode: compartmentCode ?? undefined,
|
||||
orderNumber: orderNumber ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,6 +173,19 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// Aktualisiere die Result Liste über actionHandled oder specialCommentChanged in den CustomerOrderDetails
|
||||
// updateResults wird über actionHandled oder specialCommentChanged in die queryParams überführt
|
||||
this._searchResultSubscription.add(
|
||||
this._activatedRoute.queryParams.subscribe((queryParams) => {
|
||||
const updateResults = queryParams.updateResults;
|
||||
if (!!updateResults) {
|
||||
this.search();
|
||||
const clean = { ...queryParams };
|
||||
delete clean['updateResults'];
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._searchResultSubscription.add(
|
||||
this.processId$
|
||||
.pipe(
|
||||
@@ -238,6 +251,19 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
);
|
||||
|
||||
this._customerOrderSearchStore.searchResultClearedSubject.pipe(takeUntil(this._onDestroy$)).subscribe((_) => this.clearSelectedItems());
|
||||
|
||||
// #4143 To make Splitscreen Search and Filter work combined
|
||||
this._searchResultSubscription.add(
|
||||
this._customerOrderSearchStore.searchStarted.subscribe(async (_) => {
|
||||
const queryParams = {
|
||||
...this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()),
|
||||
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
|
||||
};
|
||||
|
||||
this._customerOrderSearchStore.setQueryParams(queryParams);
|
||||
await this._customerOrderSearchStore.loadDefaultSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -250,6 +276,20 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
|
||||
}
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||
if (clean[key] == undefined) {
|
||||
delete clean[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
async removeBreadcrumbs(processId: number) {
|
||||
const detailsCrumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'details'])
|
||||
@@ -258,10 +298,19 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
|
||||
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'edit']).pipe(first()).toPromise();
|
||||
|
||||
const historyCrumbs = await this._breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'history'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
editCrumbs.forEach((crumb) => {
|
||||
this._breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
|
||||
historyCrumbs.forEach((crumb) => {
|
||||
this._breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
|
||||
detailsCrumbs.forEach((crumb) => {
|
||||
this._breadcrumb.removeBreadcrumb(crumb.id, true);
|
||||
});
|
||||
@@ -330,7 +379,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
|
||||
search(filter?: Filter) {
|
||||
if (!!filter) {
|
||||
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||
this._customerOrderSearchStore.setFilter(filter);
|
||||
}
|
||||
|
||||
this._customerOrderSearchStore.search({ clear: true });
|
||||
|
||||
@@ -10,11 +10,12 @@ import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||
import { UiScrollContainerModule } from '@ui/scroll-container';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CustomerOrderItemComponent } from './customer-order-item.component';
|
||||
import { FilterModule } from '@shared/components/filter';
|
||||
import { FilterAutocompleteProvider, FilterModule } from '@shared/components/filter';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { ProductImageModule } from '@cdn/product-image';
|
||||
import { ProcessingStatusPipe } from './customer-order-processing-status.pipe';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CustomerOrderPipesModule } from '../../pipes';
|
||||
import { CustomerOrderSearchMainAutocompleteProvider } from '../providers';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -28,6 +29,7 @@ import { FormsModule } from '@angular/forms';
|
||||
IconModule,
|
||||
UiSpinnerModule,
|
||||
UiScrollContainerModule,
|
||||
CustomerOrderPipesModule,
|
||||
],
|
||||
exports: [CustomerOrderSearchResultsComponent, CustomerOrderItemComponent],
|
||||
declarations: [
|
||||
@@ -35,7 +37,13 @@ import { FormsModule } from '@angular/forms';
|
||||
CustomerOrderItemSelectablePipe,
|
||||
CustomerOrderItemSelectedPipe,
|
||||
CustomerOrderItemComponent,
|
||||
ProcessingStatusPipe,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: FilterAutocompleteProvider,
|
||||
useClass: CustomerOrderSearchMainAutocompleteProvider,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CustomerOrderSearchResultsModule {}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './customer-order-item-selectable.pipe';
|
||||
export * from './customer-order-processing-status.pipe';
|
||||
export * from './customer-order-item-selectede.pipe';
|
||||
export * from './customer-order-search-results.component';
|
||||
export * from './customer-order-search-results.module';
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'addToPreviousCompartmentCodeLabelPipe',
|
||||
})
|
||||
export class AddToPreviousCompartmentCodeLabelPipe implements PipeTransform {
|
||||
transform(compartmentCode: string): string {
|
||||
const compartmentCodeSplit = compartmentCode?.split('_');
|
||||
compartmentCodeSplit?.shift();
|
||||
return compartmentCodeSplit?.join('_');
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SpectatorPipe, createPipeFactory } from '@ngneat/spectator';
|
||||
import { ProcessingStatusPipe } from './customer-order-processing-status.pipe';
|
||||
import { CustomerOrderProcessingStatusPipe } from './customer-order-processing-status.pipe';
|
||||
|
||||
describe('ProcessingStatusPipe', () => {
|
||||
let spectator: SpectatorPipe<ProcessingStatusPipe>;
|
||||
const createPipe = createPipeFactory(ProcessingStatusPipe);
|
||||
describe('CustomerOrderProcessingStatusPipe', () => {
|
||||
let spectator: SpectatorPipe<CustomerOrderProcessingStatusPipe>;
|
||||
const createPipe = createPipeFactory(CustomerOrderProcessingStatusPipe);
|
||||
|
||||
it('should return icon name (close) if type is icon', () => {
|
||||
spectator = createPipe(`{{ 1024 | processingStatus:'icon' }}`);
|
||||
@@ -37,7 +37,7 @@ export const ProcessingStatusNameMap = new Map<number, { value: string; disabled
|
||||
name: 'processingStatus',
|
||||
})
|
||||
@Injectable()
|
||||
export class ProcessingStatusPipe implements PipeTransform {
|
||||
export class CustomerOrderProcessingStatusPipe implements PipeTransform {
|
||||
icon = {
|
||||
16: 'done',
|
||||
128: 'done',
|
||||
7
apps/page/customer-order/src/lib/pipes/index.ts
Normal file
7
apps/page/customer-order/src/lib/pipes/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// start:ng42.barrel
|
||||
export * from './customer-order-processing-status.pipe';
|
||||
export * from './notifications-channel.pipe';
|
||||
export * from './payment-type.pipe';
|
||||
export * from './pipes.module';
|
||||
export * from './add-to-prevoius-compartment-code-label.pipe';
|
||||
// end:ng42.barrel
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { NotificationChannel } from '@swagger/oms';
|
||||
|
||||
@Pipe({
|
||||
name: 'notificationsChannel',
|
||||
})
|
||||
export class CustomerOrderNotificationsChannelPipe implements PipeTransform {
|
||||
static channels = new Map<NotificationChannel, string>([
|
||||
[1, 'E-Mail'],
|
||||
[2, 'SMS'],
|
||||
[4, 'Telefon'],
|
||||
[8, 'Fax'],
|
||||
[16, 'Brief'],
|
||||
]);
|
||||
|
||||
transform(value: NotificationChannel = 0): string {
|
||||
const result: string[] = [];
|
||||
|
||||
for (const [channel, name] of CustomerOrderNotificationsChannelPipe.channels) {
|
||||
if (value & channel) {
|
||||
result.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
return result.join(' | ');
|
||||
}
|
||||
}
|
||||
29
apps/page/customer-order/src/lib/pipes/payment-type.pipe.ts
Normal file
29
apps/page/customer-order/src/lib/pipes/payment-type.pipe.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
const paymentTypeNames = {
|
||||
0: '',
|
||||
1: 'bei Abholung',
|
||||
2: 'Kostenfrei',
|
||||
4: 'Bar',
|
||||
8: 'Bankeinzug',
|
||||
16: 'Einzugsermächtigung',
|
||||
32: 'EC-Karte',
|
||||
64: 'Kreditkarte',
|
||||
128: 'Gegen Rechnung',
|
||||
256: 'Vorauskasse',
|
||||
512: 'Gutschein',
|
||||
1024: 'Sammelrechnung',
|
||||
2048: 'PayPal',
|
||||
4069: 'InstantTransfer',
|
||||
8192: 'PayOnDelivery',
|
||||
16384: 'BonusCard',
|
||||
};
|
||||
|
||||
@Pipe({
|
||||
name: 'paymentType',
|
||||
})
|
||||
export class CustomerOrderPaymentTypePipe implements PipeTransform {
|
||||
transform(value: any, ...args: any[]): any {
|
||||
return paymentTypeNames[value] || '-';
|
||||
}
|
||||
}
|
||||
24
apps/page/customer-order/src/lib/pipes/pipes.module.ts
Normal file
24
apps/page/customer-order/src/lib/pipes/pipes.module.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { CustomerOrderProcessingStatusPipe } from './customer-order-processing-status.pipe';
|
||||
import { CustomerOrderPaymentTypePipe } from './payment-type.pipe';
|
||||
import { CustomerOrderNotificationsChannelPipe } from './notifications-channel.pipe';
|
||||
import { AddToPreviousCompartmentCodeLabelPipe } from './add-to-prevoius-compartment-code-label.pipe';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
exports: [
|
||||
CustomerOrderProcessingStatusPipe,
|
||||
CustomerOrderPaymentTypePipe,
|
||||
CustomerOrderNotificationsChannelPipe,
|
||||
AddToPreviousCompartmentCodeLabelPipe,
|
||||
],
|
||||
declarations: [
|
||||
CustomerOrderProcessingStatusPipe,
|
||||
CustomerOrderPaymentTypePipe,
|
||||
CustomerOrderNotificationsChannelPipe,
|
||||
AddToPreviousCompartmentCodeLabelPipe,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
export class CustomerOrderPipesModule {}
|
||||
@@ -126,9 +126,9 @@ export class FilterInputGroupMainComponent implements OnInit, OnDestroy, AfterVi
|
||||
switchMap(() => this.autocompleteProvider.complete(this.uiInput).pipe(takeUntil(this._cancelComplete))),
|
||||
tap((complete) => {
|
||||
if (complete?.length > 0) {
|
||||
this.autocompleteComponent.open();
|
||||
this.autocompleteComponent?.open();
|
||||
} else {
|
||||
this.autocompleteComponent.close();
|
||||
this.autocompleteComponent?.close();
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -163,6 +163,7 @@ export class FilterInputGroupMainComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
emitSearch(query: string) {
|
||||
setTimeout(() => {
|
||||
this.autocompleteComponent.close();
|
||||
this.search.emit(query);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
@@ -37,23 +37,49 @@ export class CustomerOrdersNavigationService extends NavigationService {
|
||||
compartmentCode?: string;
|
||||
orderNumber?: string;
|
||||
}): any[] {
|
||||
return [
|
||||
'/kunde',
|
||||
processId,
|
||||
'order',
|
||||
{
|
||||
outlets: {
|
||||
primary: 'filter',
|
||||
main: null,
|
||||
left: 'results',
|
||||
right: !!compartmentCode
|
||||
? ['filter', compartmentCode, processingStatus]
|
||||
: !!orderNumber
|
||||
? ['filter', orderNumber, processingStatus]
|
||||
: 'filter',
|
||||
if (!!compartmentCode) {
|
||||
return [
|
||||
'/kunde',
|
||||
processId,
|
||||
'order',
|
||||
{
|
||||
outlets: {
|
||||
primary: 'filter',
|
||||
main: null,
|
||||
left: 'results',
|
||||
right: ['filter', 'compartment', compartmentCode, processingStatus],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
} else if (!!orderNumber) {
|
||||
return [
|
||||
'/kunde',
|
||||
processId,
|
||||
'order',
|
||||
{
|
||||
outlets: {
|
||||
primary: 'filter',
|
||||
main: null,
|
||||
left: 'results',
|
||||
right: ['filter', 'order', orderNumber, processingStatus],
|
||||
},
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'/kunde',
|
||||
processId,
|
||||
'order',
|
||||
{
|
||||
outlets: {
|
||||
primary: 'filter',
|
||||
main: null,
|
||||
left: 'results',
|
||||
right: 'filter',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
getCustomerOrdersDetailsPath({
|
||||
@@ -140,6 +166,48 @@ export class CustomerOrdersNavigationService extends NavigationService {
|
||||
}
|
||||
}
|
||||
|
||||
getCustomerOrdersHistoryPath({
|
||||
processId,
|
||||
processingStatus,
|
||||
compartmentCode,
|
||||
orderNumber,
|
||||
}: {
|
||||
processId: number;
|
||||
processingStatus: number;
|
||||
compartmentCode?: string;
|
||||
orderNumber?: string;
|
||||
}): any[] {
|
||||
if (!!compartmentCode) {
|
||||
return [
|
||||
'/kunde',
|
||||
processId,
|
||||
'order',
|
||||
{
|
||||
outlets: {
|
||||
primary: ['details', 'compartment', compartmentCode, processingStatus, 'history'],
|
||||
main: null,
|
||||
left: ['results', compartmentCode, processingStatus],
|
||||
right: ['details', 'compartment', compartmentCode, processingStatus, 'history'],
|
||||
},
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'/kunde',
|
||||
processId,
|
||||
'order',
|
||||
{
|
||||
outlets: {
|
||||
primary: ['details', 'order', orderNumber, processingStatus, 'history'],
|
||||
main: null,
|
||||
left: ['results', orderNumber, processingStatus],
|
||||
right: ['details', 'order', orderNumber, processingStatus, 'history'],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
async navigateToCustomerOrdersSearch({
|
||||
processId,
|
||||
queryParams,
|
||||
@@ -215,4 +283,26 @@ export class CustomerOrdersNavigationService extends NavigationService {
|
||||
queryParamsHandling,
|
||||
});
|
||||
}
|
||||
|
||||
async navigateToHistory({
|
||||
processId,
|
||||
processingStatus,
|
||||
compartmentCode,
|
||||
orderNumber,
|
||||
queryParams,
|
||||
queryParamsHandling,
|
||||
}: {
|
||||
processId: number;
|
||||
processingStatus: number;
|
||||
compartmentCode?: string;
|
||||
orderNumber?: string;
|
||||
queryParams?: Record<string, string>;
|
||||
queryParamsHandling?: 'merge' | 'preserve' | '';
|
||||
}) {
|
||||
await this._navigateTo({
|
||||
routerLink: this.getCustomerOrdersHistoryPath({ processId, processingStatus, compartmentCode, orderNumber }),
|
||||
queryParams,
|
||||
queryParamsHandling,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user