Merged PR 1295: Merge Develop into Release2.0

Related work items: #905, #3040, #3175, #3179, #3189, #3212
This commit is contained in:
Lorenz Hilpert
2022-06-24 14:54:23 +00:00
40 changed files with 536 additions and 311 deletions

View File

@@ -140,17 +140,21 @@ export class DomainRemissionService {
)
);
} else if (arg.source === 'Abteilungsremission') {
result$ = this.getCurrentStock().pipe(
switchMap((stock) =>
this.getItemsForAbteilungsremission({
queryToken: {
stockId: stock.id,
supplierId: arg.supplierId,
...arg.queryToken,
},
})
)
);
if (!arg.queryToken.filter.abteilungen) {
result$ = of({ hits: 0, result: [] });
} else {
result$ = this.getCurrentStock().pipe(
switchMap((stock) =>
this.getItemsForAbteilungsremission({
queryToken: {
stockId: stock.id,
supplierId: arg.supplierId,
...arg.queryToken,
},
})
)
);
}
} else {
this._logger.error('Unbekannte Quelle', arg.source);
return throwError(new Error(`Unknown source: ${arg.source}`));
@@ -226,18 +230,14 @@ export class DomainRemissionService {
let { remainingQuantity, remissionQuantity } = item;
if (!remissionQuantity) {
remissionQuantity = inStock - (remainingQuantity || 0);
if (remissionQuantity < 0) {
remissionQuantity = 0;
}
remissionQuantity = inStock - (remainingQuantity || 0);
if (remissionQuantity < 0) {
remissionQuantity = 0;
}
if (!remainingQuantity) {
remainingQuantity = inStock - (remissionQuantity || 0);
if (remainingQuantity < 0) {
remainingQuantity = 0;
}
remainingQuantity = inStock - (remissionQuantity || 0);
if (remainingQuantity < 0) {
remainingQuantity = 0;
}
return { ...item, remainingQuantity, remissionQuantity, inStock };

View File

@@ -8,6 +8,7 @@ import { NotificationsHub } from '@hub/notifications';
import packageInfo from 'package';
import { interval, Observable, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Platform } from '@angular/cdk/platform';
@Component({
selector: 'app-root',
@@ -36,7 +37,8 @@ export class AppComponent implements OnInit {
@Inject(DOCUMENT) private readonly _document: Document,
private readonly _renderer: Renderer2,
private readonly _swUpdate: SwUpdate,
private readonly _notifications: NotificationsHub
private readonly _notifications: NotificationsHub,
private readonly _platform: Platform
) {
this.updateClient();
}
@@ -44,6 +46,7 @@ export class AppComponent implements OnInit {
ngOnInit() {
this.setTitle();
this.logVersion();
this.determinePlatform();
this._appService.getSection$().subscribe(this.sectionChangeHandler.bind(this));
}
@@ -56,6 +59,18 @@ export class AppComponent implements OnInit {
console.log(`%c${this._config.get('title')}\r\nVersion: ${packageInfo.version}`, 'font-weight: bold; font-size: 20px;');
}
determinePlatform() {
if (this._platform.IOS && !this._platform.SAFARI) {
this._renderer.addClass(this._document.body, 'tablet');
this._renderer.addClass(this._document.body, 'tablet-native');
} else if (this._platform.IOS && this._platform.SAFARI) {
this._renderer.addClass(this._document.body, 'tablet');
this._renderer.addClass(this._document.body, 'tablet-browser');
} else if (this._platform.isBrowser) {
this._renderer.addClass(this._document.body, 'desktop');
}
}
sectionChangeHandler(section: string) {
if (section === 'customer') {
this._renderer.removeClass(this._document.body, 'branch');

View File

@@ -2,6 +2,7 @@ import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, ErrorHandler, Injector, LOCALE_ID, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PlatformModule } from '@angular/cdk/platform';
import { Config, ConfigModule, JsonConfigLoader } from '@core/config';
import { AuthModule, AuthService } from '@core/auth';
@@ -81,6 +82,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
registrationStrategy: 'registerWhenStable:30000',
}),
ScanAdapterModule.forRoot(!environment.production),
PlatformModule,
],
providers: [
{

View File

@@ -1,6 +1,9 @@
body.branch {
--bg-color: #edeff0;
// @isa-app/scrollbar
--scrollbar-color: #596470;
// @shell/header
--shell-header-button-color: #89949e;
--shell-header-button-color-active: #586470;

View File

@@ -1,6 +1,9 @@
body.customer {
--bg-color: #e6eff9;
// @isa-app/scrollbar
--scrollbar-color: #1f466c;
// @shell/header
--shell-header-button-color: #9db2c6;
--shell-header-button-color-active: #557596;

View File

@@ -21,3 +21,23 @@ body {
width: 0; // remove scrollbar space
background: transparent; // optional: just make scrollbar invisible */
}
.desktop .scroll-bar::-webkit-scrollbar,
.desktop pdf-viewer ::-webkit-scrollbar {
width: 12px;
background-color: transparent;
}
.desktop .scroll-bar::-webkit-scrollbar-thumb,
.desktop pdf-viewer ::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgb(0 0 0 / 10%);
background-color: var(--scrollbar-color);
}
.desktop .scroll-bar::-webkit-scrollbar-track,
.desktop pdf-viewer ::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 4px rgb(0 0 0 / 10%);
border-radius: 10px;
background-color: white;
}

View File

@@ -5,7 +5,7 @@
<cdk-virtual-scroll-viewport
#scrollContainer
class="product-list scroll-bar"
class="product-list scroll-bar scroll-bar-margin"
[itemSize]="187"
minBufferPx="1200"
maxBufferPx="1200"

View File

@@ -44,3 +44,9 @@
@apply bg-brand text-white;
}
}
::ng-deep .desktop page-search-results {
.scroll-bar-margin::-webkit-scrollbar-track {
margin-bottom: 0.5rem;
}
}

View File

@@ -7,7 +7,7 @@ import { DomainCheckoutService } from '@domain/checkout';
import { ItemDTO } from '@swagger/cat';
import { AddToShoppingCartDTO } from '@swagger/checkout';
import { UiFilter } from '@ui/filter';
import { UiErrorModalComponent, UiModalRef, UiModalService } from '@ui/modal';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { CacheService } from 'apps/core/cache/src/public-api';
import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';

View File

@@ -1,132 +1,145 @@
<div class="tags">
<img
*ngIf="hasPrebooked"
[uiOverlayTrigger]="prebookedTooltip"
class="tag preorder"
src="/assets/images/tag_icon_preorder.svg"
alt="Vorbesteller"
/>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #prebookedTooltip [closeable]="true">
Artikel wird für Sie vorgemerkt.
</ui-tooltip>
</div>
<div class="row">
<div class="picture">
<img src="{{ imageUrl | async }}" alt="book" class="thumbnail" />
<div *ngFor="let subsetItem of subsetItems; index as i">
<div class="tags">
<img
*ngIf="subsetItem.isPrebooked"
[uiOverlayTrigger]="prebookedTooltip"
class="tag preorder"
src="/assets/images/tag_icon_preorder.svg"
alt="Vorbesteller"
/>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #prebookedTooltip [closeable]="true">
Artikel wird für Sie vorgemerkt.
</ui-tooltip>
</div>
<div class="details">
<div class="row title">
<span class="name">
{{ orderItem.product.contributors }}
<span *ngIf="orderItem.product.contributors && orderItem.product.name">-</span>
{{ orderItem.product.name }}</span
>
<div class="row">
<div class="picture">
<img src="{{ imageUrl | async }}" alt="book" class="thumbnail" />
</div>
<div class="row">
<span class="label">Status</span>
<span>{{ subsetItem.processingStatus | orderStatus }} </span>
</div>
<div class="row">
<span class="label">Menge</span>
<span>{{ orderItem.quantity?.quantity }}x</span>
</div>
<div class="row">
<span class="label">Preis</span>
<span>{{ orderItem.grossPrice?.value?.value | currency: ' ' }} {{ orderItem.grossPrice?.value?.currency }}</span>
</div>
<div class="row" *ngIf="subsetItem.estimatedShippingDate">
<span
class="label"
*ngIf="
orderItem.features.orderType === 'Versand' ||
orderItem.features.orderType === 'B2B-Versand' ||
orderItem.features.orderType === 'DIG-Versand'
"
>Lieferung ab</span
>
<span class="label" *ngIf="orderItem.features.orderType === 'Abholung' || orderItem.features.orderType === 'Rücklage'"
>Abholung ab</span
>
<span>{{ subsetItem.estimatedShippingDate | date: 'dd.MM.yyyy' }}</span>
</div>
<span *ngIf="!expand" class="expand">
<button class="arrow-button" (click)="expand = true">
Mehr
<ui-icon icon="arrow" size="16px"></ui-icon>
</button>
</span>
<ng-container *ngIf="expand">
<div class="row">
<span class="label">Abholfachnummer</span>
<span>{{ subsetItem.compartmentCode || '-' }} </span>
<div class="details">
<div class="row title">
<span class="name">
{{ orderItem.product.contributors }}
<span *ngIf="orderItem.product.contributors && orderItem.product.name">-</span>
{{ orderItem.product.name }}</span
>
</div>
<div class="row">
<span class="label">ISBN</span>
<span>{{ orderItem.product.ean }}</span>
<span class="label">Status</span>
<span>{{ subsetItem.processingStatus | orderStatus }} </span>
</div>
<div class="row">
<span class="label">Lieferant</span>
<span>{{ subsetItem.supplier?.data?.name || '-' }} </span>
<span class="label">Menge</span>
<span>{{ subsetItem.quantity }}x</span>
</div>
<div class="row">
<span class="label">MwSt</span>
<span>{{ orderItem.grossPrice?.vat?.inPercent }}%</span>
<span class="label">Preis</span>
<span>{{ orderItem.grossPrice?.value?.value | currency: ' ' }} {{ orderItem.grossPrice?.value?.currency }}</span>
</div>
<div class="row">
<span class="label">Zahlungsart</span>
<span>{{ order.paymentType | paymentType }}</span>
<div class="row" *ngIf="subsetItem.estimatedShippingDate">
<ng-container
*ngIf="
orderItem.features.orderType === 'Versand' ||
orderItem.features.orderType === 'B2B-Versand' ||
orderItem.features.orderType === 'DIG-Versand'
"
>
<span class="label">Lieferung ab</span><span>{{ subsetItem.estimatedShippingDate | date: 'dd.MM.yyyy' }}</span>
</ng-container>
<ng-container *ngIf="orderItem.features.orderType === 'Abholung' || orderItem.features.orderType === 'Rücklage'">
<span class="label">Abholung ab</span>
<span>{{ subsetItem.estimatedShippingDate | date: 'dd.MM.yyyy' }}</span>
</ng-container>
</div>
<ng-container
*ngIf="
orderItem.features.orderType === 'Versand' ||
orderItem.features.orderType === 'B2B-Versand' ||
orderItem.features.orderType === 'DIG-Versand'
"
>
<div class="row">
<span class="label">Rechnungsadresse</span>
<span>{{ order?.billing?.data | address }}</span>
</div>
<div class="row">
<span class="label">Lieferadresse</span>
<span>{{ order.shipping?.data | address }}</span>
</div>
</ng-container>
<div class="row" *ngIf="ssc">
<span class="label">Meldenummer</span>
<span>{{ ssc }}</span>
</div>
<div class="row">
<span class="label">Anmerkung</span>
<span>{{ subsetItem.specialComment || '-' }}</span>
</div>
<span class="collapse">
<button class="arrow-button" (click)="expand = false">
<ui-icon icon="arrow" size="16px" rotate="180deg"></ui-icon>
Weniger
<span *ngIf="!expand[i]" class="expand">
<button class="arrow-button" (click)="expand[i] = true">
<span class="relative top-px-3">Mehr</span>
<ui-icon icon="arrow" size="16px"></ui-icon>
</button>
</span>
</ng-container>
<div class="history-wrapper">
<button class="cta-history" (click)="openHistory()">Historie</button>
<ng-container *ngIf="expand[i]">
<div class="row">
<span class="label">Abholfachnummer</span>
<span>{{ subsetItem.compartmentCode || '-' }} </span>
</div>
<div class="row">
<span class="label">ISBN</span>
<span>{{ orderItem.product.ean }}</span>
</div>
<div *ngIf="orderItem?.product?.formatDetail" class="row item-format">
<span class="label">Format</span>
<img
*ngIf="orderItem?.product?.format !== '--'"
loading="lazy"
src="assets/images/Icon_{{ orderItem?.product?.format }}.svg"
[alt]="orderItem?.product?.formatDetail"
/>
<span>{{ orderItem?.product?.formatDetail }}</span>
</div>
<div class="row">
<span class="label">Lieferant</span>
<span>{{ subsetItem.supplier?.data?.name || '-' }} </span>
</div>
<div class="row">
<span class="label">MwSt</span>
<span>{{ orderItem.grossPrice?.vat?.inPercent }}%</span>
</div>
<div class="row">
<span class="label">Zahlungsart</span>
<span>{{ order.paymentType | paymentType }}</span>
</div>
<ng-container
*ngIf="
orderItem.features.orderType === 'Versand' ||
orderItem.features.orderType === 'B2B-Versand' ||
orderItem.features.orderType === 'DIG-Versand'
"
>
<div class="row">
<span class="label">Rechnungsadresse</span>
<span>{{ order?.billing?.data | address }}</span>
</div>
<div class="row">
<span class="label">Lieferadresse</span>
<span>{{ order.shipping?.data | address }}</span>
</div>
</ng-container>
<div class="row" *ngIf="ssc(subsetItem)">
<span class="label">Meldenummer</span>
<span>{{ ssc(subsetItem) }}</span>
</div>
<div class="row">
<span class="label">Anmerkung</span>
<span>{{ subsetItem.specialComment || '-' }}</span>
</div>
<span class="collapse">
<button class="arrow-button" (click)="expand[i] = false">
<ui-icon icon="arrow" size="16px" rotate="180deg"></ui-icon>
<span class="relative top-px-3">Weniger</span>
</button>
</span>
</ng-container>
<div class="history-wrapper">
<button class="cta-history" (click)="openHistory(subsetItem.id)">Historie</button>
</div>
</div>
</div>
</div>

View File

@@ -53,7 +53,7 @@
@apply mt-1 -ml-2;
}
img {
.picture img {
@apply rounded-md;
box-shadow: 0 0 18px 0 #b8b3b7;
height: 100px;
@@ -68,6 +68,14 @@ img {
@apply text-base font-bold text-brand outline-none border-none bg-transparent mr-1;
}
.item-format {
@apply flex flex-row items-center whitespace-nowrap;
img {
@apply mr-2;
}
}
// .row:first-child {
// @apply flex-grow;
// }

View File

@@ -17,38 +17,18 @@ export class CustomerOrderItemCardComponent implements OnInit {
@Input()
orderItem: OrderItemDTO;
get hasPrebooked() {
return this.orderItem?.subsetItems.some((si) => si?.data?.isPrebooked);
}
imageUrl: Observable<string>;
expand: boolean;
expand: boolean[];
get subsetItem(): OrderItemSubsetDTO {
if (this.orderItem.subsetItems?.length > 0) {
return this.orderItem.subsetItems[0]?.data;
}
}
get ssc(): string {
if (this.subsetItem) {
if (this.subsetItem.ssc) {
return `${this.subsetItem.ssc} - ${this.subsetItem.sscText}`;
} else {
return this.subsetItem.sscText;
}
}
get subsetItems(): OrderItemSubsetDTO[] {
return this.orderItem.subsetItems.map((subsetItem) => subsetItem.data);
}
constructor(private imageService: ProductImageService, private _modal: UiModalService) {}
ngOnInit() {
// this.imageUrl = this.imageService.getImageUrl(this.orderItem.product.ean, {
// width: 60,
// height: 100,
// });
this.expand = new Array(this.subsetItems.length);
this.imageUrl = of(
this.imageService.getImageUrl({
imageId: this.orderItem.product.ean,
@@ -58,7 +38,15 @@ export class CustomerOrderItemCardComponent implements OnInit {
);
}
openHistory() {
ssc(subsetItem: OrderItemSubsetDTO) {
if (subsetItem.ssc) {
return `${subsetItem.ssc} - ${subsetItem.sscText}`;
} else {
return subsetItem.sscText;
}
}
openHistory(orderItemSubsetId: number) {
this._modal.open({
content: HistoryComponent,
title: 'Historie',
@@ -67,7 +55,7 @@ export class CustomerOrderItemCardComponent implements OnInit {
mode: 'order',
item: {
order: this.order,
orderItemSubsetId: this.subsetItem.id,
orderItemSubsetId,
},
},
});

View File

@@ -18,3 +18,9 @@ a {
@apply text-lg text-brand font-bold;
}
}
::ng-deep .desktop page-customer-search-result ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 250px;
}
}

View File

@@ -60,3 +60,9 @@
shared-goods-in-out-order-group-item {
@apply cursor-pointer;
}
::ng-deep .desktop page-goods-in-cleanup-list ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 7.25rem;
}
}

View File

@@ -77,3 +77,9 @@ hr {
@apply bg-white text-brand;
}
}
::ng-deep .desktop page-goods-in-list ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 7.25rem;
}
}

View File

@@ -60,3 +60,9 @@ shared-goods-in-out-order-group-item {
::ng-deep page-goods-in-remission-preview ui-scroll-container .cta-scroll {
bottom: 35px !important;
}
::ng-deep .desktop page-goods-in-remission-preview ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 7.25rem;
}
}

View File

@@ -60,3 +60,9 @@ shared-goods-in-out-order-group-item {
::ng-deep page-goods-in-reservation ui-scroll-container .cta-scroll {
bottom: 35px !important;
}
::ng-deep .desktop page-goods-in-reservation ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 7.25rem;
}
}

View File

@@ -34,3 +34,9 @@ shared-goods-in-out-order-group-item {
::ng-deep page-goods-in-search-results ui-scroll-container .cta-scroll {
bottom: 35px !important;
}
::ng-deep .desktop page-goods-in-search-results ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 7.25rem;
}
}

View File

@@ -91,7 +91,7 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
}
});
this._goodsOutSearchStore.search();
this._goodsOutSearchStore.search({});
}
async updateBreadcrumb() {

View File

@@ -55,15 +55,15 @@ export class GoodsOutSearchComponent implements OnInit, OnDestroy {
ngOnInit() {
this._goodsOutSearchStore.loadSettings();
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((params) => {
// Reset Filter when query params are empty
this.processId$.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._activatedRoute.queryParams)).subscribe(([processId, params]) => {
if (params && Object.keys(params).length === 0) {
this._goodsOutSearchStore.setQueryParams(params);
this._goodsOutSearchStore.loadSettings();
} else {
// this._goodsOutSearchStore.resetFilter(params);
}
});
this.processId$.pipe(takeUntil(this._onDestroy$)).subscribe((processId) => {
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: 'Warenausgabe',

View File

@@ -1,11 +1,12 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { CacheService } from '@core/cache';
import { DomainGoodsService } from '@domain/oms';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ListResponseArgsOfOrderItemListItemDTO, OrderItemListItemDTO, QuerySettingsDTO } from '@swagger/oms';
import { UiFilter } from '@ui/filter';
import { isResponseArgs } from '@utils/object';
import { Subject } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { switchMap, mergeMap, withLatestFrom, filter, take, tap } from 'rxjs/operators';
export interface GoodsOutSearchState {
@@ -72,7 +73,7 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
readonly searchResultCleared = this._searchResultClearedSubject.asObservable();
constructor(private _domainGoodsInService: DomainGoodsService) {
constructor(private _domainGoodsInService: DomainGoodsService, private _cache: CacheService) {
super({
fetching: false,
hits: 0,
@@ -142,29 +143,46 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
}
}
searchRequest(options?: { take?: number; skip?: number }) {
searchRequest(options?: { take?: number; skip?: number; reload?: boolean }) {
return this.filter$.pipe(
filter((f) => f instanceof UiFilter),
take(1),
mergeMap((filter) =>
this._domainGoodsInService.searchWarenausgabe({
...filter.getQueryToken(),
skip: options?.skip ?? this.results.length,
take: options?.take ?? 50,
skip: options.reload ? 0 : options?.skip ?? this.results.length,
take: options.reload ? this.results.length : options?.take ?? 50,
})
)
);
}
search = this.effect(($) =>
$.pipe(
tap((_) => this.patchState({ fetching: true })),
search = this.effect((options$: Observable<{ siletReload?: boolean }>) =>
options$.pipe(
tap((options) => {
if (options.siletReload) {
this.patchState({ fetching: true });
}
}),
withLatestFrom(this.results$),
switchMap(([_, _results]) =>
this.searchRequest(this.searchOptions).pipe(
switchMap(([_options, _results]) => {
const queryToken = this.filter?.getQueryToken();
if (queryToken && this._cache.get(queryToken)) {
const cached = this._cache.get(queryToken);
this.patchState(cached);
}
return this.searchRequest({ ...this.searchOptions, reload: _options.siletReload }).pipe(
tapResponse(
(res) => {
const results = [...(_results ?? []), ...(res.result ?? [])];
let results: OrderItemListItemDTO[] = [];
if (_options.siletReload) {
results = res.result;
} else {
results = [...(_results ?? []), ...(res.result ?? [])];
}
this.patchState({
hits: res.hits,
@@ -172,6 +190,14 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
fetching: false,
});
if (queryToken) {
this._cache.set(queryToken, {
hits: res.hits,
results,
fetching: false,
});
}
this._searchResultSubject.next(res);
},
(err: Error) => {
@@ -187,8 +213,8 @@ export class GoodsOutSearchStore extends ComponentStore<GoodsOutSearchState> {
console.error('GoodsInSearchStore.search()', err);
}
)
)
)
);
})
)
);

View File

@@ -99,7 +99,7 @@ export class GoodsOutSearchMainComponent implements OnInit, OnDestroy {
this._cdr.markForCheck();
});
this._goodsOutSearchStore.search();
this._goodsOutSearchStore.search({});
}
async updateBreadcrumb(processId: number) {

View File

@@ -6,30 +6,32 @@
[deltaEnd]="150"
[itemLength]="itemLength$ | async"
>
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
<ng-container
*ngFor="let processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn; let lastProcessingStatus = last"
>
<ng-container *ngIf="processId$ | async; let processId">
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
<ng-container
*ngFor="let compartmentCodeGroup of processingStatusGroup.items | groupBy: byCompartmentCodeFn; let lastCompartmentCode = last"
*ngFor="let processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn; let lastProcessingStatus = last"
>
<shared-goods-in-out-order-group-item
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first"
[item]="item"
[showCompartmentCode]="firstItem"
(click)="navigateToDetails(item)"
[selectable]="item | goodsOutItemSelectable: selectionRules:selectedItems"
[selected]="item | goodsOutItemSelected: selectedOrderItemSubsetIds"
(selectedChange)="setSelectedItem(item, $event)"
></shared-goods-in-out-order-group-item>
<div class="divider" *ngIf="!lastCompartmentCode"></div>
<ng-container
*ngFor="let compartmentCodeGroup of processingStatusGroup.items | groupBy: byCompartmentCodeFn; let lastCompartmentCode = last"
>
<shared-goods-in-out-order-group-item
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
[item]="item"
[showCompartmentCode]="firstItem"
(click)="navigateToDetails(processId, item)"
[selectable]="item | goodsOutItemSelectable: selectionRules:selectedItems"
[selected]="item | goodsOutItemSelected: selectedOrderItemSubsetIds"
(selectedChange)="setSelectedItem(item, $event)"
></shared-goods-in-out-order-group-item>
<div class="divider" *ngIf="!lastCompartmentCode"></div>
</ng-container>
<div class="divider" *ngIf="!lastProcessingStatus"></div>
</ng-container>
<div class="divider" *ngIf="!lastProcessingStatus"></div>
<div class="divider" *ngIf="!lastOrderNumber"></div>
</ng-container>
<div class="divider" *ngIf="!lastOrderNumber"></div>
</ng-container>
</shared-goods-in-out-order-group>
</shared-goods-in-out-order-group>
</ng-container>
</ui-scroll-container>
<ng-template #emptyMessage>

View File

@@ -39,3 +39,9 @@ shared-goods-in-out-order-group-item {
@apply bg-white text-brand;
}
}
::ng-deep .desktop page-goods-out-search-results ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 7.25rem;
}
}

View File

@@ -1,5 +1,5 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { first, map, shareReplay, takeUntil } from 'rxjs/operators';
import { debounceTime, first, map, shareReplay, takeUntil, withLatestFrom } from 'rxjs/operators';
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
import { ActivatedRoute, Router } from '@angular/router';
import { GoodsOutSearchStore } from '../goods-out-search.store';
@@ -10,7 +10,6 @@ import { CommandService } from '@core/command';
import { OrderItemsContext } from '@domain/oms';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { UiScrollContainerComponent } from '@ui/scroll-container';
import { Config } from '@core/config';
export interface GoodsOutSearchResultsState {
selectedOrderItemSubsetIds: number[];
@@ -67,14 +66,12 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
byCompartmentCodeFn = (item: OrderItemListItemDTO) => item.compartmentCode;
get processId() {
return +this._activatedRoute.snapshot.parent.data.processId;
}
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
private _onDestroy$ = new Subject();
trackByFn = (item: OrderItemListItemDTO) => `${item.orderId}${item.orderItemId}${item.orderItemSubsetId}`;
constructor(
private _goodsOutSearchStore: GoodsOutSearchStore,
private _router: Router,
@@ -89,16 +86,22 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
}
ngOnInit() {
this.processId$.pipe(takeUntil(this._onDestroy$)).subscribe((processId) => {
this._goodsOutSearchStore.setQueryParams(this._activatedRoute.snapshot.queryParams);
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((queryParams) => {
this.updateBreadcrumb(queryParams);
});
this.processId$
.pipe(takeUntil(this._onDestroy$), debounceTime(1), withLatestFrom(this._activatedRoute.queryParams))
.subscribe(([processId, params]) => {
console.log('processId', processId);
console.log('params', params);
this.initInitialSearch(processId);
this.createBreadcrumb(processId);
this.removeBreadcrumbs(processId);
});
this._goodsOutSearchStore.setQueryParams(params);
this.updateBreadcrumb(processId, params);
this.initInitialSearch(processId, params);
this.createBreadcrumb(processId, params);
this.removeBreadcrumbs(processId);
this._goodsOutSearchStore.clearResults();
this._goodsOutSearchStore.search({ siletReload: true });
});
this._goodsOutSearchStore.searchResultCleared.pipe(takeUntil(this._onDestroy$)).subscribe((_) => this.clearSelectedItems());
}
@@ -107,10 +110,10 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
this._onDestroy$.next();
this._onDestroy$.complete();
this.updateBreadcrumb(this._goodsOutSearchStore.filter?.getQueryParams());
// this.updateBreadcrumb(this._goodsOutSearchStore.filter?.getQueryParams());
}
async removeBreadcrumbs(processId) {
async removeBreadcrumbs(processId: number) {
const detailsCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'details']).pipe(first()).toPromise();
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'edit']).pipe(first()).toPromise();
@@ -124,24 +127,24 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
});
}
async createBreadcrumb(processId: number) {
async createBreadcrumb(processId: number, params: Record<string, string>) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: this.getBreadcrumbName(),
name: this.getBreadcrumbName(params),
path: `/kunde/${processId}/goods/out/results`,
section: 'customer',
params: this._goodsOutSearchStore.filter?.getQueryParams(),
params,
tags: ['goods-out', 'results', 'filter'],
});
}
async updateBreadcrumb(queryParams: Record<string, string> = this._goodsOutSearchStore.filter?.getQueryParams()) {
async updateBreadcrumb(processId: number, queryParams: Record<string, string>) {
const scroll_position = this.scrollContainer?.scrollPos;
const take = this._goodsOutSearchStore.results?.length;
if (queryParams) {
const crumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this.processId, ['goods-out', 'results', 'filter'])
.getBreadcrumbsByKeyAndTags$(processId, ['goods-out', 'results', 'filter'])
.pipe(first())
.toPromise();
@@ -157,23 +160,23 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
}
}
getBreadcrumbName() {
const input = this._goodsOutSearchStore.filter?.getQueryParams()?.main_qs;
getBreadcrumbName(params: Record<string, string>) {
const input = params?.main_qs;
return input?.replace('ORD:', '') ?? 'Alle';
}
initInitialSearch(processId: number) {
initInitialSearch(processId: number, params: Record<string, string>) {
if (this._goodsOutSearchStore.hits === 0) {
this._goodsOutSearchStore.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.hits === 0) {
await this._router.navigate([`/kunde/${this.processId}/goods/out`], {
await this._router.navigate([`/kunde/${processId}/goods/out`], {
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
});
} else {
await this.createBreadcrumb(processId);
await this.createBreadcrumb(processId, params);
if (result.hits === 1) {
await this.navigateToDetails(result.result[0]);
await this.navigateToDetails(processId, result.result[0]);
} else {
if (!!this._goodsOutSearchStore.searchOptions?.take) {
this._goodsOutSearchStore.searchOptions = undefined;
@@ -182,7 +185,7 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
}
}
});
this._goodsOutSearchStore.search();
this._goodsOutSearchStore.search({});
}
const { scroll_position, take } = this._goodsOutSearchStore.queryParams;
@@ -193,21 +196,22 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
async loadMore() {
if (this._goodsOutSearchStore.hits > this._goodsOutSearchStore.results.length && !this._goodsOutSearchStore.fetching) {
this._goodsOutSearchStore.search();
this._goodsOutSearchStore.search({});
}
}
navigateToDetails(orderItem: OrderItemListItemDTO) {
navigateToDetails(processId: number, orderItem: OrderItemListItemDTO) {
console.log('navigateToDetails', orderItem);
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const compartmentCode = orderItem.compartmentCode;
if (compartmentCode) {
this._router.navigate([
`/kunde/${this.processId}/goods/out/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`,
`/kunde/${processId}/goods/out/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`,
]);
} else {
this._router.navigate([`/kunde/${this.processId}/goods/out/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`]);
this._router.navigate([`/kunde/${processId}/goods/out/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`]);
}
}

View File

@@ -7,6 +7,7 @@ import { UiModalService } from '@ui/modal';
import { NEVER, Subject } from 'rxjs';
import { catchError, switchMap, takeUntil, tap } from 'rxjs/operators';
import { AddProductModalComponent, AddProductModalData } from '../modals/add-product-modal';
import { RemissionListComponentStore } from '../remission-list/remission-list.component-store';
@Component({
selector: 'page-remission-add-product',
@@ -28,6 +29,7 @@ export class AddProductComponent implements OnInit, OnDestroy {
}
constructor(
private readonly _remissionListStore: RemissionListComponentStore,
private _remiService: DomainRemissionService,
private _modal: UiModalService,
private _breadcrumb: BreadcrumbService,
@@ -76,6 +78,7 @@ export class AddProductComponent implements OnInit, OnDestroy {
});
modal.afterClosed$.pipe(takeUntil(this._onDestroy)).subscribe((result) => {
if (result.data) {
this._remissionListStore._triggerReload$.next(true);
this._router.navigate(['..', 'list'], { relativeTo: this._activatedRoute, queryParams: this.queryParams });
}
});
@@ -103,7 +106,7 @@ export class AddProductComponent implements OnInit, OnDestroy {
addBreadcrumbIfNotExists() {
this._breadcrumb.addBreadcrumbIfNotExists({
key: this._config.get('process.ids.remission'),
name: 'Wannennummer',
name: 'Artikel hinzufügen',
path: '/filiale/remission/add-product',
section: 'branch',
params: this.queryParams,

View File

@@ -4,11 +4,13 @@
<p class="text-xl mt-4">
Um einen Warenbegleitschein zu <br />
eröffnen,
<ng-container *ngIf="supplierId === 2">
<ng-container *ngIf="supplierId === 2; else defaultText">
scannen Sie die Packstück-ID <br />
oder
oder lassen Sie diese automatisch generieren.
</ng-container>
lassen Sie diese automatisch generieren.
<ng-template #defaultText>
lassen Sie diesen automatisch generieren.
</ng-template>
</p>
</div>
<div class="text-center flex justify-center my-4">

View File

@@ -11,7 +11,7 @@ import {
import { UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
import { mapFromReturnItemDTO, mapFromReturnSuggestionDTO } from 'apps/domain/remission/src/lib/mappings';
import { BehaviorSubject, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { first, takeUntil } from 'rxjs/operators';
import { AddProductToShippingDocumentModalComponent } from '../../modals/add-product-to-shipping-document-modal/add-product-to-shipping-document-modal.component';
import { RemissionListComponentStore } from '../remission-list.component-store';
@@ -169,11 +169,26 @@ export class RemissionListItemComponent implements OnDestroy {
throw new Error('Item hat keinen passenden DTO Typ');
}
// Bei remittieren eines Stapels die StockInformation für alle anderen Stapel mit der selben EAN aktualisieren
if (placementType != 'Leistung') {
const items = await this._store.items$.pipe(first()).toPromise();
const itemsByEan = items?.filter(
(i) => i.dto.product.ean === this.item.dto.product.ean && i.dto.id !== this.item.dto.id && i.placementType === 'Stapel'
);
if (itemsByEan?.length > 0) {
const updatedItems = await this.updateStockInformation(itemsByEan);
updatedItems.forEach((i) => this._store.updateItem(i));
}
}
this._store.removeItem(this.item);
if (response && !!response?.item2) {
response.item2.placementType = placementType;
this.addItem(response.item2);
await this.addItem(response.item2);
}
this._store.updateCache();
} catch (err) {
this._modal.open({
@@ -201,7 +216,9 @@ export class RemissionListItemComponent implements OnDestroy {
updatedDto = await this._remissionService.returnSuggestion(this.item.dto?.id).toPromise();
}
if (updatedDto.impediment?.attempts > 3) {
if (updatedDto.impediment?.attempts === 1 && updatedDto.returnReason.startsWith('Abholfach:')) {
this._store.removeItem(this.item);
} else if (updatedDto.impediment?.attempts > 3) {
this._store.removeItem(this.item);
} else {
this._store.updateItemDto(updatedDto);
@@ -237,18 +254,21 @@ export class RemissionListItemComponent implements OnDestroy {
}
// Anzeige der Remi-Menge und des aktuellen Bestands aktualisieren
item = await this.updateStockInformation(item);
const items = await this.updateStockInformation([item]);
this._store.addItems([item]);
this._store.addItems(items);
}
async updateStockInformation(item: RemissionListItem) {
async updateStockInformation(items: RemissionListItem[]) {
try {
const res = (await this._remissionService.getStockInformation([item]).toPromise())?.find((_) => true);
item.remissionQuantity = res.remissionQuantity;
item.remainingQuantity = res.remainingQuantity;
item.inStock = res.inStock;
return item;
const res = await this._remissionService.getStockInformation(items).toPromise();
items.forEach((item) => {
const resForItem = res.find((r) => r.dto.id === item.dto.id);
item.remissionQuantity = resForItem?.remissionQuantity;
item.remainingQuantity = resForItem?.remainingQuantity;
item.inStock = resForItem?.inStock;
});
return items;
} catch (err) {
throw new Error('Fehler bei der Bestandsabfrage');
}

View File

@@ -35,8 +35,7 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
});
searchCompleted = this._searchCompleted.asObservable();
_sourceOrSupplierChange$ = new BehaviorSubject<boolean>(false);
_triggerReload$ = new BehaviorSubject<boolean>(false);
private _filterChange$ = new BehaviorSubject<boolean>(false);
@@ -154,11 +153,11 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
}
initSearch() {
combineLatest([this._sourceOrSupplierChange$, this._filterChange$])
combineLatest([this._triggerReload$, this._filterChange$])
.pipe(debounceTime(500), takeUntil(this._onDestroy$))
.subscribe(([change, filter]) => {
.subscribe(([reload, filter]) => {
const data = this.getCachedData();
if (change || data.items?.length === 0 || filter) {
if (reload || data.items?.length === 0 || filter) {
this.search({ newSearch: true });
}
});
@@ -167,7 +166,7 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
// tslint:disable: member-ordering
loadSuppliers = this.effect(($) =>
$.pipe(
tap((_) => (this.getCachedData()?.hits === 0 ? this.setFetching(true) : null)),
// tap((_) => (this.getCachedData()?.hits === 0 ? this.setFetching(true) : null)),
switchMap((_) =>
this._domainRemissionService.getSuppliers().pipe(
tapResponse(
@@ -181,7 +180,7 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
loadSources = this.effect(($) =>
$.pipe(
tap((_) => (this.getCachedData()?.hits === 0 ? this.setFetching(true) : null)),
// tap((_) => (this.getCachedData()?.hits === 0 ? this.setFetching(true) : null)),
switchMap((_) =>
this._domainRemissionService.getSources().pipe(
tapResponse(
@@ -195,7 +194,7 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
loadRequiredCapacities = this.effect(($) =>
$.pipe(
tap((_) => (this.getCachedData()?.hits === 0 ? this.setFetching(true) : null)),
// tap((_) => (this.getCachedData()?.hits === 0 ? this.setFetching(true) : null)),
withLatestFrom(this.selectedSupplier$, this.filter$),
switchMap(([_, supplier, filter]) => {
let departments = [];
@@ -215,7 +214,7 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
loadFilter = this.effect(($) =>
$.pipe(
tap((_) => (this.getCachedData()?.hits === 0 ? this.setFetching(true) : null)),
// tap((_) => (this.getCachedData()?.hits === 0 ? this.setFetching(true) : null)),
withLatestFrom(this.selectedSupplier$, this.selectedSource$, this._activatedRoute.queryParams),
filter(([, selectedSupplier, selectedSource]) => !!selectedSupplier?.id && !!selectedSource),
switchMap(([, selectedSupplier, selectedSource, queryParams]) =>
@@ -236,7 +235,13 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
}
this.setFilter(filter);
this._filterChange$.next(true);
// Only trigger search with _filterChange$ if no cached data available
// If cached data is available, there is no need to trigger the search
const data = this.getCachedData();
if (data.items?.length === 0) {
this._filterChange$.next(true);
}
},
(err) => {}
)
@@ -247,12 +252,13 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
search = this.effect((options$: Observable<{ newSearch?: boolean }>) =>
options$.pipe(
tap((options) => {
withLatestFrom(this.filter$, this.selectedSource$, this.selectedSupplier$, this.searchOptions$, this.items$),
filter(([, filter, source, supplier]) => !!supplier?.id && !!source && !!filter),
filter(([, filter, source]) => (source === 'Abteilungsremission' ? !!filter.getQueryToken().filter.abteilungen : true)),
tap(([options]) => {
this.setFetching(true);
options?.newSearch ? this.setSearchResult({ result: [], hits: 0 }) : null;
}),
withLatestFrom(this.filter$, this.selectedSource$, this.selectedSupplier$, this.searchOptions$, this.items$),
filter(([, filter, source, supplier]) => !!supplier?.id && !!source && !!filter),
switchMap(([options, filter, source, supplier, searchOptions, items]) =>
this._domainRemissionService
.getItems({
@@ -265,11 +271,13 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
supplierId: supplier.id,
})
.pipe(
// Nochmal letzten Stand der Items holen, da diese während dem Nachladen manipuliert werden können (remittieren)
withLatestFrom(this.items$),
tapResponse(
(res) => {
([res, currentItems]) => {
const results = options?.newSearch
? { result: [...res.result], hits: res.hits ?? 0 }
: { result: [...(items ?? []), ...(res.result ?? [])], hits: res.hits ?? 0 };
: { result: [...(currentItems ?? []), ...(res.result ?? [])], hits: res.hits ?? 0 };
// find items that are already in the list and remove the first duplicate
// why do we need it? when we use the action item not found we will add this item to the end of the list
@@ -277,8 +285,8 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
// to keep the list clean we remove all duplicates and just keep the last one
const itemsToRemove: number[] = [];
results.result.forEach((item) => {
const duplicates = items.filter((i) => i.dto.id === item.dto.id);
if (duplicates.length > 1) {
const duplicates = results.result.filter((i) => i.dto.id === item.dto.id);
if (duplicates.length > 1 && !itemsToRemove.includes(duplicates.find((_) => true)?.dto?.id)) {
// skip the first duplicate
// and add ids of the duplicates to the list of items to remove
itemsToRemove.push(...duplicates.slice(1).map((i) => i.dto.id));
@@ -289,6 +297,8 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
const index = results.result.findIndex((i) => i.dto.id === id);
if (index > -1) {
results.result.splice(index, 1);
// Hits anpassen, da sonst beim runterscrollen versucht wird nachzuladen obwohl evtl. nichts mehr kommt
results.hits--;
}
});
@@ -380,6 +390,13 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
hits: state.hits - 1,
}));
updateItem = this.updater<RemissionListItem>((state, item) => {
return {
...state,
items: state.items.map((i) => (i.dto.id === item.dto.id ? { ...item } : i)),
};
});
updateItemDto = this.updater<ReturnItemDTO | ReturnSuggestionDTO>((state, itemDto) => {
const itemToUpdate = state.items.find((i) => i.dto?.id === itemDto.id);
@@ -436,7 +453,10 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
}
async setSupplier(supplier: SupplierDTO) {
this._sourceOrSupplierChange$.next(true);
if (supplier.id === this.selectedSupplier?.id) {
return;
}
this._triggerReload$.next(true);
this.setSearchResult({ result: [], hits: 0 });
this.clearCache();
await this._router.navigate([], {
@@ -449,7 +469,10 @@ export class RemissionListComponentStore extends ComponentStore<RemissionState>
}
async setSource(source: string) {
this._sourceOrSupplierChange$.next(true);
if (source === this.selectedSource) {
return;
}
this._triggerReload$.next(true);
this.setSearchResult({ result: [], hits: 0 });
this.clearCache();
await this._router.navigate([], {

View File

@@ -9,7 +9,7 @@
<cdk-virtual-scroll-viewport
#scrollContainer
class="remission-list scroll-bar"
class="remission-list"
[itemSize]="368"
minBufferPx="1200"
maxBufferPx="1200"
@@ -36,7 +36,7 @@
</button>
</div>
<br />
<div class="inline-flex flex-row bg-white rounded-md mt-4">
<div *ngIf="(filteredSuppliers$ | async).length > 1" class="inline-flex flex-row bg-white rounded-md mt-4">
<button
class="w-48 py-2 bg-white text-black rounded-md font-bold"
type="button"
@@ -76,8 +76,17 @@
<page-remission-list-item [item]="item" [returnDto]="return$ | async"></page-remission-list-item>
</div>
<page-remission-list-item-loading *ngIf="fetching$ | async"></page-remission-list-item-loading>
<div *ngIf="listEmpty$ | async" class="bg-white text-center font-semibold text-inactive-branch py-10 rounded-card">
Es sind im Moment keine Artikel vorhanden
<div
*ngIf="listEmpty$ | async"
class="bg-white text-center font-semibold text-inactive-branch py-10 rounded-card"
[ngSwitch]="showSelectDepartmenttext$ | async"
>
<span *ngSwitchCase="true">
Wählen Sie die Abteilung, aus der Sie remittieren möchten.
</span>
<span *ngSwitchCase="false">
Es sind im Moment keine Artikel vorhanden
</span>
</div>
</div>
</div>

View File

@@ -17,7 +17,7 @@ import { RemissionComponentStore } from './remission.component-store';
templateUrl: 'remission-list.component.html',
styleUrls: ['remission-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [RemissionListComponentStore, RemissionComponentStore],
providers: [RemissionComponentStore],
})
export class RemissionListComponent implements OnInit, OnDestroy {
@ViewChildren(RemissionListItemComponent) listItems: QueryList<RemissionListItemComponent>;
@@ -70,6 +70,24 @@ export class RemissionListComponent implements OnInit, OnDestroy {
return this._remissionStore.return$;
}
get filter$() {
return this._remissionListStore.filter$;
}
showSelectDepartmenttext$ = combineLatest([this.filter$, this.selectedSource$]).pipe(
map(([filter, source]) => {
if (source !== 'Abteilungsremission') {
return false;
}
if (!!filter?.getQueryToken()?.filter?.abteilungen) {
return false;
}
return true;
})
);
listEmpty$ = combineLatest([this.fetching$, this.hits$]).pipe(
map(([loading, hits]) => !loading && hits === 0),
shareReplay()
@@ -121,8 +139,6 @@ export class RemissionListComponent implements OnInit, OnDestroy {
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$), debounceTime(0)).subscribe(async (queryParams) => {
const { supplier, source } = queryParams;
this._remissionListStore.loadFilter();
if (supplier) {
this._remissionListStore.setSelectedSupplierId(+supplier);
}

View File

@@ -7,11 +7,13 @@ import { Config } from '@core/config';
import { ToastService } from '@core/toast';
import { Subject } from 'rxjs';
import { filter, first, takeUntil } from 'rxjs/operators';
import { RemissionListComponentStore } from './remission-list/remission-list.component-store';
@Component({
selector: 'page-remission',
templateUrl: './remission.component.html',
styleUrls: ['./remission.component.scss'],
providers: [RemissionListComponentStore],
})
export class RemissionComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
@@ -21,6 +23,7 @@ export class RemissionComponent implements OnInit, OnDestroy {
}
constructor(
private readonly _remissionListComponentStore: RemissionListComponentStore,
private readonly _breadcrumb: BreadcrumbService,
private readonly _config: Config,
private _activatedRoute: ActivatedRoute,

View File

@@ -8,6 +8,4 @@
<div class="progress-inner" [style.width]="staplePercentage + '%'"></div>
</div>
<div class="mt-px-2 text-active-branch">Stapelplätze: {{ staple }} von {{ maxStaple }} Titel</div>
<div class="mt-4 text-cool-grey font-semibold text-center">Wählen Sie die Abteilung aus, die Sie<br />remittieren möchten.</div>
</ng-container>

View File

@@ -20,17 +20,12 @@
<div>Remi-Menge</div>
<div class="font-bold col-span-3">{{ item.quantity }}x</div>
</div>
<div class="grid grid-cols-4">
<div>Platz</div>
<div class="font-bold col-span-2">Leistung</div>
<div *ngIf="canRemoveItem" class="text-right">
<button [disabled]="loading" class="text-card-sub bg-transparent text-brand border-none font-bold" (click)="removeItem.emit(item)">
<ui-spinner [show]="loading">
Entfernen
</ui-spinner>
</button>
</div>
<div *ngIf="canRemoveItem" class="flex justify-end">
<button [disabled]="loading" class="text-card-sub bg-transparent text-brand border-none font-bold" (click)="removeItem.emit(item)">
<ui-spinner [show]="loading">
Entfernen
</ui-spinner>
</button>
</div>
</div>
</div>

View File

@@ -4,7 +4,7 @@
[canRemoveItems]="remissionStarted$ | async"
[showAddToRemiHint]="remissionStarted$ | async"
(deleted)="deleted($event)"
(itemDeleted)="reloadReturn()"
(itemDeleted)="itemDeleted()"
></page-shared-shipping-document-details>
<ng-container *ngIf="notCompleted$ | async">

View File

@@ -9,6 +9,7 @@ import { ReturnDTO } from '@swagger/remi';
import { NEVER, Observable } from 'rxjs';
import { catchError, filter, first, map, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { ShortReceiptNumberPipe } from '../../pipes/short-receipt-number.pipe';
import { RemissionListComponentStore } from '../../remission-list/remission-list.component-store';
export interface ShippingDocumentDetailsState {
remissionStarted?: boolean;
@@ -73,6 +74,7 @@ export class ShippingDocumentDetailsComponent extends ComponentStore<ShippingDoc
);
constructor(
private _remissionListStore: RemissionListComponentStore,
private _activatedRoute: ActivatedRoute,
private _remissionService: DomainRemissionService,
private _breadcrumb: BreadcrumbService,
@@ -134,10 +136,21 @@ export class ShippingDocumentDetailsComponent extends ComponentStore<ShippingDoc
}
deleted() {
this._cache.delete({ processId: String(this.processId) });
// Cache leeren und Reload bei Wechsel zurück zur Liste triggern
this._remissionListStore.clearCache();
this._remissionListStore._triggerReload$.next(true);
this.navigateBack();
}
itemDeleted() {
// Cache leeren und Reload bei Wechsel zurück zur Liste triggern
this._remissionListStore.clearCache();
this._remissionListStore._triggerReload$.next(true);
this.reloadReturn();
}
reloadReturn = this.effect(($) =>
$.pipe(
tap((_) => {

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { Component, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy, Output, EventEmitter } from '@angular/core';
import { Subscription } from 'rxjs';
import { IUiOption, UiOption } from '../../../tree';
@@ -21,6 +21,8 @@ export class UiFilterInputOptionBoolComponent implements OnDestroy {
this.subscribeChanges();
}
@Output() optionChange = new EventEmitter<UiOption>();
get uiOption() {
return this._option;
}
@@ -37,8 +39,9 @@ export class UiFilterInputOptionBoolComponent implements OnDestroy {
this.unsubscribeChanges();
if (this.uiOption) {
this.optionChangeSubscription.add(
this.uiOption.changes.subscribe(() => {
this.uiOption.changes.subscribe((change) => {
this.cdr.markForCheck();
this.optionChange.next(change.target);
})
);
}

View File

@@ -4,7 +4,11 @@
<button type="button" (click)="setSelected(undefined)">
Alle entfernen
</button>
<button type="button" (click)="setSelected(true)" *ngIf="uiInputOptions?.parent?.type === 2 || uiInputOptions?.parent?.type === 4">
<button
type="button"
(click)="setSelected(true)"
*ngIf="!uiInputOptions?.max && (uiInputOptions?.parent?.type === 2 || uiInputOptions?.parent?.type === 4)"
>
Alle auswählen
</button>
</div>
@@ -16,10 +20,14 @@
<ng-container *ngIf="uiInputOptions?.parent?.type === 2 || uiInputOptions?.parent?.type === 4">
<div class="input-options" #inputOptionsConainter (scroll)="markForCheck()">
<ng-container *ngIf="uiInputOptions?.parent?.type === 2">
<ui-input-option-bool *ngFor="let option of uiInputOptions?.values" [option]="option"></ui-input-option-bool>
<ui-input-option-bool
*ngFor="let option of uiInputOptions?.values"
[option]="option"
(optionChange)="optionChange($event)"
></ui-input-option-bool>
</ng-container>
<ng-container *ngIf="uiInputOptions?.parent?.type === 4">
<ui-input-option-tri-state *ngFor="let option of uiInputOptions?.values" [option]="option"></ui-input-option-tri-state>
<ui-input-option-tri-state *ngFor="let option of uiInputOptions?.values" [option]="option"> </ui-input-option-tri-state>
</ng-container>
</div>
<button class="cta-scroll" [class.up]="scrollPersantage > 20" *ngIf="scrollable" (click)="scroll(20)">

View File

@@ -1,5 +1,6 @@
import { Component, ChangeDetectionStrategy, Input, ViewChild, ElementRef, AfterViewInit, ChangeDetectorRef } from '@angular/core';
import { IUiInputOptions, UiInputOptions, UiInputType } from '../../tree';
import { Component, ChangeDetectionStrategy, Input, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { IUiInputOptions, UiInputOptions, UiInputType, UiOption } from '../../tree';
@Component({
selector: 'ui-filter-input-options',
@@ -24,6 +25,7 @@ export class UiFilterInputOptionsComponent {
} else {
this._inputOptions = UiInputOptions.create(value);
}
this.markForCheck();
}
@@ -48,6 +50,20 @@ export class UiFilterInputOptionsComponent {
constructor(private cdr: ChangeDetectorRef) {}
optionChange(option: UiOption) {
if (!this.uiInputOptions?.max || !option.selected) {
return;
}
const max = this.uiInputOptions.max;
const selectedOptions = this.uiInputOptions.values.filter((o) => o.selected);
if (selectedOptions.length > max) {
const optionsToUnselect = selectedOptions.filter((o) => o.label !== option.label);
optionsToUnselect.forEach((option) => option.setSelected(false));
}
}
markForCheck() {
setTimeout(() => this.cdr.markForCheck(), 0);
}

View File

@@ -2,14 +2,6 @@
@apply relative grid grid-flow-row gap-3;
}
::ng-deep ui-scroll-container .scroll-bar::-webkit-scrollbar {
background-color: transparent !important;
}
::ng-deep ui-scroll-container .scroll-bar::-webkit-scrollbar-track {
background-color: white !important;
}
.spacer {
@apply h-px-100;
}
@@ -25,20 +17,12 @@
}
::ng-deep .customer ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 10rem;
}
.cta-scroll {
@apply text-active-customer;
}
}
::ng-deep .branch ui-scroll-container {
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 15.5rem;
}
.cta-scroll {
@apply text-cool-grey;
}