#4365 RD // Abholfach - Alte Bestellpostensuche Seite ist erreichbar

This commit is contained in:
Lorenz Hilpert
2023-10-12 16:18:21 +02:00
parent fe3dfd00ab
commit cb39b3b79b
44 changed files with 40 additions and 2174 deletions

View File

@@ -2,19 +2,12 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { GoodsInCleanupListComponent } from './goods-in-cleanup-list/goods-in-cleanup-list.component';
import { GoodsInCleanupListModule } from './goods-in-cleanup-list/goods-in-cleanup-list.module';
import { GoodsInDetailsComponent } from './goods-in-details';
import { GoodsInEditModule } from './goods-in-edit';
import { GoodsInEditComponent } from './goods-in-edit/goods-in-edit.component';
import { GoodsInListComponent } from './goods-in-list/goods-in-list.component';
import { GoodsInListModule } from './goods-in-list/goods-in-list.module';
import { GoodsInRemissionPreviewComponent } from './goods-in-remission-preview/goods-in-remission-preview.component';
import { GoodsInRemissionPreviewModule } from './goods-in-remission-preview/goods-in-remission-preview.module';
import { GoodsInReservationComponent } from './goods-in-reservation/goods-in-reservation.component';
import { GoodsInReservationModule } from './goods-in-reservation/goods-in-reservation.module';
import { GoodsInSearchComponent, GoodsInSearchModule } from './goods-in-search';
import { GoodsInSearchMainComponent, GoodsInSearchMainModule } from './goods-in-search/search-main';
import { GoodsInSearchResultsModule } from './goods-in-search/search-results';
import { GoodsInSearchResultsComponent } from './goods-in-search/search-results/goods-in-search-results.component';
import { GoodsInComponent } from './goods-in.component';
const routes: Routes = [
@@ -22,22 +15,6 @@ const routes: Routes = [
path: '',
component: GoodsInComponent,
children: [
{
path: '',
component: GoodsInSearchComponent,
children: [
{ path: '', component: GoodsInSearchMainComponent },
{ path: 'results', component: GoodsInSearchResultsComponent },
],
},
{
path: 'details/customer/:customerNumber/order/:orderNumber/item/:orderItemId/:processingStatus',
component: GoodsInDetailsComponent,
},
{
path: 'details/customer/:customerNumber/order/:orderNumber/item/:orderItemId/:processingStatus/edit',
component: GoodsInEditComponent,
},
{ path: 'list', component: GoodsInListComponent },
{ path: 'reservation', component: GoodsInReservationComponent },
{ path: 'cleanup', component: GoodsInCleanupListComponent },
@@ -48,10 +25,6 @@ const routes: Routes = [
@NgModule({
imports: [
RouterModule.forChild(routes),
GoodsInSearchModule,
GoodsInSearchResultsModule,
GoodsInSearchMainModule,
GoodsInEditModule,
GoodsInListModule,
GoodsInCleanupListModule,
GoodsInReservationModule,

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { CommandService } from '@core/command';
@@ -11,6 +11,7 @@ import { UiScrollContainerComponent } from '@ui/scroll-container';
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
import { first, map, shareReplay, takeUntil } from 'rxjs/operators';
import { GoodsInCleanupListStore } from './goods-in-cleanup-list.store';
import { PickupShelfInNavigationService } from '@shared/services';
@Component({
selector: 'page-goods-in-cleanup-list',
@@ -20,6 +21,7 @@ import { GoodsInCleanupListStore } from './goods-in-cleanup-list.store';
providers: [GoodsInCleanupListStore],
})
export class GoodsInCleanupListComponent implements OnInit, OnDestroy {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
@ViewChildren(GoodsInOutOrderGroupItemComponent) listItems: QueryList<GoodsInOutOrderGroupItemComponent>;
@@ -186,16 +188,9 @@ export class GoodsInCleanupListComponent implements OnInit, OnDestroy {
}
navigateToDetails(orderItem: OrderItemListItemDTO) {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemSubsetId;
const nav = this._pickupShelfInNavigationService.detailRoute({ item: orderItem });
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(
orderNumber
)}/item/${orderItemId}/${processingStatus}`,
]);
this._router.navigate(nav.path, { queryParams: nav.queryParams });
}
async handleAction(action: KeyValueDTOOfStringAndString) {

View File

@@ -1,19 +0,0 @@
<shared-goods-in-out-order-details (actionHandled)="actionHandled($event)">
<shared-goods-in-out-order-details-header
[selectedOrderItemId]="selectedOrderItemId$ | async"
[order]="order$ | async"
(editClick)="navigateToEditPage($event)"
></shared-goods-in-out-order-details-header>
<shared-goods-in-out-order-details-item
[orderItem]="selectedItem$ | async"
[order]="order$ | async"
></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-covers
[orderItems]="coverItems$ | async"
[selectedOrderItem]="selectedItem$ | async"
(coverClick)="coverClick($event)"
[fetching]="fetchingCoverItems$ | async"
></shared-goods-in-out-order-details-covers>
</shared-goods-in-out-order-details>
<div class="spacer"></div>

View File

@@ -1,8 +0,0 @@
:host {
@apply block overflow-y-scroll;
max-height: calc(100vh - 135px - 80px);
}
.spacer {
@apply h-16;
}

View File

@@ -1,160 +0,0 @@
// import { ActivatedRoute, Router } from '@angular/router';
// import { RouterTestingModule } from '@angular/router/testing';
// import { ProductImageService } from '@cdn/product-image';
// import { ApplicationService } from '@core/application';
// import { BreadcrumbService } from '@core/breadcrumb';
// import { CoreCommandModule, CommandService } from '@core/command';
// import { CrmCustomerService } from '@domain/crm';
// import { DomainGoodsService, DomainOmsService, DomainReceiptService } from '@domain/oms';
// import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator';
// import { DateAdapter } from '@ui/common';
// import { UiModalService } from '@ui/modal';
// import { SharedGoodsInOutOrderDetailsModule } from 'apps/shared/goods-in-out/src/lib/goods-in-out-order-details';
// import { of } from 'rxjs';
// import { GoodsInDetailsComponent } from '.';
// describe('GoodsInDetailsCompoonent', () => {
// let spectator: Spectator<GoodsInDetailsComponent>;
// let breadcrumbServiceMock = {
// addOrUpdateBreadcrumbIfNotExists: jasmine.createSpy(),
// getBreadcrumbsByKeyAndTags$: jasmine.createSpy(),
// };
// let dateAdapterMock = {
// today: () => {},
// addCalendarDays: () => {},
// };
// let routerMock: jasmine.SpyObj<Router>;
// let activatedRouteMock = {
// orderId: 1,
// orderItemId: 2,
// processingStatus: 16,
// compartmentCode: 3,
// };
// const createComponent = createComponentFactory({
// component: GoodsInDetailsComponent,
// imports: [RouterTestingModule, SharedGoodsInOutOrderDetailsModule, CoreCommandModule],
// providers: [
// mockProvider(DomainGoodsService),
// mockProvider(DomainOmsService),
// mockProvider(ApplicationService),
// mockProvider(CommandService),
// mockProvider(CrmCustomerService),
// mockProvider(DomainReceiptService),
// mockProvider(UiModalService),
// mockProvider(ProductImageService),
// ],
// });
// beforeEach(async () => {
// spectator = createComponent({
// providers: [
// { provide: BreadcrumbService, useValue: breadcrumbServiceMock },
// {
// provide: ActivatedRoute,
// useValue: {
// params: of(activatedRouteMock),
// },
// },
// {
// provide: DateAdapter,
// useValue: dateAdapterMock,
// },
// ],
// });
// routerMock = spectator.inject(Router);
// });
// it('should create', () => {
// expect(spectator.component).toBeTruthy();
// });
// // describe('shared-goods-in-out-order-details', () => {
// // it('should render', () => {
// // expect(spectator.query('shared-goods-in-out-order-details')).toBeVisible();
// // });
// // });
// // describe('shared-goods-in-out-order-details-header', () => {
// // it('should render', () => {
// // expect(spectator.query('shared-goods-in-out-order-details-header')).toBeVisible();
// // });
// // });
// // describe('shared-goods-in-out-order-details-item', () => {
// // it('should render', () => {
// // expect(spectator.query('shared-goods-in-out-order-details-item')).toBeVisible();
// // });
// // });
// // describe('shared-goods-in-out-order-details-tags', () => {
// // it('should not render when no selectedItem is set', () => {
// // expect(spectator.query('shared-goods-in-out-order-details-tags')).not.toBeVisible();
// // });
// // it('should render when selectedItem$ with command arrived is set', () => {
// // spectator.component.selectedItem$ = of({ orderItemId: 1, actions: [{ command: 'ARRIVED' }] });
// // spectator.detectComponentChanges();
// // expect(spectator.query('shared-goods-in-out-order-details-tags')).toBeVisible();
// // });
// // });
// // describe('shared-goods-in-out-order-details-covers', () => {
// // it('should render', () => {
// // expect(spectator.query('shared-goods-in-out-order-details-covers')).toBeVisible();
// // });
// // });
// // describe('updateBreadcrumb', () => {
// // it('should call addOrUpdateBreadcrumbIfNotExists', () => {
// // const orderNumber = '1';
// // const orderItemId = 2;
// // const processingStatus = 16;
// // spectator.component.updateBreadcrumb({ orderNumber, orderItemId, processingStatus });
// // expect(breadcrumbServiceMock.addOrUpdateBreadcrumbIfNotExists).toHaveBeenCalledWith({
// // key: 'goods-in',
// // name: orderNumber,
// // path: `/goods/in/details/order/${orderNumber}/item/${orderItemId}/${processingStatus}`,
// // section: 'branch',
// // tags: ['goods-in', 'details', orderNumber],
// // });
// // });
// // });
// // describe('navigate', () => {
// // it('should navigate to /goods/in when parameter is "search"', () => {
// // spyOn(routerMock, 'navigate');
// // spectator.component.navigate('search');
// // expect(routerMock.navigate).toHaveBeenCalledWith(['/goods/in']);
// // });
// // it('should call navigateToEdit when parameter is "edit"', () => {
// // spyOn(spectator.component, 'navigateToEdit');
// // spectator.component.navigate('edit');
// // expect(spectator.component.navigateToEdit).toHaveBeenCalled();
// // });
// // });
// // describe('navigateToEdit', () => {
// // it('should navigate to /goods/in/details/compartment when compartmentCode is set', async () => {
// // activatedRouteMock.compartmentCode = 3;
// // spyOn(routerMock, 'navigate');
// // await spectator.component.navigateToEdit();
// // expect(routerMock.navigate).toHaveBeenCalledWith([
// // `/goods/in/details/compartment/${activatedRouteMock.compartmentCode}/item/${activatedRouteMock.orderItemId}/${activatedRouteMock.processingStatus}/edit`,
// // ]);
// // });
// // it('should navigate to /goods/in/details/order when compartmentCode is not set', async () => {
// // activatedRouteMock.compartmentCode = undefined;
// // spyOn(routerMock, 'navigate');
// // await spectator.component.navigateToEdit();
// // expect(routerMock.navigate).toHaveBeenCalledWith([
// // `/goods/in/details/order/${activatedRouteMock.orderId}/item/${activatedRouteMock.orderItemId}/${activatedRouteMock.processingStatus}/edit`,
// // ]);
// // });
// // });
// });

View File

@@ -1,259 +0,0 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { DomainGoodsService, DomainOmsService, OrderItemsContext } from '@domain/oms';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { OrderItemListItemDTO, OrderItemProcessingStatusValue } from '@swagger/oms';
import { combineLatest, Subject } from 'rxjs';
import { debounceTime, filter, first, map, shareReplay, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
export interface GoodsInDetailsComponentState {
fetching: boolean;
fetchingCoverItems: boolean;
customerNumber?: string;
orderNumber?: string;
processingStatus?: OrderItemProcessingStatusValue;
selectedOrderItemId?: number;
items?: OrderItemListItemDTO[];
coverItems?: OrderItemListItemDTO[];
orderId?: number;
}
@Component({
selector: 'page-goods-in-details',
templateUrl: 'goods-in-details.component.html',
styleUrls: ['goods-in-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInDetailsComponent extends ComponentStore<GoodsInDetailsComponentState> implements OnInit, OnDestroy {
customerNumber$ = this.select((s) => decodeURIComponent(s.customerNumber ?? ''));
orderNumber$ = this.select((s) => decodeURIComponent(s.orderNumber ?? ''));
selectedOrderItemId$ = this.select((s) => +s.selectedOrderItemId);
items$ = this.select((s) => s.items ?? []);
coverItems$ = this.select((s) => s.coverItems ?? []);
orderId$ = this.select((s) => s.orderId);
get items() {
return this.get((s) => s.items);
}
processingStatus$ = this.select((s) => s.processingStatus);
itemsWithProcessingStatus$ = combineLatest([this.items$, this.processingStatus$]).pipe(
map(([items, processingStatus]) => items.filter((item) => item.processingStatus === processingStatus))
);
order$ = this.orderId$.pipe(
filter((orderId) => !!orderId),
switchMap((orderId) => this._omsService.getOrder(orderId)),
shareReplay()
);
selectedItem$ = combineLatest([this.selectedOrderItemId$, this.itemsWithProcessingStatus$]).pipe(
map(([orderItemId, items]) => items.find((item) => item.orderItemSubsetId === orderItemId) || items[0])
);
fetchingCoverItems$ = this.select((s) => s.fetchingCoverItems);
private _onDestroy$ = new Subject();
constructor(
private _activatedRoute: ActivatedRoute,
private _domainGoodsInService: DomainGoodsService,
private _omsService: DomainOmsService,
private _breadcrumb: BreadcrumbService,
private _router: Router,
private readonly _config: Config
) {
super({
fetching: false,
fetchingCoverItems: false,
});
}
ngOnInit() {
this._activatedRoute.params.pipe(takeUntil(this._onDestroy$)).subscribe(async (params) => {
const customerNumber: string = params?.customerNumber;
const orderNumber: string = params?.orderNumber;
const selectedOrderItemId: number = +params?.orderItemId || undefined;
const processingStatus = +params?.processingStatus as OrderItemProcessingStatusValue;
this.patchState({ customerNumber, orderNumber, selectedOrderItemId, processingStatus });
await this.removeDetailsCrumbs();
this.loadItems();
});
this.removeBreadcrumbs();
this.loadCoverItems();
}
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.unsubscribe();
}
async removeBreadcrumbs() {
const editCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'edit'])
.pipe(first())
.toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
}
async removeDetailsCrumbs() {
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'details'])
.pipe(first())
.toPromise();
detailsCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
}
async updateBreadcrumb(item: OrderItemListItemDTO) {
if (item) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsIn'),
name: item?.orderNumber,
path: `/filiale/goods/in/details/customer/${encodeURIComponent(item.buyerNumber)}/order/${encodeURIComponent(
item?.orderNumber
)}/item/${item?.orderItemSubsetId}/${item?.processingStatus}`,
section: 'branch',
tags: ['goods-in', 'details', item?.orderNumber],
});
}
}
// tslint:disable-next-line: member-ordering
loadItems = this.effect(($) =>
$.pipe(
tap(() => this.patchState({ fetching: true })),
debounceTime(500),
withLatestFrom(this.orderNumber$, this.selectedOrderItemId$, this.processingStatus$),
switchMap(([_, orderNumber, selectedOrderItemId, processingStatus]) =>
this._domainGoodsInService.getWareneingangItemByOrderNumber(orderNumber).pipe(
tapResponse(
(res) => {
this.patchState({
items: res.result.map((item) => ({
...item,
// Anzeige von Teilabholung in Wareneingang ausblenden
actions: item?.actions?.filter((action) => action.command !== 'FETCHED_PARTIAL') ?? [],
})),
orderId: res.result[0].orderId,
});
this.updateBreadcrumb(
res.result.find((item) => item.orderItemSubsetId === selectedOrderItemId && item.processingStatus === processingStatus)
);
},
() => {},
() => {
this.patchState({ fetching: false });
}
)
)
)
)
);
loadCoverItems = this.effect(($) =>
$.pipe(
tap(() => this.patchState({ fetchingCoverItems: true })),
debounceTime(200),
withLatestFrom(this.customerNumber$),
switchMap(([_, customerNumber]) =>
this._domainGoodsInService.getWareneingangItemByCustomerNumber(customerNumber).pipe(
tapResponse(
(res) => {
this.patchState({
coverItems: res?.result?.map((item) => ({
...item,
})),
fetchingCoverItems: false,
});
},
() => {},
() => {
this.patchState({ fetchingCoverItems: false });
}
)
)
)
)
);
navigateToLandingPage() {
this._router.navigate(['/filiale/goods/in/']);
}
navigateToReservationPage() {
this._router.navigate(['/filiale/goods/in/reservation']);
}
navigateToEditPage(orderItem: OrderItemListItemDTO) {
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(orderItem.buyerNumber)}/order/${encodeURIComponent(
orderItem?.orderNumber
)}/item/${orderItem?.orderItemSubsetId}/${orderItem?.processingStatus}/edit`,
]);
}
navigateToDetailsPage(orderItem: OrderItemListItemDTO) {
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(orderItem?.buyerNumber)}/order/${encodeURIComponent(
orderItem?.orderNumber
)}/item/${orderItem?.orderItemSubsetId}/${orderItem?.processingStatus}`,
]);
}
coverClick(orderItems: OrderItemListItemDTO[]) {
if (orderItems.length === 1) {
this.navigateToDetailsPage(orderItems[0]);
} else {
if (orderItems.every((item) => item.processingStatus === 128)) {
// Search by processingStatus and compartmentCode
const queryParams = {
main_qs: encodeURIComponent(orderItems.find((_) => true).compartmentCode),
filter_orderitemprocessingstatus: orderItems.find((_) => true).processingStatus,
};
this._router.navigate(['/filiale/goods/in', 'results'], { queryParams });
} else {
// Search by processingStatus and orderNumber
const queryParams = {
main_qs: encodeURIComponent(orderItems.find((_) => true).orderNumber),
filter_orderitemprocessingstatus: orderItems
.map((item) => item.processingStatus)
.filter((v, i, a) => a.indexOf(v) === i)
.join(';'),
};
this._router.navigate(['/filiale/goods/in', 'results'], { queryParams });
}
}
}
async actionHandled(handler: { orderItemsContext: OrderItemsContext; command: string; navigation: 'details' | 'main' | 'reservation' }) {
switch (handler.navigation) {
case 'details':
this.navigateToDetailsPage(handler.orderItemsContext.items.find((_) => true));
await this.removeDetailsCrumbs();
this.loadItems();
break;
case 'main':
this.navigateToLandingPage();
break;
case 'reservation':
this.navigateToReservationPage();
break;
default:
break;
}
}
}

View File

@@ -1,13 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SharedGoodsInOutOrderDetailsModule } from '@shared/components/goods-in-out';
import { GoodsInDetailsComponent } from './goods-in-details.component';
@NgModule({
imports: [CommonModule, SharedGoodsInOutOrderDetailsModule],
exports: [GoodsInDetailsComponent],
declarations: [GoodsInDetailsComponent],
providers: [],
})
export class GoodsInDetailsModule {}

View File

@@ -1,4 +0,0 @@
// start:ng42.barrel
export * from './goods-in-details.component';
export * from './goods-in-details.module';
// end:ng42.barrel

View File

@@ -1,3 +0,0 @@
<div>
<shared-goods-in-out-order-edit (navigation)="navigateToDetailsPage($event)" [items]="items$ | async"></shared-goods-in-out-order-edit>
</div>

View File

@@ -1,4 +0,0 @@
div {
@apply overflow-y-scroll;
height: calc(100vh - 280px);
}

View File

@@ -1,63 +0,0 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { DomainGoodsService } from '@domain/oms';
import { Observable } from 'rxjs';
import { map, shareReplay, switchMap } from 'rxjs/operators';
@Component({
selector: 'page-goods-in-edit',
templateUrl: 'goods-in-edit.component.html',
styleUrls: ['goods-in-edit.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInEditComponent implements OnInit {
orderNumber$: Observable<string> = this._activatedRoute.params.pipe(
map((params) => decodeURIComponent(params.orderNumber ?? '') || undefined)
);
items$ = this.orderNumber$
.pipe(switchMap((orderNumber) => this._domainGoodsInService.getWareneingangItemByOrderNumber(orderNumber)))
.pipe(
map((response) => response.result),
shareReplay()
);
constructor(
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService,
private _domainGoodsInService: DomainGoodsService,
private _router: Router,
private readonly _config: Config
) {}
ngOnInit() {
this.updateBreadcrumb();
}
async updateBreadcrumb() {
const orderNumber = this._activatedRoute.snapshot.params.orderNumber;
const orderItemId = this._activatedRoute.snapshot.params.orderItemId;
const processingStatus = this._activatedRoute.snapshot.params.processingStatus;
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsIn'),
name: 'Bearbeiten',
path: `/filiale/goods/in/details/order/${encodeURIComponent(orderNumber)}/item/${orderItemId}/${processingStatus}/edit`,
section: 'branch',
tags: ['goods-in', 'edit', orderNumber],
});
}
navigateToDetailsPage({ options }: { options?: { processingStatus?: number } }) {
const customerNumber = this._activatedRoute.snapshot.params.customerNumber;
const orderNumber = this._activatedRoute.snapshot.params.orderNumber;
const orderItemId = this._activatedRoute.snapshot.params.orderItemId;
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(
orderNumber
)}/item/${orderItemId}/${processingStatus}`,
]);
}
}

View File

@@ -1,12 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SharedGoodsInOutOrderEditModule } from '@shared/components/goods-in-out';
import { GoodsInEditComponent } from './goods-in-edit.component';
@NgModule({
imports: [CommonModule, SharedGoodsInOutOrderEditModule],
exports: [GoodsInEditComponent],
declarations: [GoodsInEditComponent],
providers: [],
})
export class GoodsInEditModule {}

