#3790 Kundenbestellungen

This commit is contained in:
Lorenz Hilpert
2023-02-24 13:42:58 +01:00
parent 4772e24c78
commit 4da7f02cf7
56 changed files with 2140 additions and 10 deletions

View File

@@ -1,7 +1,11 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { AuthService } from './auth.service';
import { OAuthModule } from 'angular-oauth2-oidc';
@NgModule({})
import { IfRoleDirective } from './if-role.directive';
@NgModule({
declarations: [IfRoleDirective],
exports: [IfRoleDirective],
})
export class AuthModule {
static forRoot(): ModuleWithProviders<AuthModule> {
return {

View File

@@ -1,3 +1,4 @@
import { coerceArray, coerceStringArray } from '@angular/cdk/coercion';
import { Injectable } from '@angular/core';
import { Config } from '@core/config';
import { isNullOrUndefined } from '@utils/common';
@@ -85,4 +86,16 @@ export class AuthService {
async logout() {
await this._oAuthService.revokeTokenAndLogout();
}
hasRole(role: string | string[]) {
const roles = coerceArray(role);
const userRoles = this.getClaimByKey('role');
if (isNullOrUndefined(userRoles)) {
return false;
}
return roles.every((r) => userRoles.includes(r));
}
}

View File

@@ -0,0 +1,65 @@
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator';
import { IfRoleDirective } from './if-role.directive';
import { AuthService } from './auth.service';
import { TemplateRef, ViewContainerRef } from '@angular/core';
describe('IfRoleDirective', () => {
let spectator: SpectatorDirective<IfRoleDirective>;
const createDirective = createDirectiveFactory({
directive: IfRoleDirective,
mocks: [AuthService],
});
it('should create an instance', () => {
spectator = createDirective(`<div *ifRole="'admin'"></div>`);
expect(spectator.directive).toBeTruthy();
});
it('should render template when user has the role', () => {
spectator = createDirective(`<div *ifRole="'admin'"></div>`);
const authService = spectator.inject(AuthService);
const viewContainerRef = spectator.inject(ViewContainerRef);
const templateRef = spectator.inject(TemplateRef);
authService.hasRole.and.returnValue(true);
spectator.directive.ngOnChanges();
expect(viewContainerRef.createEmbeddedView).toHaveBeenCalledWith(templateRef, spectator.directive.getContext());
});
it('should render else template when user does not have the role', () => {
authService.hasRole.and.returnValue(false);
const elseTemplateRef = {} as TemplateRef<any>;
spectator = createDirective(`<ng-template #elseTemplateRef></ng-template><div *ifRole="'admin'; else elseTemplateRef"></div>`, {
hostProps: {
elseTemplateRef,
},
});
spectator.directive.ngOnChanges();
expect(viewContainerRef.createEmbeddedView).toHaveBeenCalledWith(elseTemplateRef, spectator.directive.getContext());
});
it('should render else template when user does not have the role using ifNotRole input', () => {
authService.hasRole.and.returnValue(false);
const elseTemplateRef = {} as TemplateRef<any>;
spectator = createDirective(`<ng-template #elseTemplateRef></ng-template><div *ifNotRole="'admin'; else elseTemplateRef"></div>`, {
hostProps: {
elseTemplateRef,
},
});
spectator.directive.ngOnChanges();
expect(viewContainerRef.createEmbeddedView).toHaveBeenCalledWith(elseTemplateRef, spectator.directive.getContext());
});
it('should clear view when user does not have the role and elseTemplateRef is not defined', () => {
authService.hasRole.and.returnValue(false);
spectator = createDirective(`<div *ifRole="'admin'"></div>`);
spectator.directive.ngOnChanges();
expect(viewContainerRef.clear).toHaveBeenCalled();
});
it('should set $implicit to ifRole or ifNotRole input', () => {
spectator = createDirective(`<div *ifRole="'admin'"></div>`);
expect(spectator.directive.getContext().$implicit).toEqual('admin');
spectator.setInput('ifNotRole', 'user');
expect(spectator.directive.getContext().$implicit).toEqual('user');
});
});

View File

@@ -0,0 +1,59 @@
import { Directive, Input, OnChanges, TemplateRef, ViewContainerRef } from '@angular/core';
import { AuthService } from './auth.service';
@Directive({
selector: '[ifRole],[ifRoleElse],[ifNotRole],[ifNotRoleElse]',
})
export class IfRoleDirective implements OnChanges {
@Input()
ifRole: string | string[];
@Input()
ifRoleElse: TemplateRef<any>;
@Input()
ifNotRole: string | string[];
@Input()
ifNotRoleElse: TemplateRef<any>;
get renderTemplateRef() {
if (this.ifRole) {
return this._authService.hasRole(this.ifRole);
}
if (this.ifNotRole) {
return !this._authService.hasRole(this.ifNotRole);
}
return false;
}
get elseTemplateRef() {
return this.ifRoleElse || this.ifNotRoleElse;
}
constructor(private _templateRef: TemplateRef<any>, private _viewContainer: ViewContainerRef, private _authService: AuthService) {}
ngOnChanges() {
this.render();
}
render() {
if (this.renderTemplateRef) {
this._viewContainer.createEmbeddedView(this._templateRef, this.getContext());
return;
}
if (this.elseTemplateRef) {
this._viewContainer.createEmbeddedView(this.elseTemplateRef, this.getContext());
return;
}
this._viewContainer.clear();
}
getContext(): { $implicit: string | string[] } {
return {
$implicit: this.ifRole || this.ifNotRole,
};
}
}

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import {
AutocompleteTokenDTO,
BranchService,
ChangeStockStatusCodeValues,
HistoryDTO,
@@ -10,6 +11,7 @@ import {
OrderItemSubsetDTO,
OrderListItemDTO,
OrderService,
QueryTokenDTO,
ReceiptService,
ResponseArgsOfIEnumerableOfHistoryDTO,
StatusValues,
@@ -246,4 +248,25 @@ export class DomainOmsService {
)
);
}
complete(payload: AutocompleteTokenDTO) {
return this.orderService.OrderQueryOrderItemAutocomplete(payload);
}
search(payload: QueryTokenDTO) {
return this.orderService.OrderQueryOrderItem(payload);
}
getOrderItemsByOrderNumber(orderNumber: string) {
return this.orderService.OrderQueryOrderItem({
filter: { all_branches: 'true', archive: 'true' },
input: {
qs: orderNumber,
},
});
}
settings() {
return this.orderService.OrderOrderItemQuerySettings();
}
}

View File

@@ -56,6 +56,17 @@ const routes: Routes = [
canActivate: [CanActivateProductWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateGoodsOutGuard],
},
{
path: ':processId/order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateGoodsOutWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'customer',
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),

View File

@@ -47,10 +47,14 @@
<ui-icon icon="customer" size="24px"></ui-icon>
Kundensuche
</a>
<a [routerLink]="[customerBasePath$ | async, 'goods', 'out']" routerLinkActive="active">
<a *ifRole="'Store'" [routerLink]="[customerBasePath$ | async, 'goods', 'out']" routerLinkActive="active">
<ui-icon icon="box_out" size="24px"></ui-icon>
Warenausgabe
</a>
<a *ifRole="'CallCenter'" [routerLink]="[customerBasePath$ | async, 'order']" routerLinkActive="active">
<ui-svg-icon icon="package-variant-closed" [size]="28"></ui-svg-icon>
Kundenbestellungen
</a>
</ng-container>
<ng-container *ngIf="section === 'branch'">
<a [routerLink]="['/filiale/assortment']" routerLinkActive="active">

View File

@@ -9,9 +9,10 @@ import { ShellFooterModule } from '@shell/footer';
import { ShellComponent } from './shell.component';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { AuthModule } from '@core/auth';
@NgModule({
imports: [RouterModule, CommonModule, ShellHeaderModule, ShellProcessModule, ShellFooterModule, UiIconModule, OverlayModule],
imports: [RouterModule, CommonModule, ShellHeaderModule, ShellProcessModule, ShellFooterModule, UiIconModule, OverlayModule, AuthModule],
exports: [ShellComponent],
declarations: [ShellComponent],
providers: [],

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/page/goods-out",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,10 @@
<shared-goods-in-out-order-details (actionHandled)="actionHandled($event)" [itemsSelectable]="true">
<shared-goods-in-out-order-details-header [order]="order$ | async" (editClick)="navigateToEditPage($event)">
</shared-goods-in-out-order-details-header>
<shared-goods-in-out-order-details-item
*ngFor="let item of items$ | async"
[orderItem]="item"
[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>

View File

@@ -0,0 +1,4 @@
:host {
@apply block overflow-y-scroll box-border;
max-height: calc(100vh - 280px);
}

View File

@@ -0,0 +1,229 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainGoodsService, DomainOmsService, OrderItemsContext } from '@domain/oms';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ListResponseArgsOfOrderItemListItemDTO, OrderItemListItemDTO, OrderItemProcessingStatusValue } from '@swagger/oms';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { combineLatest, Observable, Subject } from 'rxjs';
import { debounceTime, filter, first, map, shareReplay, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
export interface CustomerOrderDetailsComponentState {
fetching: boolean;
orderNumber?: string;
buyerNumber?: string;
processingStatus?: OrderItemProcessingStatusValue;
compartmentCode?: string;
items?: OrderItemListItemDTO[];
orderId?: number;
archive?: boolean;
}
@Component({
selector: 'page-customer-order-details',
templateUrl: 'customer-order-details.component.html',
styleUrls: ['customer-order-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderDetailsComponent extends ComponentStore<CustomerOrderDetailsComponentState> implements OnInit, OnDestroy {
orderNumber$ = this.select((s) => decodeURIComponent(s.orderNumber ?? '') || undefined);
buyerNumber$ = this.select((s) => s.buyerNumber);
compartmentCode$ = this.select((s) => decodeURIComponent(s.compartmentCode ?? '') || undefined);
processingStatus$ = this.select((s) => s.processingStatus);
archive$ = this.select((s) => s.archive);
get archive() {
return this.get((s) => s.archive);
}
items$ = this.select((s) => s.items ?? []);
orderId$ = this.select((s) => s.orderId);
get items() {
return this.get((s) => s.items);
}
order$ = this.orderId$.pipe(
filter((orderId) => !!orderId),
switchMap((orderId) => this._omsService.getOrder(orderId)),
shareReplay()
);
get processId() {
return +this._activatedRoute.snapshot.parent.data.processId;
}
processId$ = this._activatedRoute.parent.data.pipe(map((params) => +params.processId));
private _onDestroy$ = new Subject();
constructor(
private _activatedRoute: ActivatedRoute,
private _domainGoodsInService: DomainOmsService,
private _omsService: DomainOmsService,
private _breadcrumb: BreadcrumbService,
private _router: Router,
private _uiModal: UiModalService
) {
super({
fetching: false,
});
}
ngOnInit() {
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((params) => {
const buyerNumber: string = decodeURIComponent(params.buyerNumber ?? '');
const archive = params?.archive || false;
this.patchState({ buyerNumber, archive });
});
this._activatedRoute.params.pipe(takeUntil(this._onDestroy$)).subscribe(async (params) => {
const orderNumber: string = params?.orderNumber;
const compartmentCode = params?.compartmentCode;
const processingStatus = +params?.processingStatus as OrderItemProcessingStatusValue;
this.patchState({ orderNumber, compartmentCode, processingStatus });
await this.removeDetailsCrumbs();
this.loadItems();
});
this.removeBreadcrumbs();
}
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.unsubscribe();
}
async updateBreadcrumb(item: OrderItemListItemDTO) {
if (item) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this.processId,
name: item?.compartmentCode || item?.orderNumber,
path: this.getDetailsPath(item),
params: {
buyerNumber: item.buyerNumber,
archive: this.archive,
},
section: 'customer',
tags: ['customer-order', 'details', item?.compartmentCode || item?.orderNumber],
});
}
}
async removeBreadcrumbs() {
const editCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this.processId, ['customer-order', 'edit'])
.pipe(first())
.toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
}
async removeDetailsCrumbs() {
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(this.processId, ['customer-order', 'details'])
.pipe(first())
.toPromise();
detailsCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
}
// tslint:disable-next-line: member-ordering
loadItems = this.effect(($) =>
$.pipe(
tap(() => this.patchState({ fetching: false })),
debounceTime(500),
withLatestFrom(this.orderNumber$, this.compartmentCode$, this.archive$),
switchMap(([_, orderNumber, compartmentCode, archive]) => {
let request$: Observable<ListResponseArgsOfOrderItemListItemDTO>;
// if (compartmentCode) {
// request$ = this._domainGoodsInService.getWarenausgabeItemByCompartment(compartmentCode, archive);
// } else {
request$ = this._domainGoodsInService.getOrderItemsByOrderNumber(orderNumber);
// }
return combineLatest([request$, this.processingStatus$, this.buyerNumber$]).pipe(
tapResponse(
([res, processingStatus, buyerNumber]) => {
const items = res.result.filter((item) => {
return item.processingStatus === processingStatus && (!!buyerNumber ? item.buyerNumber === buyerNumber : true);
});
this.openModalIfItemsHaveDifferentCustomers(items);
this.patchState({
items,
orderId: items[0].orderId,
});
this.updateBreadcrumb(items[0]);
},
(err) => {},
() => {
this.patchState({ fetching: false });
}
)
);
})
)
);
openModalIfItemsHaveDifferentCustomers(items: OrderItemListItemDTO[]) {
const buyerNumbers = new Set(items.map((item) => item.buyerNumber));
if (buyerNumbers.size > 1) {
this._uiModal.open({
content: UiMessageModalComponent,
title: 'Achtung',
data: {
message: 'Anzeige ist fehlerhaft.\nEs wurden mehrere Positionen gefunden, die mehreren Kunden unterschiedlichen sind.',
},
});
}
}
navigateToEditPage(orderItem: OrderItemListItemDTO) {
this._router.navigate([this.getEditPath(orderItem)], {
queryParams: { buyerNumber: orderItem.buyerNumber, archive: this.archive },
});
}
navigateToDetailsPage(item: OrderItemListItemDTO) {
this._router.navigate([this.getDetailsPath(item)], {
queryParams: { buyerNumber: item.buyerNumber, archive: this.archive },
});
}
navigateToLandingPage() {
this._router.navigate([`/kunde/${this.processId}/order`]);
}
async actionHandled(handler: { orderItemsContext: OrderItemsContext; command: string; navigation: 'details' | 'main' | 'reservation' }) {
if (handler.navigation === 'main') {
this.navigateToLandingPage();
} else {
this.navigateToDetailsPage(handler.orderItemsContext.items.find((_) => true));
await this.removeDetailsCrumbs();
this.loadItems();
}
}
getDetailsPath(item: OrderItemListItemDTO) {
return item?.compartmentCode
? `/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/${this.processId}/order/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
}
getEditPath(item: OrderItemListItemDTO) {
return item?.compartmentCode
? `/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}/edit`
: `/kunde/${this.processId}/order/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}/edit`;
}
}

View File

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

View File

@@ -0,0 +1,2 @@
export * from './customer-order-details.component';
export * from './customer-order-details.module';

View File

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

View File

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

View File

@@ -0,0 +1,112 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainGoodsService, DomainOmsService } from '@domain/oms';
import { OrderItemListItemDTO } from '@swagger/oms';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { combineLatest, Observable } from 'rxjs';
import { map, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
@Component({
selector: 'page-customer-order-edit',
templateUrl: 'customer-order-edit.component.html',
styleUrls: ['customer-order-edit.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderEditComponent implements OnInit {
get processId() {
return +this._activatedRoute.snapshot.parent.data.processId;
}
processId$ = this._activatedRoute.parent.data.pipe(map((params) => +params.processId));
orderNumber$: Observable<string> = this._activatedRoute.params.pipe(
map((params) => decodeURIComponent(params.orderNumber ?? '') || undefined)
);
processingStatus$ = this._activatedRoute.params.pipe(map((params) => params?.processingStatus || undefined));
archive$ = this._activatedRoute.queryParams.pipe(map((params) => params?.archive || false));
compartmentCode$: Observable<string> = this._activatedRoute.params.pipe(
map((params) => decodeURIComponent(params?.compartmentCode ?? '') || undefined)
);
buyerNumber$ = this._activatedRoute.queryParams.pipe(map((params) => params.buyerNumber));
items$ = combineLatest([this.orderNumber$, this.compartmentCode$, this.archive$]).pipe(
switchMap(([orderNumber, compartmentCode, archive]) =>
// compartmentCode
// ? this._domainGoodsInService.getWarenausgabeItemByCompartment(compartmentCode, archive)
// : this._domainGoodsInService.getWarenausgabeItemByOrderNumber(orderNumber, archive)
this._domainGoodsInService.getOrderItemsByOrderNumber(orderNumber)
),
withLatestFrom(this.processingStatus$, this.buyerNumber$),
map(([response, processingStatus, buyerNumber]) => {
return response.result.filter(
(item) => item.processingStatus === +processingStatus && (!!buyerNumber ? item.buyerNumber === buyerNumber : true)
);
}),
tap((items) => this.openModalIfItemsHaveDifferentCustomers(items)),
shareReplay()
);
constructor(
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService,
private _domainGoodsInService: DomainOmsService,
private _router: Router,
private _uiModal: UiModalService
) {}
ngOnInit() {
this.updateBreadcrumb();
}
async updateBreadcrumb() {
const orderNumber = this._activatedRoute.snapshot.params.orderNumber;
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
const processingStatus = this._activatedRoute.snapshot.params.processingStatus;
const buyerNumber = this._activatedRoute.snapshot.queryParams.buyerNumber;
const archive = this._activatedRoute.snapshot.queryParams.archive;
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this.processId,
name: 'Bearbeiten',
path: compartmentCode
? `/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}/edit`
: `/kunde/${this.processId}/order/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}/edit`,
section: 'customer',
params: { buyerNumber, archive },
tags: ['customer-order', 'edit', compartmentCode || orderNumber],
});
}
navigateToDetailsPage({ options }: { options?: { processingStatus?: number } }) {
const orderNumber = this._activatedRoute.snapshot.params.orderNumber;
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
const buyerNumber = this._activatedRoute.snapshot.queryParams.buyerNumber;
const archive = this._activatedRoute.snapshot.queryParams.archive;
compartmentCode
? this._router.navigate(
[`/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`],
{ queryParams: { buyerNumber, archive } }
)
: this._router.navigate([`/kunde/${this.processId}/order/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`], {
queryParams: { buyerNumber, archive },
});
}
openModalIfItemsHaveDifferentCustomers(items: OrderItemListItemDTO[]) {
const buyerNumbers = new Set(items.map((item) => item.buyerNumber));
if (buyerNumbers.size > 1) {
this._uiModal.open({
content: UiMessageModalComponent,
title: 'Achtung',
data: {
message: 'Anzeige ist fehlerhaft.\nEs wurden mehrere Positionen gefunden, die unterschiedlichen Kunden zugeordnet sind.',
},
});
}
}
}

View File

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

View File

@@ -0,0 +1,2 @@
export * from './customer-order-edit.component';
export * from './customer-order-edit.module';

View File

@@ -0,0 +1,52 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomerOrderDetailsComponent, CustomerOrderDetailsModule } from './customer-order-details';
import { CustomerOrderEditComponent } from './customer-order-edit';
import { CustomerOrderSearchComponent, CustomerOrderSearchModule } from './customer-order-search';
import { CustomerOrderSearchMainComponent, CustomerOrderSearchMainModule } from './customer-order-search/search-main';
import { CustomerOrderSearchResultsComponent, CustomerOrderSearchResultsModule } from './customer-order-search/search-results';
import { CustomerOrderComponent } from './customer-order.component';
const routes: Routes = [
{
path: '',
component: CustomerOrderComponent,
children: [
{
path: '',
component: CustomerOrderSearchComponent,
children: [
{ path: '', component: CustomerOrderSearchMainComponent },
{ path: 'results', component: CustomerOrderSearchResultsComponent },
],
},
{
path: 'details/compartment/:compartmentCode/:processingStatus',
component: CustomerOrderDetailsComponent,
},
{
path: 'details/order/:orderNumber/:processingStatus',
component: CustomerOrderDetailsComponent,
},
{
path: 'details/compartment/:compartmentCode/:processingStatus/edit',
component: CustomerOrderEditComponent,
},
{
path: 'details/order/:orderNumber/:processingStatus/edit',
component: CustomerOrderEditComponent,
},
],
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CustomerOrderSearchModule,
CustomerOrderSearchMainModule,
CustomerOrderSearchResultsModule,
CustomerOrderDetailsModule,
],
exports: [RouterModule],
})
export class CustomerOrderRoutingModule {}

View File

@@ -0,0 +1,29 @@
<div class="goods-out-search-filter-content">
<button class="btn-close" type="button" (click)="close.emit()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
<div class="goods-out-search-filter-content-main">
<h1 class="title">Filter</h1>
<ui-filter
[filter]="filter"
[loading]="loading$ | async"
(search)="applyFilter()"
[hint]="message"
resizeInputOptionsToElement="page-goods-out-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

@@ -0,0 +1,45 @@
:host {
@apply block;
}
.goods-out-search-filter-content {
@apply relative mx-auto;
}
.btn-close {
@apply absolute text-inactive-customer top-3 p-4 right-4 outline-none border-none bg-transparent;
}
.goods-out-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

@@ -0,0 +1,129 @@
import {
Component,
ChangeDetectionStrategy,
Output,
EventEmitter,
ChangeDetectorRef,
OnInit,
OnDestroy,
Input,
ViewChild,
} from '@angular/core';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { OrderItemListItemDTO } from '@swagger/oms';
import { UiFilter, UiFilterComponent } from '@ui/filter';
import { Observable, Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { CustomerOrderSearchStore } from '../customer-order-search.store';
@Component({
selector: 'page-customer-order-search-filter',
templateUrl: 'customer-order-search-filter.component.html',
styleUrls: ['customer-order-search-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
@Output()
close = new EventEmitter();
filter: UiFilter;
loading$: Observable<boolean>;
message: string;
@Input()
processId: number;
private _onDestroy$ = new Subject();
@ViewChild(UiFilterComponent, { static: false })
uiFilter: UiFilterComponent;
constructor(
private _goodsOutSearchStore: CustomerOrderSearchStore,
private _breadcrumb: BreadcrumbService,
private _cdr: ChangeDetectorRef,
private _router: Router
) {}
ngOnInit() {
this._initSettings();
this._initLoading$();
}
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.complete();
}
private _initSettings() {
this.filter = UiFilter.create(this._goodsOutSearchStore.filter);
}
private _initLoading$() {
this.loading$ = this._goodsOutSearchStore.fetching$;
}
async resetFilter() {
const queryParams = { main_qs: this.filter?.getQueryParams()?.main_qs || '' };
this._goodsOutSearchStore.resetFilter(queryParams);
this._initSettings();
this._cdr.markForCheck();
}
async applyFilter() {
this.uiFilter?.cancelAutocomplete();
this._goodsOutSearchStore.setFilter(this.filter);
this.message = undefined;
await this.updateQueryParams();
this._goodsOutSearchStore.searchResult$.pipe(takeUntil(this._onDestroy$), take(1)).subscribe((result) => {
if (result.results.error) {
} else {
if (result.results.hits > 0) {
if (result.results.hits === 1) {
const orderItem = result.results.result[0];
this._router.navigate([this.getDetailsPath(orderItem)]);
} else {
this._router.navigate(['/kunde', this.processId, 'order', 'results'], {
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
});
}
this.close.emit();
} else {
this.message = 'keine Suchergebnisse';
}
this._cdr.markForCheck();
}
});
this._goodsOutSearchStore.search({ clear: true });
}
async updateBreadcrumb() {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this.processId,
name: 'Warenausgabe',
path: `/kunde/${this.processId}/order`,
tags: ['customer-order', 'main', 'filter'],
section: 'customer',
params: this._goodsOutSearchStore.filter?.getQueryParams(),
});
}
async updateQueryParams() {
await this._router.navigate([], { queryParams: this._goodsOutSearchStore.filter?.getQueryParams() });
this.updateBreadcrumb();
}
getDetailsPath(item: OrderItemListItemDTO) {
return item?.compartmentCode
? `/kunde/${this.processId}/order/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/${this.processId}/order/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
}
}