View File

@@ -1,4 +0,0 @@
// start:ng42.barrel
export * from './goods-in-edit.component';
export * from './goods-in-edit.module';
// end:ng42.barrel

View File

@@ -1,4 +1,14 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
OnDestroy,
OnInit,
QueryList,
ViewChild,
ViewChildren,
inject,
} from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
@@ -11,6 +21,7 @@ import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { debounceTime, first, map, shareReplay, takeUntil, tap } from 'rxjs/operators';
import { GoodsInListItemComponent } from './goods-in-list-item/goods-in-list-item.component';
import { GoodsInListStore } from './goods-in-list.store';
import { PickupShelfInNavigationService } from '@shared/services';
@Component({
selector: 'page-goods-in-list',
@@ -20,6 +31,7 @@ import { GoodsInListStore } from './goods-in-list.store';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@ViewChildren(GoodsInListItemComponent) listItems: QueryList<GoodsInListItemComponent>;
@ViewChild('scrollContainer', { static: false })
@@ -135,16 +147,9 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
return;
}
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemSubsetId;
const nav = this._pickupShelfInNavigationService.detailRoute({ item: orderItem });
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(
orderNumber
)}/item/${orderItemId}/${processingStatus}`,
]);
this._router.navigate(nav.path, { queryParams: nav.queryParams });
}
async removeBreadcrumbs() {

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
@@ -9,6 +9,7 @@ import { first, map, shareReplay, takeUntil } from 'rxjs/operators';
import { GoodsInRemissionPreviewStore } from './goods-in-remission-preview.store';
import { Config } from '@core/config';
import { ToasterService } from '@shared/shell';
import { PickupShelfInNavigationService } from '@shared/services';
@Component({
selector: 'page-goods-in-remission-preview',
@@ -18,6 +19,7 @@ import { ToasterService } from '@shared/shell';
providers: [GoodsInRemissionPreviewStore],
})
export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
private _scrollPosition: number;
@@ -146,16 +148,9 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
}
navigateToDetails(orderItem: OrderItemListItemDTO) {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemSubsetId;
const nav = this._pickupShelfInNavigationService.detailRoute({ item: orderItem });
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(
orderNumber
)}/item/${orderItemId}/${processingStatus}`,
]);
this._router.navigate(nav.path, { queryParams: nav.queryParams });
}
async handleAction(action: KeyValueDTOOfStringAndString) {

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { CommandService } from '@core/command';
@@ -11,6 +11,7 @@ import { UiScrollContainerComponent } from '@ui/scroll-container';
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
import { map, shareReplay, takeUntil, first } from 'rxjs/operators';
import { GoodsInReservationStore } from './goods-in-reservation.store';
import { PickupShelfInNavigationService } from '@shared/services';
@Component({
selector: 'page-goods-in-reservation',
@@ -20,6 +21,8 @@ import { GoodsInReservationStore } from './goods-in-reservation.store';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInReservationComponent implements OnInit, OnDestroy {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
@ViewChildren(GoodsInOutOrderGroupItemComponent) listItems: QueryList<GoodsInOutOrderGroupItemComponent>;
@@ -147,16 +150,9 @@ export class GoodsInReservationComponent implements OnInit, OnDestroy {
}
navigateToDetails(orderItem: OrderItemListItemDTO) {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemSubsetId;
const nav = this._pickupShelfInNavigationService.detailRoute({ item: orderItem });
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(
orderNumber
)}/item/${orderItemId}/${processingStatus}`,
]);
this._router.navigate(nav.path, { queryParams: nav.queryParams });
}
initInitialSearch() {

View File

@@ -1,29 +0,0 @@
<div class="goods-in-search-filter-content">
<button class="btn-close" type="button" (click)="close.emit()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
<div class="goods-in-search-filter-content-main">
<h1 class="title">Filter</h1>
<ui-filter
[filter]="filter"
[loading]="loading$ | async"
(search)="applyFilter()"
[hint]="message"
resizeInputOptionsToElement="page-goods-in-search-filter .cta-wrapper"
[scanner]="true"
></ui-filter>
</div>
</div>
<div class="cta-wrapper">
<button class="cta-reset-filter" (click)="resetFilter()" [disabled]="loading$ | async">
Filter zurücksetzen
</button>
<button class="cta-apply-filter" (click)="applyFilter()" [disabled]="loading$ | async">
<ui-spinner [show]="loading$ | async">
Filter anwenden
</ui-spinner>
</button>
</div>

View File

@@ -1,45 +0,0 @@
:host {
@apply block;
}
.goods-in-search-filter-content {
@apply relative mx-auto;
}
.btn-close {
@apply absolute text-cool-grey top-3 right-4 outline-none border-none bg-transparent;
}
.goods-in-search-filter-content-main {
@apply px-4;
h1.title {
@apply text-center text-3xl font-bold py-4;
}
}
::ng-deep ui-filter-input-options .input-options {
max-height: calc(100vh - 555px);
}
.cta-wrapper {
@apply fixed bottom-8 whitespace-nowrap;
left: 50%;
transform: translateX(-50%);
}
.cta-reset-filter,
.cta-apply-filter {
@apply text-lg font-bold px-6 py-3 rounded-full border-solid border-2 border-brand outline-none mx-2;
&:disabled {
@apply bg-inactive-branch cursor-not-allowed border-none text-white;
}
}
.cta-reset-filter {
@apply bg-white text-brand;
}
.cta-apply-filter {
@apply text-white bg-brand;
}

View File

@@ -1,136 +0,0 @@
// import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator';
// import { IUiFilter, UiFilter, UiFilterComponent } from '@ui/filter';
// import { UiIconModule } from '@ui/icon';
// import { cold, hot } from 'jasmine-marbles';
// import { MockComponent } from 'ng-mocks';
// import { BehaviorSubject } from 'rxjs';
// // import { GoodsInSearchService } from '../goods-in-search.service';
// import { GoodsInSearchFilterComponent } from './goods-in-search-filter.component';
// describe('GoodsInSearchFilterComponent', () => {
// let spectator: Spectator<GoodsInSearchFilterComponent>;
// const createComponent = createComponentFactory({
// component: GoodsInSearchFilterComponent,
// imports: [UiIconModule],
// declarations: [MockComponent(UiFilterComponent)],
// // providers: [mockProvider(GoodsInSearchService)],
// });
// // let goodsInServiceMock: jasmine.SpyObj<GoodsInSearchService>;
// beforeEach(() => {
// spectator = createComponent();
// // goodsInServiceMock = spectator.inject(GoodsInSearchService);
// });
// // it('should create the component', () => {
// // expect(spectator.component).toBeTruthy();
// // });
// // describe('.btn-close', () => {
// // it('should emit close on click', () => {
// // let emitted = false;
// // spectator.output('close').subscribe(() => (emitted = true));
// // spectator.click('.btn-close');
// // expect(emitted).toBe(true);
// // });
// // });
// // describe('settings', () => {
// // it('should be an instance of UiFilter', () => {
// // expect(spectator.component.filter instanceof UiFilter).toBe(true);
// // });
// // it('should UiFilter.create with GoodsInSearchService.settings to create a new instance of UiFilter', () => {
// // const settings = UiFilter.create({ input: [], filter: [] });
// // spyOn(UiFilter, 'create').and.callThrough();
// // goodsInServiceMock.settings = settings;
// // spectator.component['_initSettings']();
// // expect(UiFilter.create).toHaveBeenCalledWith(settings);
// // expect(settings).not.toBe(spectator.component.filter);
// // });
// // });
// // describe('loading$', () => {
// // it('should set loading$ with GoodsInSearchService.loading$', () => {
// // goodsInServiceMock.loading$ = new BehaviorSubject(true);
// // spyOn(goodsInServiceMock.loading$, 'asObservable').and.returnValue(hot('a--b--a--', { a: true, b: false }));
// // spectator.component['_initLoading$']();
// // expect(goodsInServiceMock.loading$.asObservable).toHaveBeenCalled();
// // expect(spectator.component.loading$).toBeObservable(cold('a--b--a--', { a: true, b: false }));
// // });
// // });
// // describe('resetFilter()', () => {
// // it('should call GoodsInSearchService.loadSettings()', async () => {
// // goodsInServiceMock.loadSettings.and.returnValue(Promise.resolve());
// // await spectator.component.resetFilter();
// // expect(goodsInServiceMock.loadSettings).toHaveBeenCalled();
// // });
// // it('should UiFilter.create with GoodsInSearchService.settings to create a new instance of UiFilter', async () => {
// // const settings = UiFilter.create({ input: [], filter: [] });
// // spyOn(UiFilter, 'create').and.callThrough();
// // goodsInServiceMock.loadSettings.and.returnValue(Promise.resolve());
// // goodsInServiceMock.settings = settings;
// // await spectator.component.resetFilter();
// // expect(UiFilter.create).toHaveBeenCalledWith(settings);
// // expect(settings).not.toBe(spectator.component.filter);
// // });
// // });
// // describe('applyFilter', () => {
// // it('should call GoodsInSearchService.setSettings with settings', () => {
// // spectator.component.filter = UiFilter.create({});
// // spectator.component.applyFilter();
// // expect(goodsInServiceMock.setSettings).toHaveBeenCalledWith(spectator.component.filter);
// // });
// // it('should call GoodsInSearchService.search', () => {
// // spectator.component.applyFilter();
// // expect(goodsInServiceMock.search).toHaveBeenCalledWith();
// // });
// // it('should call close.emit', () => {
// // spyOn(spectator.component.close, 'emit');
// // spectator.component.applyFilter();
// // expect(spectator.component.close.emit).toHaveBeenCalledWith();
// // });
// // });
// // describe('Element .cta-reset-filter', () => {
// // it('should call resetFilter() on click', () => {
// // spyOn(spectator.component, 'resetFilter');
// // spectator.click('.cta-reset-filter');
// // expect(spectator.component.resetFilter).toHaveBeenCalled();
// // });
// // });
// // describe('Element .cta-apply-filter', () => {
// // it('should call resetFilter() on click', () => {
// // spyOn(spectator.component, 'applyFilter');
// // spectator.click('.cta-apply-filter');
// // expect(spectator.component.applyFilter).toHaveBeenCalled();
// // });
// // });
// // describe('Element ui-filter', () => {
// // it('should call applyFilter() when on search', () => {
// // spyOn(spectator.component, 'applyFilter');
// // spectator.triggerEventHandler('ui-filter', 'search', undefined);
// // expect(spectator.component.applyFilter).toHaveBeenCalled();
// // });
// // });
// });

View File

@@ -1,116 +0,0 @@
import { Component, ChangeDetectionStrategy, Output, EventEmitter, ChangeDetectorRef, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { UiFilter, UiFilterComponent } from '@ui/filter';
import { Observable, Subject } from 'rxjs';
import { first, take, takeUntil } from 'rxjs/operators';
import { GoodsInSearchStore } from '../goods-in-search.store';
@Component({
selector: 'page-goods-in-search-filter',
templateUrl: 'goods-in-search-filter.component.html',
styleUrls: ['goods-in-search-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInSearchFilterComponent implements OnInit, OnDestroy {
@Output()
close = new EventEmitter();
filter: UiFilter;
loading$: Observable<boolean>;
message: string;
private _onDestroy$ = new Subject();
@ViewChild(UiFilterComponent, { static: false })
uiFilter: UiFilterComponent;
constructor(
private _goodsInSearchStore: GoodsInSearchStore,
private _breadcrumb: BreadcrumbService,
private _cdr: ChangeDetectorRef,
private _router: Router,
private _config: Config
) {}
ngOnInit() {
this._initSettings();
this._initLoading$();
}
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.complete();
}
private _initSettings() {
this.filter = UiFilter.create(this._goodsInSearchStore.filter);
}
private _initLoading$() {
this.loading$ = this._goodsInSearchStore.fetching$;
}
async resetFilter() {
const queryParams = { main_qs: this.filter?.getQueryParams()?.main_qs || '' };
this._goodsInSearchStore.resetFilter(queryParams);
this._initSettings();
this._cdr.markForCheck();
}
async applyFilter() {
this.uiFilter?.cancelAutocomplete();
this._goodsInSearchStore.clearResults();
this._goodsInSearchStore.setFilter(this.filter);
this.message = undefined;
await this.updateQueryParams();
this._goodsInSearchStore.searchResult$.pipe(takeUntil(this._onDestroy$), take(1)).subscribe((result) => {
if (result.error) {
} else {
if (result.hits > 0) {
if (result.hits === 1) {
const orderItem = result.result[0];
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(orderItem.buyerNumber)}/order/${encodeURIComponent(
orderItem.orderNumber
)}/item/${orderItem.orderItemSubsetId}/${orderItem.processingStatus}`,
]);
} else {
this._router.navigate(['/filiale', 'goods', 'in', 'results'], {
queryParams: this._goodsInSearchStore.filter.getQueryParams(),
});
}
this.close.emit();
} else {
this.message = 'keine Suchergebnisse';
}
this._cdr.markForCheck();
}
});
this._goodsInSearchStore.search();
}
async updateBreadcrumb() {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsIn'),
name: 'Abholfach',
path: '/filiale/goods/in',
tags: ['goods-in', 'main', 'filter'],
section: 'branch',
params: this._goodsInSearchStore.filter?.getQueryParams(),
});
}
async updateQueryParams() {
await this._router.navigate([], { queryParams: this._goodsInSearchStore.filter?.getQueryParams() });
this.updateBreadcrumb();
}
}

View File

@@ -1,3 +0,0 @@
// start:ng42.barrel
export * from './goods-in-search-filter.component';
// end:ng42.barrel

View File

@@ -1,10 +0,0 @@
<button class="filter" [class.active]="hasFilter$ | async" (click)="shellFilterOverlay.open()">
<ui-icon size="20px" icon="filter_alit"></ui-icon>
<span class="label">Filter</span>
</button>
<router-outlet></router-outlet>
<shell-filter-overlay #shellFilterOverlay>
<page-goods-in-search-filter *ngIf="shellFilterOverlay.isOpen" (close)="shellFilterOverlay.close()"> </page-goods-in-search-filter>
</shell-filter-overlay>

View File

@@ -1,16 +0,0 @@
:host {
@apply flex flex-col w-full box-content relative;
}
.filter {
@apply absolute font-sans flex items-center font-bold bg-gray-400 border-0 text-p2 -top-12 right-0 py-px-8 px-px-15 rounded-filter justify-center z-sticky;
min-width: 106px;
.label {
@apply ml-px-5;
}
&.active {
@apply bg-active-branch text-white ml-px-5;
}
}

View File

@@ -1,95 +0,0 @@
// import { By } from '@angular/platform-browser';
// import { RouterTestingModule } from '@angular/router/testing';
// import { BreadcrumbService } from '@core/breadcrumb';
// import { DomainGoodsService } from '@domain/oms';
// import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator';
// import { UiIconComponent } from '@ui/icon';
// import { MockComponent } from 'ng-mocks';
// import { GoodsInSearchFilterComponent } from './goods-in-search-filter';
// import { GoodsInSearchComponent } from './goods-in-search.component';
// import { GoodsInSearchStore } from './goods-in-search.store';
// describe('GoodsInSearchComponent', () => {
// let spectator: Spectator<GoodsInSearchComponent>;
// let goodsInSearchStoreMock: jasmine.SpyObj<GoodsInSearchStore>;
// const createGoodsInComponent = createComponentFactory({
// component: GoodsInSearchComponent,
// declarations: [MockComponent(GoodsInSearchFilterComponent), MockComponent(UiIconComponent)],
// imports: [RouterTestingModule],
// componentProviders: [
// mockProvider(DomainGoodsService),
// mockProvider(GoodsInSearchStore, {
// loadSettings: async () => {},
// }),
// mockProvider(BreadcrumbService),
// ],
// });
// beforeEach(() => {
// spectator = createGoodsInComponent();
// goodsInSearchStoreMock = spectator.inject(GoodsInSearchStore, true);
// });
// it('should create', () => {
// expect(spectator.component).toBeTruthy();
// });
// describe('ngOnInit()', () => {
// it('should call GoodsInSearchService.loadSettings()', () => {
// spyOn(goodsInSearchStoreMock, 'loadSettings');
// spectator.component.ngOnInit();
// expect(goodsInSearchStoreMock.loadSettings).toHaveBeenCalled();
// });
// });
// describe('toggleFilterOverlay()', () => {
// it('should set showFilterOverlay to true', () => {
// spectator.component.showFilterOverlay = false;
// spectator.component.toggleFilterOverlay();
// expect(spectator.component.showFilterOverlay).toBe(true);
// });
// it('should set showFilterOverlay to false', () => {
// spectator.component.showFilterOverlay = true;
// spectator.component.toggleFilterOverlay();
// expect(spectator.component.showFilterOverlay).toBe(false);
// });
// });
// describe('button.filter', () => {
// it('should call toggleFilterOverlay() on click', () => {
// spyOn(spectator.component, 'toggleFilterOverlay');
// spectator.click('button.filter');
// expect(spectator.component.toggleFilterOverlay).toHaveBeenCalled();
// });
// });
// describe('page-goods-in-search-filter', () => {
// it('should not render the component if showFilterOverlay is false', () => {
// spectator.component.showFilterOverlay = false;
// spectator.detectComponentChanges();
// expect(spectator.query('page-goods-in-search-filter')).toBeNull();
// });
// it('should render the component if showFilterOverlay is true', () => {
// spectator.component.showFilterOverlay = true;
// spectator.detectComponentChanges();
// expect(spectator.query('page-goods-in-search-filter')).not.toBeNull();
// });
// it('should call toggleFilterOverlay() on (close)', () => {
// spyOn(spectator.component, 'toggleFilterOverlay');
// spectator.component.showFilterOverlay = true;
// spectator.detectComponentChanges();
// spectator.query('page-goods-in-search-filter');
// const filter: GoodsInSearchFilterComponent = spectator.debugElement.query(By.directive(GoodsInSearchFilterComponent))
// .componentInstance;
// filter.close.emit();
// expect(spectator.component.toggleFilterOverlay).toHaveBeenCalled();
// });
// });
// });