View File

@@ -0,0 +1 @@
export * from './customer-order-search-filter.component';

View File

@@ -0,0 +1,15 @@
<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-customer-order-search-filter
[processId]="processId$ | async"
(close)="shellFilterOverlay.close()"
*ngIf="shellFilterOverlay.isOpen"
>
</page-customer-order-search-filter>
</shell-filter-overlay>

View File

@@ -0,0 +1,16 @@
:host {
@apply flex flex-col w-full box-content relative;
}
.filter {
@apply absolute font-sans flex items-center font-bold bg-wild-blue-yonder border-0 text-regular -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-dark-cerulean text-white ml-px-5;
}
}

View File

@@ -0,0 +1,71 @@
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 { UiFilter, UiFilterAutocompleteProvider } from '@ui/filter';
import { isEqual } from 'lodash';
import { combineLatest, Subject } from 'rxjs';
import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
import { CustomerOrderSearchStore } from './customer-order-search.store';
import { CustomerOrderSearchMainAutocompleteProvider } from './providers/customer-order-search-main-autocomplete.provider';
@Component({
selector: 'page-customer-order-search',
templateUrl: 'customer-order-search.component.html',
styleUrls: ['customer-order-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
CustomerOrderSearchStore,
{
provide: UiFilterAutocompleteProvider,
useClass: CustomerOrderSearchMainAutocompleteProvider,
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 CustomerOrderSearchComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
hasFilter$ = combineLatest([this._goodsOutSearchStore.filter$, this._goodsOutSearchStore.defaultSettings$]).pipe(
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), UiFilter.create(defaultFilter).getQueryParams()))
);
processId$ = this._activatedRoute.data.pipe(map((data) => +data.processId));
constructor(
private _goodsOutSearchStore: CustomerOrderSearchStore,
private _breadcrumb: BreadcrumbService,
private _activatedRoute: ActivatedRoute
) {}
ngOnInit() {
this.processId$.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._activatedRoute.queryParams)).subscribe(([processId]) => {
// if (params && Object.keys(params).length === 0) {
// console.log('params is empty');
// this._goodsOutSearchStore.setQueryParams(params);
// this._goodsOutSearchStore.loadSettings();
// } else {
// // this._goodsOutSearchStore.resetFilter(params);
// }
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: 'Kundenbestellung',
path: `/kunde/${processId}/order`,
tags: ['customer-order', 'main', 'filter'],
section: 'customer',
});
});
}
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
}

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomerOrderSearchComponent } from './customer-order-search.component';
import { CustomerOrderSearchFilterComponent } from './customer-order-search-filter';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [CommonModule, UiIconModule, RouterModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
exports: [CustomerOrderSearchComponent],
declarations: [CustomerOrderSearchComponent, CustomerOrderSearchFilterComponent],
})
export class CustomerOrderSearchModule {}