View File

@@ -1,58 +0,0 @@
import { animate, style, transition, trigger } from '@angular/animations';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { UiFilter, UiFilterAutocompleteProvider } from '@ui/filter';
import { isEqual } from 'lodash';
import { combineLatest, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { GoodsInSearchStore } from './goods-in-search.store';
import { GoodsInSearchMainAutocompleteProvider } from './providers/goods-in-search-main-autocomplete.provider';
@Component({
selector: 'page-goods-in-search',
templateUrl: 'goods-in-search.component.html',
styleUrls: ['goods-in-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
GoodsInSearchStore,
{
provide: UiFilterAutocompleteProvider,
useClass: GoodsInSearchMainAutocompleteProvider,
multi: true,
},
],
animations: [
trigger('slideInOut', [
transition(':enter', [style({ transform: 'translateX(100%)' }), animate('250ms', style({ transform: 'translateX(0%)' }))]),
transition(':leave', [style({ transform: '*' }), animate('250ms', style({ transform: 'translateX(100%)' }))]),
]),
],
})
export class GoodsInSearchComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
hasFilter$ = combineLatest([this._goodsInSearchStore.filter$, this._goodsInSearchStore.defaultSettings$]).pipe(
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), UiFilter.create(defaultFilter).getQueryParams()))
);
constructor(private _goodsInSearchStore: GoodsInSearchStore, private _activatedRoute: ActivatedRoute) {}
ngOnInit() {
this._goodsInSearchStore.loadSettings();
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((params) => {
// Reset Filter when query params are empty
if (params && Object.keys(params).length === 0) {
this._goodsInSearchStore.setQueryParams(params);
this._goodsInSearchStore.loadSettings();
}
});
}
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
}