View File

@@ -0,0 +1,228 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { CacheService } from '@core/cache';
import { DomainGoodsService, DomainOmsService } 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 { Observable, Subject } from 'rxjs';
import { switchMap, filter, tap, first, map } from 'rxjs/operators';
export interface CustomerOrderSearchState {
defaultSettings?: QuerySettingsDTO;
queryParams?: Record<string, string>;
filter?: UiFilter;
message?: string;
fetching: boolean;
silentFetching: boolean;
hits: number;
results: OrderItemListItemDTO[];
}
@Injectable()
export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearchState> {
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 silentFetching() {
return this.get((s) => s.silentFetching);
}
get queryParams() {
return this.get((s) => s.queryParams);
}
readonly queryParams$ = this.select((s) => s.queryParams);
private _searchResultSubject = new Subject<{ results: ListResponseArgsOfOrderItemListItemDTO; cached: boolean }>();
readonly searchResult$ = this._searchResultSubject.asObservable();
private _searchResultFromCacheSubject = new Subject<{ hits: number; results: OrderItemListItemDTO[] }>();
readonly searchResultFromCache$ = this._searchResultFromCacheSubject.asObservable();
private _searchResultClearedSubject = new Subject<void>();
readonly searchResultCleared = this._searchResultClearedSubject.asObservable();
constructor(private _domainGoodsInService: DomainOmsService, private _cache: CacheService) {
super({
fetching: false,
silentFetching: false,
hits: 0,
results: [],
});
this.loadDefaultSettings();
}
async loadDefaultSettings() {
const defaultSettings = await this._domainGoodsInService
.settings()
.pipe(map((res) => res?.result))
.toPromise();
const filter = UiFilter.create(defaultSettings);
if (this.queryParams) {
filter.fromQueryParams(this.queryParams);
}
this.setFilter(filter);
this.patchState({ defaultSettings });
}
setFilter(filter: UiFilter | QuerySettingsDTO) {
if (filter instanceof UiFilter) {
this.patchState({ filter });
} else {
this.patchState({ filter: UiFilter.create(filter) });
}
}
resetFilter(queryParams?: Record<string, string>) {
const filter = UiFilter.create(this.defaultSettings);
if (!!queryParams) {
filter?.fromQueryParams(queryParams);
}
this.patchState({ queryParams });
this.setFilter(filter);
}
setQueryParams(queryParams: Record<string, string>) {
this.patchState({ queryParams });
if (this.filter instanceof UiFilter) {
this.filter?.fromQueryParams(queryParams);
this.setFilter(this.filter);
}
}
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
options$.pipe(
switchMap((options) => {
return this.results$.pipe(
map((results) => [options, results]),
first()
);
}),
switchMap(([options, results]) => {
return this.filter$.pipe(
filter((f) => f instanceof UiFilter),
map((filter) => [options, results, filter]),
first()
);
}),
tap(([options, _, filter]: [{ clear?: boolean; siletReload?: boolean }, OrderItemListItemDTO[], UiFilter]) => {
if (!options?.siletReload) {
this.patchState({ fetching: true });
} else {
this.patchState({ silentFetching: true });
}
if (options?.clear) {
this._searchResultClearedSubject.next();
this._cache.delete(filter?.getQueryToken());
}
}),
switchMap(([options, results, filter]) => {
const queryToken = filter?.getQueryToken() ?? {};
let cachedResultCount: number;
const cached = options?.siletReload && this._cache.get(filter?.getQueryToken());
if (cached) {
const cachedResults = this._cache.get(queryToken);
if (cachedResults?.results?.length > 0) {
this.patchState(cachedResults);
cachedResultCount = cachedResults.results.length;
this._searchResultFromCacheSubject.next({ hits: cachedResults.hits, results: cachedResults.results });
}
}
if (options.clear) {
queryToken.skip = 0;
queryToken.take = 50;
} else if (options.siletReload) {
queryToken.skip = 0;
queryToken.take = cachedResultCount || results.length || 50;
} else {
queryToken.skip = results.length;
queryToken.take = 50;
}
return this._domainGoodsInService.search(queryToken).pipe(
tapResponse(
(res) => {
let _results: OrderItemListItemDTO[] = [];
if (options.siletReload) {
_results = res.result;
} else if (options.clear) {
_results = res.result;
} else {
_results = [...results, ...(res.result ?? [])];
}
this.patchState({
hits: res.hits,
results: _results,
fetching: false,
silentFetching: false,
});
this._cache.set(filter?.getQueryToken(), {
hits: res.hits,
results: _results,
fetching: false,
});
this._searchResultSubject.next({ results: res, cached });
},
(err: Error) => {
if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
this._searchResultSubject.next({ results: err.error, cached });
} else {
this._searchResultSubject.next({
results: {
error: true,
message: err.message,
},
cached,
});
}
this.patchState({ fetching: false, silentFetching: false });
console.error('GoodsInSearchStore.search()', err);
}
)
);
})
)
);
}