View File

@@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { GoodsInSearchComponent } from './goods-in-search.component';
import { UiIconModule } from '@ui/icon';
import { GoodsInSearchFilterComponent } from './goods-in-search-filter';
import { RouterModule } from '@angular/router';
import { UiFilterNextModule } from '@ui/filter';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [CommonModule, RouterModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
exports: [GoodsInSearchComponent],
declarations: [GoodsInSearchComponent, GoodsInSearchFilterComponent],
})
export class GoodsInSearchModule {}

View File

@@ -1,78 +0,0 @@
// import { DomainGoodsService } from '@domain/oms';
// import { createServiceFactory, mockProvider, SpectatorService } from '@ngneat/spectator';
// import { QuerySettingsDTO } from '@swagger/oms';
// import { UiFilter } from '@ui/filter';
// import { isObservable } from 'rxjs';
// import { GoodsInSearchStore } from './goods-in-search.store';
// describe('GoodsInSearchStore', () => {
// let spectator: SpectatorService<GoodsInSearchStore>;
// const createService = createServiceFactory({
// service: GoodsInSearchStore,
// providers: [mockProvider(DomainGoodsService, {})],
// });
// let goodsInSearchMock: jasmine.SpyObj<DomainGoodsService>;
// beforeEach(() => {
// spectator = createService();
// goodsInSearchMock = spectator.inject(DomainGoodsService);
// });
// it('should create the service', () => {
// expect(spectator.service).toBeTruthy();
// });
// // describe('get defaultSettings()', () => {
// // it('should return the defaultSettings from the component store', () => {
// // const settings: QuerySettingsDTO = {
// // filter: [],
// // input: [],
// // };
// // spectator.service.patchState({ defaultSettings: settings });
// // expect(spectator.service.defaultSettings).toBe(settings);
// // });
// // });
// describe('get filter()', () => {
// it('shoudl return the filter from the component store', () => {
// const filter = UiFilter.create({});
// spectator.service.patchState({ filter });
// expect(spectator.service.filter).toBe(filter);
// });
// });
// // describe('get results()', () => {
// // it('shoudl return the filter from the component store', () => {
// // const results = [];
// // spectator.service.patchState({ results });
// // expect(spectator.service.results).toBe(results);
// // });
// // });
// describe('searchResult$', () => {
// it('should be an observable', () => {
// expect(isObservable(spectator.service.searchResult$)).toBe(true);
// });
// it('should return a value when _searchResultSubject emits a value', () => {
// const next = { error: false };
// let result;
// spectator.service.searchResult$.subscribe((res) => (result = res));
// spectator.service['_searchResultSubject'].next(next);
// expect(result).toBe(next);
// });
// });
// // describe('effect loadSettings', () => {
// // it('should call DomainGoodsInService.querySettings()', () => {
// // spectator.service.loadSettings();
// // expect(goodsInSearchMock.querySettings).toHaveBeenCalled();
// // });
// // });
// });

View File