View File

@@ -0,0 +1,7 @@
export * from './customer-order-search-filter';
export * from './providers';
export * from './search-main';
export * from './search-results';
export * from './customer-order-search.component';
export * from './customer-order-search.module';
export * from './customer-order-search.store';

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@angular/core';
import { DomainOmsService } from '@domain/oms';
import { UiFilterAutocomplete, UiFilterAutocompleteProvider, UiInput } from '@ui/filter';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@Injectable()
export class CustomerOrderSearchMainAutocompleteProvider extends UiFilterAutocompleteProvider {
for = 'goods-out';
constructor(private omsService: DomainOmsService) {
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.omsService
.complete({
filter,
input: input.value,
take: 5,
type,
})
.pipe(
catchError(() => of({ result: [] })),
map((res) => res.result)
);
} else {
return of([]);
}
}
}

View File

@@ -0,0 +1 @@
export * from './customer-order-search-main-autocomplete.provider';

View File

@@ -0,0 +1,22 @@
<div class="search-main">
<h1 class="search-main-title">Kundenbestellung</h1>
<p class="search-main-paragraph">
Suchen Sie den Kundenbestellungen via <br />
Kundenname, ISBN/EAN, Bestellnummer, <br />
Vorgang ID, Abholscheinnummer, <br />
Abholfachnummer, Kundenkartennummer oder scannen Sie <br />
die Kundenkarte.
</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"
(queryChange)="queryChangeDebounce()"
>
</ui-filter-input-group-main>
</ng-container>
</div>