@@ -1,189 +0,0 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
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 { filter, mergeMap, switchMap, take, tap, withLatestFrom } from 'rxjs/operators';
export interface GoodsInSearchState {
defaultSettings?: QuerySettingsDTO;
queryParams?: Record<string, string>;
filter?: UiFilter;
message?: string;
fetching: boolean;
hits: number;
results: OrderItemListItemDTO[];
searchOptions?: { take?: number; skip?: number };
}
@Injectable()
export class GoodsInSearchStore extends ComponentStore<GoodsInSearchState> {
get defaultSettings() {
return this.get((s) => s.defaultSettings);
}
readonly defaultSettings$ = this.select((s) => s.defaultSettings);
get filter() {
return this.get((s) => s.filter);
}
readonly filter$ = this.select((s) => s.filter);
get results() {
return this.get((s) => s.results);
}
readonly results$ = this.select((s) => s.results);
get hits() {
return this.get((s) => s.hits);
}
readonly hits$ = this.select((s) => s.hits);
get fetching() {
return this.get((s) => s.fetching);
}
readonly fetching$ = this.select((s) => s.fetching);
get queryParams() {
return this.get((s) => s.queryParams);
}
readonly queryParams$ = this.select((s) => s.queryParams);
get searchOptions() {
return this.get((s) => s.searchOptions);
}
set searchOptions(searchOptions: { take?: number; skip?: number }) {
this.patchState({ searchOptions });
}
private _searchResultSubject = new Subject<ListResponseArgsOfOrderItemListItemDTO>();
readonly searchResult$ = this._searchResultSubject.asObservable();
constructor(private _domainGoodsInService: DomainGoodsService) {
super({
fetching: false,
hits: 0,
results: [],
});
}
loadSettings = this.effect(($) =>
$.pipe(
switchMap(() =>
this._domainGoodsInService.goodsInQuerySettings().pipe(
tapResponse(
(res) => {
this.setDefaultSettings(res.result);
const filter = UiFilter.create(res.result);
if (this.queryParams) {
filter.fromQueryParams(this.queryParams);
}
this.setFilter(filter);
},
(err) => {
console.error('GoodsInSearchStore.loadSettings()', err);
}
)
)
)
)
);
setDefaultSettings(defaultSettings: QuerySettingsDTO) {
this.patchState({ defaultSettings });
}
setFilter(filter: UiFilter | QuerySettingsDTO) {
if (filter instanceof UiFilter) {
this.patchState({ filter });
} else {
this.patchState({ filter: UiFilter.create(filter) });
}
}
resetFilter(defaultQueryParams?: Record<string, string>) {
const filter = UiFilter.create(this.defaultSettings);
if (!!defaultQueryParams) {
filter?.fromQueryParams(defaultQueryParams);
}
this.setFilter(filter);
}
clearResults() {
this.patchState({
fetching: false,
hits: 0,
message: undefined,
results: [],
});
}
setQueryParams(queryParams: Record<string, string>) {
this.patchState({ queryParams });
if (this.filter instanceof UiFilter) {
this.filter?.fromQueryParams(queryParams);
this.setFilter(this.filter);
}
}
searchRequest(options?: { take?: number; skip?: number }) {
return this.filter$.pipe(
filter((f) => f instanceof UiFilter),
take(1),
mergeMap((filter) =>
this._domainGoodsInService.searchWareneingang({
...filter.getQueryToken(),
skip: options?.skip ?? this.results.length,
take: options?.take ?? 50,
})
)
);
}
search = this.effect(($) =>
$.pipe(
tap((_) => this.patchState({ fetching: true })),
withLatestFrom(this.results$),
switchMap(([_, _results]) =>
this.searchRequest(this.searchOptions).pipe(
tapResponse(
(res) => {
const results = [...(_results ?? []), ...(res.result ?? [])];
this.patchState({
hits: res.hits,
results,
fetching: false,
});
this._searchResultSubject.next(res);
},
(err: Error) => {
if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
this._searchResultSubject.next(err.error);
} else {
this._searchResultSubject.next({
error: true,
message: err.message,
});
}
this.patchState({ fetching: false });
console.error('GoodsInSearchStore.search()', err);
}
)
)
)
)
);
}

View File

@@ -1,4 +0,0 @@
// start:ng42.barrel
export * from './goods-in-search.component';
export * from './goods-in-search.module';
// end:ng42.barrel

View File

@@ -1,51 +0,0 @@
// import { DomainGoodsService } from '@domain/oms';
// import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
// import { UiInput, UiInputType } from '@ui/filter';
// import { cold, hot } from 'jasmine-marbles';
// import { of } from 'rxjs';
// import { GoodsInSearchMainAutocompleteProvider } from './goods-in-search-main-autocomplete.provider';
// describe('GoodsInSearchMainAutocompleteProvider', () => {
// let spectator: SpectatorService<GoodsInSearchMainAutocompleteProvider>;
// let domainGoodsInServiceMock: jasmine.SpyObj<DomainGoodsService>;
// const createProvider = createServiceFactory({
// service: GoodsInSearchMainAutocompleteProvider,
// mocks: [DomainGoodsService],
// });
// beforeEach(() => {
// spectator = createProvider();
// domainGoodsInServiceMock = spectator.inject(DomainGoodsService);
// });
// it('should create the provider', () => {
// expect(spectator.service).toBeDefined();
// });
// it('should have the value "main" for the property for', () => {
// expect(spectator.service.for).toBe('main');
// });
// // describe('complete(input: UiInput)', () => {
// // it('should call DomainGoodsInService.complete with the UiInput.value as input', () => {
// // domainGoodsInServiceMock.complete.and.returnValue(of({ result: [] }));
// // spectator.service.complete(UiInput.create({ value: 'my value', type: UiInputType.Text }));
// // expect(domainGoodsInServiceMock.complete).toHaveBeenCalledWith({ input: 'my value', take: 5 });
// // });
// // it('should map the response to the response.result', () => {
// // const result = [{ display: 'display 1' }, { display: 'display 1' }];
// // domainGoodsInServiceMock.complete.and.returnValue(hot('-(a|)', { a: { result } }));
// // expect(spectator.service.complete(UiInput.create({ value: 'my value', type: UiInputType.Text }))).toBeObservable(
// // cold('-(a|)', { a: result })
// // );
// // });
// // it('should catch the error', () => {
// // domainGoodsInServiceMock.complete.and.returnValue(hot('-#', {}, new Error()));
// // expect(spectator.service.complete(UiInput.create({ value: 'my value', type: UiInputType.Text }))).toBeObservable(
// // cold('-(a|)', { a: [] })
// // );
// // });
// // });
// });

View File

@@ -1,36 +0,0 @@
import { Injectable } from '@angular/core';
import { DomainGoodsService } from '@domain/oms';
import { UiFilterAutocomplete, UiFilterAutocompleteProvider, UiInput } from '@ui/filter';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@Injectable()
export class GoodsInSearchMainAutocompleteProvider extends UiFilterAutocompleteProvider {
for = 'goods-in';
constructor(private domainGoodsInService: DomainGoodsService) {
super();
}
complete(input: UiInput): Observable<UiFilterAutocomplete[]> {
const token = input?.parent?.parent?.getQueryToken();
const filter = token?.filter;
const type = Object.keys(token?.input).join(';');
if (input.value.length >= 3) {
return this.domainGoodsInService
.wareneingangComplete({
filter,
input: input.value,
take: 5,
type,
})
.pipe(
catchError(() => of({ result: [] })),
map((res) => res.result)
);
} else {
return of([]);
}
}
}

View File

@@ -1,30 +0,0 @@
<a class="goods-in-list-navigation" [routerLink]="['/filiale/goods/in', 'list']">
Wareneingangsliste
</a>
<a class="goods-in-list-remission-preview" [routerLink]="['/filiale/goods/in', 'preview']">
Abholfachremissionsvorschau
</a>
<a class="goods-in-list-cleanup" [routerLink]="['/filiale/goods/in', 'cleanup']">
Abholfachbereinigung
</a>
<a class="goods-in-list-reservation" [routerLink]="['/filiale/goods/in', 'reservation']">
Reservierungen
</a>
<div class="search-main">
<h1 class="search-main-title">Bestellpostensuche</h1>
<p class="search-main-paragraph">
Scannen Sie den Artikel,<br />
um diesen einzubuchen oder<br />
suchen Sie nach Kundennamen.
</p>
<ng-container *ngIf="filter$ | async; let filter">
<ui-filter-input-group-main
*ngIf="filter?.input | group: 'main'; let inputGroup"
[inputGroup]="inputGroup"
[loading]="loading$ | async"
(search)="search()"
[hint]="message"
[scanner]="true"
></ui-filter-input-group-main>
</ng-container>
</div>

View File