View File

@@ -0,0 +1,25 @@
:host {
@apply block;
}
.goods-out-list-navigation {
@apply text-center text-2xl text-active-branch block bg-white rounded-t-card font-bold no-underline py-5;
}
.search-main {
@apply bg-white text-center rounded-t-card py-5 shadow-card;
height: calc(100vh - 380px);
.search-main-title {
@apply text-2xl font-bold;
}
.search-main-paragraph {
@apply text-2xl mb-12 mt-6;
}
ui-filter-input-group-main {
@apply block mx-auto;
max-width: 600px;
}
}

View File

@@ -0,0 +1,156 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { OrderItemListItemDTO } from '@swagger/oms';
import { UiFilterInputGroupMainComponent } from '@ui/filter';
import { debounce, isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import { debounceTime, first, map, withLatestFrom } from 'rxjs/operators';
import { CustomerOrderSearchStore } from '../customer-order-search.store';
@Component({
selector: 'page-customer-order-search-main',
templateUrl: 'customer-order-search-main.component.html',
styleUrls: ['customer-order-search-main.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
filter$ = this._goodsOutSearchStore.filter$;
loading$ = this._goodsOutSearchStore.fetching$;
queryChanged$ = new BehaviorSubject<boolean>(false);
message: string;
lastProcessId: number | undefined;
private _subscriptions = new Subscription();
get processId() {
return +this._activatedRoute.snapshot.data.processId;
}
processId$ = this._activatedRoute.data.pipe(map((data) => +data.processId));
@ViewChild(UiFilterInputGroupMainComponent, { static: false })
filterInputGroup: UiFilterInputGroupMainComponent;
constructor(
private _goodsOutSearchStore: CustomerOrderSearchStore,
private _cdr: ChangeDetectorRef,
private _router: Router,
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService
) {}
ngOnInit() {
// Clear scroll position
localStorage.removeItem(`SCROLL_POSITION_${this.processId}`);
this._subscriptions.add(
this._goodsOutSearchStore.filter$.subscribe((f) => {
this._cdr.markForCheck();
})
);
this._subscriptions.add(
combineLatest([this.processId$, this._activatedRoute.queryParams])
.pipe(debounceTime(50), withLatestFrom(this.queryChanged$))
.subscribe(([[processId, queryParams], queryChanged]) => {
if (!isEqual(queryParams, this._goodsOutSearchStore.filter?.getQueryParams()) && !queryChanged) {
this._goodsOutSearchStore.resetFilter(queryParams);
}
this.queryChanged$.next(false);
this.removeBreadcrumbs(processId);
this.updateBreadcrumb(processId, queryParams);
})
);
}
ngOnDestroy() {
this._subscriptions.unsubscribe();
}
async removeBreadcrumbs(processId: number) {
const resultCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'results'])
.pipe(first())
.toPromise();
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'details'])
.pipe(first())
.toPromise();
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'edit']).pipe(first()).toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
resultCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
detailsCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
}
async search() {
this.filterInputGroup?.cancelAutocomplete();
await this.updateQueryParams(this.processId);
this.message = undefined;
this._goodsOutSearchStore.searchResult$.pipe(first()).subscribe((result) => {
if (result.results.error) {
} else {
if (result.results.hits > 0) {
if (result.results.hits === 1) {
const orderItem = result.results.result[0];
this._router.navigate([this.getDetailsPath(orderItem, this.processId)]);
} else {
this._router.navigate(['/kunde', this.processId, 'order', 'results'], {
queryParams: this._goodsOutSearchStore.filter.getQueryParams(),
});
}
} else {
this.message = 'keine Suchergebnisse';
}
}
this._cdr.markForCheck();
});
this._goodsOutSearchStore.search({ clear: true });
}
async updateBreadcrumb(processId: number, params: Record<string, string>) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: 'Kundenbestellung',
path: `/kunde/${processId}/order`,
tags: ['customer-order', 'main', 'filter'],
section: 'customer',
params,
});
}
async updateQueryParams(processId: number) {
const queryParams = { ...this._goodsOutSearchStore.filter?.getQueryParams() };
queryParams.main_qs = queryParams.main_qs ?? '';
await this._router.navigate([], { queryParams });
this.updateBreadcrumb(processId, queryParams);
}
getDetailsPath(item: OrderItemListItemDTO, processId: number) {
return item?.compartmentCode
? `/kunde/${processId}/order/details/compartment/${encodeURIComponent(item?.compartmentCode)}/${item?.processingStatus}`
: `/kunde/${processId}/order/details/order/${encodeURIComponent(item?.orderNumber)}/${item?.processingStatus}`;
}
queryChangeDebounce = debounce(async () => {
this.queryChanged$.next(true);
await this.updateQueryParams(this.processId);
}, 500);
}

View File

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

View File

@@ -0,0 +1,2 @@
export * from './customer-order-search-main.component';
export * from './customer-order-search-main.module';

View File

@@ -0,0 +1,21 @@
import { Pipe, PipeTransform } from '@angular/core';
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
@Pipe({
name: 'goodsOutItemSelectable',
})
export class CustomerOrderItemSelectablePipe implements PipeTransform {
transform(
item: OrderItemListItemDTO,
selectionRules: (action: KeyValueDTOOfStringAndString) => boolean,
selectedItems: OrderItemListItemDTO[]
): boolean {
if (selectedItems.length > 0) {
const firstSelectedItem = selectedItems[0];
const selectedLabel = firstSelectedItem.actions.find((action) => selectionRules(action)).label;
return item?.actions?.some((action) => selectionRules(action) && action.label === selectedLabel);
} else {
return item?.actions?.some((action) => selectionRules(action));
}
}
}

View File

@@ -0,0 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core';
import { OrderItemListItemDTO } from '@swagger/oms';
@Pipe({
name: 'goodsOutItemSelected',
})
export class CustomerOrderItemSelectedPipe implements PipeTransform {
transform(item: OrderItemListItemDTO, selectedOrderItemSubsetIds: number[]): any {
return selectedOrderItemSubsetIds?.includes(item?.orderItemSubsetId);
}
}

View File

@@ -0,0 +1,56 @@
<div class="hits">{{ hits$ | async }} Titel</div>
<ui-scroll-container
*ngIf="!(listEmpty$ | async); else emptyMessage"
[loading]="loading$ | async"
(reachEnd)="loadMore()"
[deltaEnd]="150"
[itemLength]="itemLength$ | async"
[initialScroll]="scrollTo"
>
<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 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; 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="!lastOrderNumber"></div>
</ng-container>
</shared-goods-in-out-order-group>
</ng-container>
</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>
<div class="actions" *ngIf="actions$ | async; let actions">
<button
[disabled]="(loadingFetchedActionButton$ | async) || (loading$ | async)"
class="cta-action"
*ngFor="let action of actions"
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
(click)="handleAction(action)"
>
<ui-spinner [show]="(loadingFetchedActionButton$ | async) || (loading$ | async)">{{ action.label }}</ui-spinner>
</button>
</div>

View File

@@ -0,0 +1,47 @@
:host {
@apply block relative;
}
.hits {
@apply text-active-customer text-right mb-3 font-semibold text-base;
}
.empty-message {
@apply bg-white text-center font-semibold text-inactive-customer py-10 rounded-card;
}
.divider {
height: 2px;
}
shared-goods-in-out-order-group-item {
@apply cursor-pointer;
}
.actions {
@apply fixed bottom-28 inline-grid grid-flow-col gap-7;
left: 50%;
transform: translateX(-50%);
.cta-action {
@apply border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none self-end whitespace-nowrap;
&:disabled {
@apply bg-inactive-branch border-inactive-branch text-white;
}
}
.cta-action-primary {
@apply bg-brand text-white;
}
.cta-action-secondary {
@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

@@ -0,0 +1,327 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, TrackByFunction } from '@angular/core';
import { debounceTime, first, map, shareReplay, takeUntil, withLatestFrom } from 'rxjs/operators';
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
import { ActivatedRoute, Router } from '@angular/router';
import { CustomerOrderSearchStore } from '../customer-order-search.store';
import { BehaviorSubject, combineLatest, Observable, Subject, Subscription } from 'rxjs';
import { BreadcrumbService } from '@core/breadcrumb';
import { ComponentStore } from '@ngrx/component-store';
import { CommandService } from '@core/command';
import { OrderItemsContext } from '@domain/oms';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { UiScrollContainerComponent } from '@ui/scroll-container';
import { UiFilter } from '@ui/filter';
export interface CustomerOrderSearchResultsState {
selectedOrderItemSubsetIds: number[];
}
@Component({
selector: 'page-customer-order-search-results',
templateUrl: 'customer-order-search-results.component.html',
styleUrls: ['customer-order-search-results.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderSearchResultsComponent extends ComponentStore<CustomerOrderSearchResultsState> implements OnInit, OnDestroy {
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
items$: Observable<OrderItemListItemDTO[]> = this._goodsOutSearchStore.results$;
itemLength$ = this.items$.pipe(map((items) => items?.length));
hits$ = this._goodsOutSearchStore.hits$;
loading$ = this._goodsOutSearchStore.fetching$.pipe(shareReplay());
loadingFetchedActionButton$ = new BehaviorSubject<boolean>(false);
listEmpty$ = combineLatest([this.loading$, this.hits$]).pipe(
map(([loading, hits]) => !loading && hits === 0),
shareReplay()
);
selectedOrderItemSubsetIds$ = this.select((s) => s.selectedOrderItemSubsetIds);
get selectedOrderItemSubsetIds() {
return this.get((s) => s.selectedOrderItemSubsetIds);
}
actions$ = combineLatest([this.items$, this.selectedOrderItemSubsetIds$]).pipe(
map(([items, selectedOrderItemSubsetIds]) =>
items?.find((item) => selectedOrderItemSubsetIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId))
),
map((item) => item?.actions?.filter((action) => this.selectionRules(action)))
);
get selectedItems() {
const items = this._goodsOutSearchStore.results;
const selectedOrderItemSubsetIds = this.selectedOrderItemSubsetIds;
return items.filter((item) => selectedOrderItemSubsetIds.some((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId));
}
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;
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
previousProcessId: undefined | number;
private _onDestroy$ = new Subject();
trackByFn: TrackByFunction<OrderItemListItemDTO> = (index, item) => `${item.orderId}${item.orderItemId}${item.orderItemSubsetId}`;
private _searchResultSubscription: Subscription;
scrollTo: number;
constructor(
private _goodsOutSearchStore: CustomerOrderSearchStore,
private _router: Router,
private _activatedRoute: ActivatedRoute,
private _breadcrumb: BreadcrumbService,
private _commandService: CommandService,
private _modal: UiModalService
) {
super({
selectedOrderItemSubsetIds: [],
});
}
saveScrollPosition(processId: number, scrollPosition: number) {
localStorage.setItem(`SCROLL_POSITION_${processId}`, JSON.stringify(scrollPosition));
}
getScrollPosition(processId: number): number | undefined {
try {
const scroll_position = localStorage.getItem(`SCROLL_POSITION_${processId}`);
return scroll_position ? JSON.parse(scroll_position) : undefined;
} catch {
return undefined;
}
}
removeScrollPosition(processId: number) {
localStorage.removeItem(`SCROLL_POSITION_${processId}`);
}
ngOnInit() {
this.processId$
.pipe(takeUntil(this._onDestroy$), debounceTime(10), withLatestFrom(this._activatedRoute.queryParams))
.subscribe(async ([processId, params]) => {
if (this.previousProcessId && this.previousProcessId !== processId) {
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
}
if (!(this._goodsOutSearchStore.filter instanceof UiFilter)) {
await this._goodsOutSearchStore.loadDefaultSettings();
}
this._goodsOutSearchStore.resetFilter(params);
this.updateBreadcrumb(processId, params);
if (this.previousProcessId !== processId) {
this.initInitialSearch(processId);
this.createBreadcrumb(processId, params);
this.removeBreadcrumbs(processId);
this.previousProcessId = processId;
if (!this._goodsOutSearchStore.results?.length) {
this._goodsOutSearchStore.search({ siletReload: true });
}
}
});
this._goodsOutSearchStore.searchResultCleared.pipe(takeUntil(this._onDestroy$)).subscribe((_) => this.clearSelectedItems());
}
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.complete();
if (this._searchResultSubscription) {
this._searchResultSubscription.unsubscribe();
}
this.updateBreadcrumb(this.previousProcessId, this._goodsOutSearchStore.filter?.getQueryParams());
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
}
async removeBreadcrumbs(processId: number) {
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'details'])
.pipe(first())
.toPromise();
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'edit']).pipe(first()).toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
detailsCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
}
async createBreadcrumb(processId: number, params: Record<string, string>) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: this.getBreadcrumbName(params),
path: `/kunde/${processId}/order/results`,
section: 'customer',
params,
tags: ['customer-order', 'results', 'filter'],
});
}
async updateBreadcrumb(processId: number, queryParams: Record<string, string>) {
if (queryParams) {
const crumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'results', 'filter'])
.pipe(first())
.toPromise();
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
const params = { ...queryParams };
for (const crumb of crumbs) {
this._breadcrumb.patchBreadcrumb(crumb.id, {
name,
params,
});
}
}
}
getBreadcrumbName(params: Record<string, string>) {
const input = params?.main_qs;
return input?.replace('ORD:', '') ?? 'Alle';
}
initInitialSearch(processId: number) {
if (this._searchResultSubscription) {
this._searchResultSubscription.unsubscribe();
}
this._searchResultSubscription = new Subscription();
this._searchResultSubscription.add(
this._goodsOutSearchStore.searchResult$.subscribe(async (result) => {
const queryParams = this._goodsOutSearchStore.filter?.getQueryParams();
if (result.results.hits === 0) {
await this._router.navigate([`/kunde/${processId}/order`], {
queryParams,
});
} else {
await this.createBreadcrumb(processId, queryParams);
if (result.results.hits === 1) {
await this.navigateToDetails(processId, result.results.result[0]);
} else if (!result.cached) {
const scrollPos = this.getScrollPosition(processId) || 0;
this.scrollTo = scrollPos;
this.scrollContainer?.scrollTo(scrollPos);
this.removeScrollPosition(processId);
}
}
})
);
this._searchResultSubscription.add(
this._goodsOutSearchStore.searchResultFromCache$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result?.hits > 0) {
const scrollPos = this.getScrollPosition(processId) || 0;
this.scrollTo = scrollPos;
this.scrollContainer?.scrollTo(scrollPos);
this.removeScrollPosition(processId);
}
})
);
}
async loadMore() {
if (
this._goodsOutSearchStore.hits > this._goodsOutSearchStore.results.length &&
!this._goodsOutSearchStore.fetching &&
!this._goodsOutSearchStore.silentFetching
) {
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
this._goodsOutSearchStore.search({});
}
}
navigateToDetails(processId: number, orderItem: OrderItemListItemDTO) {
const orderNumber = orderItem.orderNumber;
const processingStatus = orderItem.processingStatus;
const compartmentCode = orderItem.compartmentCode;
const archive = !!this._goodsOutSearchStore.filter?.getQueryParams()?.main_archive || false;
if (compartmentCode) {
this._router.navigate([`/kunde/${processId}/order/details/compartment/${encodeURIComponent(compartmentCode)}/${processingStatus}`], {
queryParams: {
buyerNumber: orderItem.buyerNumber,
archive,
},
});
} else {
this._router.navigate([`/kunde/${processId}/order/details/order/${encodeURIComponent(orderNumber)}/${processingStatus}`], {
queryParams: {
archive,
},
});
}
}
setSelectedItem(item: OrderItemListItemDTO, selected: boolean) {
const included = this.selectedOrderItemSubsetIds.includes(item.orderItemSubsetId);
if (!included && selected) {
this.patchState({
selectedOrderItemSubsetIds: [...this.selectedOrderItemSubsetIds, item.orderItemSubsetId],
});
} else if (included && !selected) {
this.patchState({
selectedOrderItemSubsetIds: this.selectedOrderItemSubsetIds.filter((id) => id !== item?.orderItemSubsetId),
});
}
}
clearSelectedItems() {
this.patchState({ selectedOrderItemSubsetIds: [] });
}
selectionRules(action: KeyValueDTOOfStringAndString) {
return (
(action.command.includes('FETCHED') && action.key === '256' && action.enabled !== false) ||
action.command.includes('COLLECT_ON_DELIVERYNOTE')
);
}
async handleAction(action: KeyValueDTOOfStringAndString) {
this.loadingFetchedActionButton$.next(true);
const commandData: OrderItemsContext = {
items: this.selectedItems,
};
try {
this.saveScrollPosition(this.previousProcessId, this.scrollContainer?.scrollPos);
await this._commandService.handleCommand(action.command, commandData);
this._goodsOutSearchStore.search({ siletReload: true, clear: true });
this.clearSelectedItems();
this.loadingFetchedActionButton$.next(false);
} catch (error) {
console.error(error);
this._modal.open({
content: UiErrorModalComponent,
data: error,
});
this.loadingFetchedActionButton$.next(false);
}
}
}

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomerOrderSearchResultsComponent } from './customer-order-search-results.component';
import { GoodsInOutOrderGroupModule } from '@shared/components/goods-in-out';
import { UiCommonModule } from '@ui/common';
import { CustomerOrderItemSelectablePipe } from './customer-order-item-selectable.pipe';
import { CustomerOrderItemSelectedPipe } from './customer-order-item-selectede.pipe';
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { UiScrollContainerModule } from '@ui/scroll-container';
@NgModule({
imports: [CommonModule, GoodsInOutOrderGroupModule, UiCommonModule, UiSpinnerModule, UiScrollContainerModule],
exports: [CustomerOrderSearchResultsComponent],
declarations: [CustomerOrderSearchResultsComponent, CustomerOrderItemSelectablePipe, CustomerOrderItemSelectedPipe],
})
export class CustomerOrderSearchResultsModule {}