@@ -1,34 +0,0 @@
:host {
@apply block;
}
.goods-in-list-navigation,
.goods-in-list-reservation,
.goods-in-list-cleanup,
.goods-in-list-remission-preview {
@apply text-center text-xl text-inactive-branch block bg-white rounded-t font-bold no-underline py-4;
}
.goods-in-list-reservation,
.goods-in-list-cleanup,
.goods-in-list-remission-preview {
@apply shadow-card;
}
.search-main {
@apply bg-white text-center rounded-t py-5 shadow-card;
height: calc(100vh - 510px);
.search-main-title {
@apply text-h3 font-bold;
}
.search-main-paragraph {
@apply text-h3 mb-12 mt-6;
}
ui-filter-input-group-main {
@apply block mx-auto;
max-width: 600px;
}
}

View File

@@ -1,72 +0,0 @@
// import { fakeAsync } from '@angular/core/testing';
// import { RouterTestingModule } from '@angular/router/testing';
// import { BreadcrumbService } from '@core/breadcrumb';
// import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator';
// import { UiFilter, UiInputGroupSelectorPipe } from '@ui/filter';
// import { hot } from 'jasmine-marbles';
// import { MockPipe } from 'ng-mocks';
// import { EMPTY } from 'rxjs';
// import { GoodsInSearchStore } from '../goods-in-search.store';
// // import { GoodsInSearchService } from '../goods-in-search.service';
// import { GoodsInSearchMainComponent } from './goods-in-search-main.component';
// describe('GoodsInSearchMainComponent', () => {
// let spectator: Spectator<GoodsInSearchMainComponent>;
// const createComponent = createComponentFactory({
// component: GoodsInSearchMainComponent,
// imports: [RouterTestingModule],
// providers: [
// mockProvider(BreadcrumbService),
// mockProvider(GoodsInSearchStore, {
// loadSettings: async () => {},
// }),
// ],
// declarations: [MockPipe(UiInputGroupSelectorPipe)],
// });
// // let goodsInSearchServiceMock: jasmine.SpyObj<GoodsInSearchService>;
// beforeEach(() => {
// spectator = createComponent();
// // goodsInSearchServiceMock = spectator.inject(GoodsInSearchService);
// });
// // it('should create the component', () => {
// // expect(spectator.component).toBeTruthy();
// // });
// // describe('get settings()', () => {
// // it('should retun the settings from GoodsInSearchService.settings', () => {
// // const settings = UiFilter.create({});
// // goodsInSearchServiceMock.settings = settings;
// // expect(spectator.component.settings).toBe(settings);
// // });
// // });
// // describe('ngOnInit()', () => {
// // it('should subscribe to settingsChange and add it to the subscriptions', () => {
// // spyOn(spectator.component['_subscriptions'], 'add');
// // spectator.component.ngOnInit();
// // expect(spectator.component['_subscriptions'].add).toHaveBeenCalled();
// // });
// // });
// // describe('ngOnDestroy()', () => {
// // it('should unsubscribe the subscriptions', () => {
// // spyOn(spectator.component['_subscriptions'], 'unsubscribe');
// // spectator.component.ngOnDestroy();
// // expect(spectator.component['_subscriptions'].unsubscribe).toHaveBeenCalled();
// // });
// // });
// // describe('search()', () => {
// // it('should call GoodsInSearchService.search() to start a search', () => {
// // spectator.component.search();
// // expect(goodsInSearchServiceMock.search).toHaveBeenCalled();
// // });
// // });
// });

View File