View File

@@ -0,0 +1,4 @@
export * from './customer-order-item-selectable.pipe';
export * from './customer-order-item-selectede.pipe';
export * from './customer-order-search-results.component';
export * from './customer-order-search-results.module';

View File

@@ -0,0 +1,3 @@
<shell-breadcrumb [key]="processId$ | async" [includesTags]="['customer-order']"></shell-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -0,0 +1,7 @@
:host {
@apply block;
}
shell-breadcrumb {
@apply sticky z-sticky top-0 py-4;
}

View File

@@ -0,0 +1,15 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators';
@Component({
selector: 'page-customer-order',
templateUrl: 'customer-order.component.html',
styleUrls: ['customer-order.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderComponent {
processId$ = this._activatedRoute.data.pipe(map((data) => String(data.processId)));
constructor(private _activatedRoute: ActivatedRoute) {}
}

View File

@@ -0,0 +1,94 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CustomerOrderComponent } from './customer-order.component';
import {
AcceptedActionHandler,
ArrivedActionHandler,
AssembledActionHandler,
AvailableForDownloadActionHandler,
BackToStockActionHandler,
CanceledByBuyerActionHandler,
CanceledByRetailerActionHandler,
CanceledBySupplierActionHandler,
CreateShippingNoteActionHandler,
DeliveredActionHandler,
DetermineSupplierActionHandler,
DispatchedActionHandler,
DownloadedActionHandler,
FetchedActionHandler,
InProcessActionHandler,
NotAvailableActionHandler,
NotFetchedActionHandler,
OrderAtSupplierActionHandler,
OrderingActionHandler,
OverdueActionHandler,
PackedActionHandler,
ParkedActionHandler,
PlacedActionHandler,
PreparationForShippingActionHandler,
PrintCompartmentLabelActionHandler,
PrintShippingNoteActionHandler,
ReOrderActionHandler,
RedirectedInternaqllyActionHandler,
RequestedActionHandler,
ReserverdActionHandler,
ReturnedByBuyerActionHandler,
ShippingNoteActionHandler,
SupplierTemporarilyOutOfStockActionHandler,
ReOrderedActionHandler,
CollectOnDeliveryNoteActionHandler,
PrintPriceDiffQrCodeLabelActionHandler,
} from '@domain/oms';
import { CoreCommandModule } from '@core/command';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { CustomerOrderRoutingModule } from './customer-order-routing.module';
@NgModule({
declarations: [CustomerOrderComponent],
imports: [
CommonModule,
CustomerOrderRoutingModule,
ShellBreadcrumbModule,
CoreCommandModule.forChild([
AcceptedActionHandler,
ArrivedActionHandler,
AssembledActionHandler,
AvailableForDownloadActionHandler,
BackToStockActionHandler,
CanceledByBuyerActionHandler,
CanceledByRetailerActionHandler,
CanceledBySupplierActionHandler,
CreateShippingNoteActionHandler,
DeliveredActionHandler,
DetermineSupplierActionHandler,
DispatchedActionHandler,
DownloadedActionHandler,
FetchedActionHandler,
InProcessActionHandler,
NotAvailableActionHandler,
NotFetchedActionHandler,
OrderAtSupplierActionHandler,
OrderingActionHandler,
OverdueActionHandler,
PackedActionHandler,
ParkedActionHandler,
PlacedActionHandler,
PreparationForShippingActionHandler,
PrintCompartmentLabelActionHandler,
PrintShippingNoteActionHandler,
ReOrderActionHandler,
ReOrderedActionHandler,
RedirectedInternaqllyActionHandler,
RequestedActionHandler,
ReserverdActionHandler,
ReturnedByBuyerActionHandler,
ShippingNoteActionHandler,
SupplierTemporarilyOutOfStockActionHandler,
CollectOnDeliveryNoteActionHandler,
PrintPriceDiffQrCodeLabelActionHandler,
]),
],
exports: [CustomerOrderComponent],
})
export class CustomerOrderModule {}

View File

@@ -0,0 +1,9 @@
// start:ng42.barrel
export * from './customer-order-routing.module';
export * from './customer-order.component';
export * from './customer-order.module';
export * from './customer-order-search';
export * from './customer-order-search/customer-order-search-filter';
export * from './customer-order-search/search-main';
export * from './customer-order-search/search-results';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
/*
* Public API Surface of goods-out
*/
export * from './lib';

View File

@@ -156,7 +156,6 @@ export { CompanyDTO } from './models/company-dto';
export { EntityDTOBaseOfCompanyDTOAndICompany } from './models/entity-dtobase-of-company-dtoand-icompany';
export { EntityDTOContainerOfCategoryDTO } from './models/entity-dtocontainer-of-category-dto';
export { CategoryDTO } from './models/category-dto';
export { EntityDTOBaseOfCategoryDTOAndICategory } from './models/entity-dtobase-of-category-dtoand-icategory';
export { EntityDTOContainerOfFileDTO } from './models/entity-dtocontainer-of-file-dto';
export { FileDTO } from './models/file-dto';
export { EntityDTOBaseOfFileDTOAndIFile } from './models/entity-dtobase-of-file-dtoand-ifile';

View File

@@ -1,8 +1,8 @@
/* tslint:disable */
import { EntityDTOBaseOfCategoryDTOAndICategory } from './entity-dtobase-of-category-dtoand-icategory';
import { EntityDTOBase } from './entity-dtobase';
import { EntityDTOContainerOfCategoryDTO } from './entity-dtocontainer-of-category-dto';
import { EntityDTOContainerOfTenantDTO } from './entity-dtocontainer-of-tenant-dto';
export interface CategoryDTO extends EntityDTOBaseOfCategoryDTOAndICategory{
export interface CategoryDTO extends EntityDTOBase{
key?: string;
name?: string;
parent?: EntityDTOContainerOfCategoryDTO;

View File

@@ -7,5 +7,6 @@ export interface EntityDTO extends TouchedBase{
id?: number;
pId?: string;
status?: EntityStatus;
uId?: string;
version?: number;
}

View File

@@ -1,4 +0,0 @@
/* tslint:disable */
import { EntityDTOBase } from './entity-dtobase';
export interface EntityDTOBaseOfCategoryDTOAndICategory extends EntityDTOBase{
}

View File

@@ -8,4 +8,5 @@ export interface EntityDTOReferenceContainer extends TouchedBase{
id?: number;
pId?: string;
selected?: boolean;
uId?: string;
}

View File

@@ -16,6 +16,9 @@ import { ResponseArgsOfOrderItemSubsetDTO } from '../models/response-args-of-ord
import { ResponseArgsOfIEnumerableOfOrderDTO } from '../models/response-args-of-ienumerable-of-order-dto';
import { ListResponseArgsOfOrderListItemDTO } from '../models/list-response-args-of-order-list-item-dto';
import { QueryTokenDTO } from '../models/query-token-dto';
import { ResponseArgsOfQuerySettingsDTO } from '../models/response-args-of-query-settings-dto';
import { ResponseArgsOfIEnumerableOfAutocompleteDTO } from '../models/response-args-of-ienumerable-of-autocomplete-dto';
import { AutocompleteTokenDTO } from '../models/autocomplete-token-dto';
import { ListResponseArgsOfOrderItemListItemDTO } from '../models/list-response-args-of-order-item-list-item-dto';
import { ResponseArgsOfIEnumerableOfOrderItemDTO } from '../models/response-args-of-ienumerable-of-order-item-dto';
import { OrderItemDTO } from '../models/order-item-dto';
@@ -45,6 +48,8 @@ class OrderService extends __BaseService {
static readonly OrderGetOrdersByCompartmentPath = '/order/compartment';
static readonly OrderGetOrdersByBuyerNumberPath = '/buyer/order';
static readonly OrderQueryOrdersPath = '/order/s';
static readonly OrderOrderItemQuerySettingsPath = '/order/item/s/settings';
static readonly OrderQueryOrderItemAutocompletePath = '/order/item/s/complete';
static readonly OrderQueryOrderItemPath = '/order/item/s';
static readonly OrderGetOrderItemPath = '/order/orderitem/{orderItemId}';
static readonly OrderGetOrderItemsPath = '/order/orderitem';
@@ -462,6 +467,75 @@ class OrderService extends __BaseService {
);
}
/**
* Warenausgabe-Filter
*/
OrderOrderItemQuerySettingsResponse(): __Observable<__StrictHttpResponse<ResponseArgsOfQuerySettingsDTO>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
let req = new HttpRequest<any>(
'GET',
this.rootUrl + `/order/item/s/settings`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfQuerySettingsDTO>;
})
);
}
/**
* Warenausgabe-Filter
*/
OrderOrderItemQuerySettings(): __Observable<ResponseArgsOfQuerySettingsDTO> {
return this.OrderOrderItemQuerySettingsResponse().pipe(
__map(_r => _r.body as ResponseArgsOfQuerySettingsDTO)
);
}
/**
* Autocomplete für Bestellposten
* @param payload Suchkriterien
*/
OrderQueryOrderItemAutocompleteResponse(payload?: AutocompleteTokenDTO): __Observable<__StrictHttpResponse<ResponseArgsOfIEnumerableOfAutocompleteDTO>> {
let __params = this.newParams();
let __headers = new HttpHeaders();
let __body: any = null;
__body = payload;
let req = new HttpRequest<any>(
'POST',
this.rootUrl + `/order/item/s/complete`,
__body,
{
headers: __headers,
params: __params,
responseType: 'json'
});
return this.http.request<any>(req).pipe(
__filter(_r => _r instanceof HttpResponse),
__map((_r) => {
return _r as __StrictHttpResponse<ResponseArgsOfIEnumerableOfAutocompleteDTO>;
})
);
}
/**
* Autocomplete für Bestellposten
* @param payload Suchkriterien
*/
OrderQueryOrderItemAutocomplete(payload?: AutocompleteTokenDTO): __Observable<ResponseArgsOfIEnumerableOfAutocompleteDTO> {
return this.OrderQueryOrderItemAutocompleteResponse(payload).pipe(
__map(_r => _r.body as ResponseArgsOfIEnumerableOfAutocompleteDTO)
);
}
/**
* @param queryToken undefined
*/