@@ -1,166 +0,0 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { UiFilterInputGroupMainComponent } from '@ui/filter';
import { Subscription } from 'rxjs';
import { first, debounceTime } from 'rxjs/operators';
import { GoodsInSearchStore } from '../goods-in-search.store';
@Component({
selector: 'page-goods-in-search-main',
templateUrl: 'goods-in-search-main.component.html',
styleUrls: ['goods-in-search-main.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInSearchMainComponent implements OnInit, OnDestroy {
filter$ = this._goodsInSearchStore.filter$;
loading$ = this._goodsInSearchStore.fetching$;
message: string;
private _subscriptions = new Subscription();
@ViewChild(UiFilterInputGroupMainComponent, { static: false })
uiFilterGroupMain: UiFilterInputGroupMainComponent;
constructor(
private _goodsInSearchStore: GoodsInSearchStore,
private _cdr: ChangeDetectorRef,
private _router: Router,
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService,
private readonly _config: Config
) {}
ngOnInit() {
this._subscriptions.add(
this._goodsInSearchStore.filter$.subscribe(() => {
this._cdr.markForCheck();
})
);
this._subscriptions.add(
this._activatedRoute.queryParams.pipe(debounceTime(50)).subscribe((queryParams) => {
this._goodsInSearchStore.setQueryParams(queryParams);
this.removeBreadcrumbs();
this.updateBreadcrumb(queryParams);
})
);
}
ngOnDestroy() {
this._subscriptions.unsubscribe();
}
async removeBreadcrumbs() {
const resultCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'results'])
.pipe(first())
.toPromise();
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'details'])
.pipe(first())
.toPromise();
const editCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'edit'])
.pipe(first())
.toPromise();
const listCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'list'])
.pipe(first())
.toPromise();
const reservationCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'reservation'])
.pipe(first())
.toPromise();
const cleanupCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'cleanup'])
.pipe(first())
.toPromise();
const previewCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'preview'])
.pipe(first())
.toPromise();
resultCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
detailsCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
listCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
reservationCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
cleanupCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
previewCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
}
async search() {
this.uiFilterGroupMain?.cancelAutocomplete();
this._goodsInSearchStore.clearResults();
await this.updateQueryParams();
this.message = undefined;
this._goodsInSearchStore.searchResult$.pipe(first()).subscribe((result) => {
if (result.error) {
} else {
if (result.hits > 0) {
if (result.hits === 1) {
const orderItem = result.result[0];
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(orderItem.buyerNumber)}/order/${encodeURIComponent(
orderItem.orderNumber
)}/item/${orderItem.orderItemSubsetId}/${orderItem.processingStatus}`,
]);
} else {
this._router.navigate(['/filiale', 'goods', 'in', 'results'], {
queryParams: this._goodsInSearchStore.filter.getQueryParams(),
});
}
} else {
this.message = 'keine Suchergebnisse';
}
}
this._cdr.markForCheck();
});
this._goodsInSearchStore.search();
}
async updateBreadcrumb(queryParams: Record<string, string>) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsIn'),
name: 'Abholfach',
path: '/filiale/goods/in',
tags: ['goods-in', 'main', 'filter'],
section: 'branch',
params: queryParams,
});
}
async updateQueryParams() {
const queryParams = this._goodsInSearchStore.filter?.getQueryParams();
await this._router.navigate([], { queryParams });
this.updateBreadcrumb(queryParams);
}
}

View File

@@ -1,13 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { GoodsInSearchMainComponent } from './goods-in-search-main.component';
import { UiFilterNextModule } from '@ui/filter';
import { RouterModule } from '@angular/router';
@NgModule({
imports: [CommonModule, UiFilterNextModule, RouterModule],
exports: [GoodsInSearchMainComponent],
declarations: [GoodsInSearchMainComponent],
})
export class GoodsInSearchMainModule {}

View File

@@ -1,4 +0,0 @@
// start:ng42.barrel
export * from './goods-in-search-main.component';
export * from './goods-in-search-main.module';
// end:ng42.barrel

View File

@@ -1,48 +0,0 @@
<div class="goods-in-search-results-headline">
<h1 class="goods-in-search-results-title">
Bestellpostentrefferliste
</h1>
<p class="goods-in-search-results-paragraph">
Setzen Sie die Bestellposten<br />
auf eingetroffen
</p>
</div>
<div class="hits">{{ hits$ | async }} Titel</div>
<ui-scroll-container
*ngIf="!(listEmpty$ | async); else emptyMessage"
[loading]="loading$ | async"
(reachEnd)="loadMore()"
[deltaEnd]="150"
[itemLength]="(items$ | async).length"
[containerHeight]="24.5"
>
<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
*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"
[item]="item"
[showCompartmentCode]="firstItem"
(click)="navigateToDetails(item)"
>
</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="!lastOrderNumber"></div>
</ng-container>
</shared-goods-in-out-order-group>
</ui-scroll-container>
<ng-template #emptyMessage>
<div class="empty-message">
Es sind im Moment keine Bestellposten vorhanden,<br />
die bearbeitet werden können.
</div>
</ng-template>

View File

@@ -1,42 +0,0 @@
:host {
@apply block relative;
}
.goods-in-search-results-headline {
@apply text-center;
margin-bottom: -16px;
.goods-in-search-results-title {
@apply text-h3 font-bold m-0;
}
.goods-in-search-results-paragraph {
@apply text-h3 m-0 mt-1;
}
}
.hits {
@apply text-active-branch text-right mb-3 font-semibold text-p2;
}
.empty-message {
@apply bg-white text-center font-semibold text-inactive-branch py-10 rounded;
}
.divider {
height: 2px;
}
shared-goods-in-out-order-group-item {
@apply cursor-pointer;
}
::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

@@ -1,76 +0,0 @@
// import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator';
// import { GoodsInSearchResultsComponent } from './goods-in-search-results.component';
// import { BehaviorSubject, of } from 'rxjs';
// import { UiCommonModule } from '@ui/common';
// // import { GoodsInSearchService } from '../goods-in-search.service';
// import { RouterTestingModule } from '@angular/router/testing';
// import { GoodsInSearchStore } from '../goods-in-search.store';
// import { BreadcrumbService } from '@core/breadcrumb';
// describe('GoodsInSearchResultsComponent', () => {
// let spectator: Spectator<GoodsInSearchResultsComponent>;
// let goodsInSearchStore: GoodsInSearchStore;
// const createComponent = createComponentFactory({
// component: GoodsInSearchResultsComponent,
// imports: [UiCommonModule, RouterTestingModule],
// providers: [
// mockProvider(BreadcrumbService, {
// getBreadcrumbsByKeyAndTags$: async () => {},
// }),
// mockProvider(GoodsInSearchStore, {
// results$: new BehaviorSubject({ error: false }),
// fetching$: new BehaviorSubject<boolean>(false),
// search: async () => {},
// loadSettings: async () => {},
// }),
// ],
// });
// beforeEach(() => {
// spectator = createComponent();
// goodsInSearchStore = spectator.inject(GoodsInSearchStore);
// });
// // it('should create', () => {
// // expect(spectator.component).toBeTruthy();
// // });
// // describe('loading$', () => {
// // it('should render the UiLoaderComponent if loading$ is true', () => {
// // spectator.component.loading$.next(true);
// // spectator.detectComponentChanges();
// // expect(spectator.query('.loading-text')).toBeVisible();
// // });
// // });
// // describe('checkIfReload()', () => {
// // it('should call GoodsInFilterService.search() if HTML Element contains the class last and itemLength < hits', async () => {
// // const ele = document.createElement('div');
// // ele.classList.add('last');
// // spectator.component.hits$ = of(6);
// // spectator.component.items$ = of(new Array(5));
// // spectator.detectComponentChanges();
// // await spectator.component.checkIfReload(ele);
// // expect(goodsInSearchService.search).toHaveBeenCalled();
// // });
// // it('should not call GoodsInFilterService.search() if itemLength >= hits', async () => {
// // const ele = document.createElement('div');
// // ele.classList.add('last');
// // spectator.component.hits$ = of(5);
// // spectator.component.items$ = of(new Array(6));
// // spectator.detectComponentChanges();
// // await spectator.component.checkIfReload(ele);
// // expect(goodsInSearchService.search).not.toHaveBeenCalled();
// // });
// // it('should not call GoodsInFilterService.search() if HTML Element does not contain the class last', async () => {
// // const ele = document.createElement('div');
// // spectator.component.hits$ = of(6);
// // spectator.component.items$ = of(new Array(5));
// // spectator.detectComponentChanges();
// // await spectator.component.checkIfReload(ele);
// // expect(goodsInSearchService.search).not.toHaveBeenCalled();
// // });
// // });
// });

View File

@@ -1,170 +0,0 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { first, map, shareReplay, takeUntil } from 'rxjs/operators';
import { OrderItemListItemDTO } from '@swagger/oms';
import { ActivatedRoute, Router } from '@angular/router';
import { GoodsInSearchStore } from '../goods-in-search.store';
import { combineLatest, Subject } from 'rxjs';
import { BreadcrumbService } from '@core/breadcrumb';
import { UiScrollContainerComponent } from '@ui/scroll-container';
import { Config } from '@core/config';
@Component({
selector: 'page-goods-in-search-results',
templateUrl: 'goods-in-search-results.component.html',
styleUrls: ['goods-in-search-results.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInSearchResultsComponent implements OnInit, OnDestroy {
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
items$ = this._goodsInSearchStore.results$.pipe(shareReplay());
hits$ = this._goodsInSearchStore.hits$;
loading$ = this._goodsInSearchStore.fetching$.pipe(shareReplay());
listEmpty$ = combineLatest([this.loading$, this.hits$]).pipe(
map(([loading, hits]) => !loading && hits === 0),
shareReplay()
);
byBuyerNumberFn = (item: OrderItemListItemDTO) => item.buyerNumber;
byOrderNumberFn = (item: OrderItemListItemDTO) => item.orderNumber;
byProcessingStatusFn = (item: OrderItemListItemDTO) => item.processingStatus;
byCompartmentCodeFn = (item: OrderItemListItemDTO) =>
!!item.compartmentInfo ? `${item.compartmentCode}_${item.compartmentInfo}` : item.compartmentCode;
private _onDestroy$ = new Subject();
constructor(
private _goodsInSearchStore: GoodsInSearchStore,
private _router: Router,
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService,
private readonly _config: Config
) {}
ngOnInit() {
this._goodsInSearchStore.setQueryParams(this._activatedRoute.snapshot.queryParams);
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((queryParams) => {
this.updateBreadcrumb(queryParams);
});
this.initInitialSearch();
this.createBreadcrumb();
this.removeBreadcrumbs();
}
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.complete();
this.updateBreadcrumb(this._goodsInSearchStore.filter?.getQueryParams());
}
async removeBreadcrumbs() {
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'details'])
.pipe(first())
.toPromise();
const editCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'edit'])
.pipe(first())
.toPromise();
detailsCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
}
async createBreadcrumb() {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsIn'),
name: this.getBreadcrumbName(),
path: '/filiale/goods/in/results',
section: 'branch',
params: this._goodsInSearchStore.filter?.getQueryParams(),
tags: ['goods-in', 'results', 'filter'],
});
}
async updateBreadcrumb(queryParams: Record<string, string> = this._goodsInSearchStore.filter?.getQueryParams()) {
const scroll_position = this.scrollContainer?.scrollPos;
const take = this._goodsInSearchStore.results?.length;
if (queryParams) {
const crumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'results', 'filter'])
.pipe(first())
.toPromise();
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
const params = { ...queryParams, scroll_position, take };
for (const crumb of crumbs) {
this._breadcrumb.patchBreadcrumb(crumb.id, {
name,
params,
});
}
}
}
getBreadcrumbName() {
const input = this._goodsInSearchStore.filter?.getQueryParams()?.main_qs;
return input?.replace('ORD:', '') ?? 'Alle';
}
initInitialSearch() {
if (this._goodsInSearchStore.hits === 0) {
this._goodsInSearchStore.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.hits === 0) {
await this._router.navigate(['/filiale/goods/in'], { queryParams: this._goodsInSearchStore.filter.getQueryParams() });
} else {
await this.createBreadcrumb();
if (result.hits === 1) {
await this.navigateToDetails(result.result[0]);
} else {
if (!!this._goodsInSearchStore.searchOptions?.take) {
this._goodsInSearchStore.searchOptions = undefined;
this.scrollContainer.scrollTo(Number(scroll_position ?? 0));
}
}
}
});
this._goodsInSearchStore.search();
}
const { scroll_position, take } = this._goodsInSearchStore.queryParams;
if (!!take && !!scroll_position) {
this._goodsInSearchStore.searchOptions = { take: Number(take) };
}
}
async loadMore() {
if (this._goodsInSearchStore.hits > this._goodsInSearchStore.results.length && !this._goodsInSearchStore.fetching) {
this._goodsInSearchStore.search();
}
}
navigateToDetails(orderItem: OrderItemListItemDTO) {
const customerNumber = orderItem.buyerNumber;
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const orderItemId = orderItem.orderItemSubsetId;
this._router.navigate([
`/filiale/goods/in/details/customer/${encodeURIComponent(customerNumber)}/order/${encodeURIComponent(
orderNumber
)}/item/${orderItemId}/${processingStatus}`,
]);
}
}

View File

@@ -1,14 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { GoodsInSearchResultsComponent } from './goods-in-search-results.component';
import { UiCommonModule } from '@ui/common';
import { GoodsInOutOrderGroupModule } from '@shared/components/goods-in-out';
import { UiScrollContainerModule } from '@ui/scroll-container';
@NgModule({
imports: [CommonModule, GoodsInOutOrderGroupModule, UiCommonModule, UiScrollContainerModule],
exports: [GoodsInSearchResultsComponent],
declarations: [GoodsInSearchResultsComponent],
})
export class GoodsInSearchResultsModule {}

View File

@@ -1,4 +0,0 @@
// start:ng42.barrel
export * from './goods-in-search-results.component';
export * from './goods-in-search-results.module';
// end:ng42.barrel

View File

@@ -1,6 +1,7 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, inject } from '@angular/core';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { PickupShelfInNavigationService } from '@shared/services';
@Component({
selector: 'page-goods-in',
@@ -9,15 +10,21 @@ import { Config } from '@core/config';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInComponent implements OnInit {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
goodsInKey = this._config.get('process.ids.goodsIn');
pickupShelfKey = this._config.get('process.ids.pickupShelf');
constructor(private readonly _config: Config, private _breadcrumb: BreadcrumbService) {}
ngOnInit() {
const nav = this._pickupShelfInNavigationService.defaultRoute();
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsIn'),
name: 'Abholfach',
path: '/filiale/goods/in',
path: nav.path,
params: nav.queryParams,
tags: ['goods-in', 'main', 'filter'],
section: 'branch',
});